Code Review Videos > Map, Filter, Reduce > [Easy] Reduce in TypeScript

[Easy] Reduce in TypeScript

The following are the TypeScript solutions to the reduce problems defined under Map, Filter, Reduce.

As TypeScript / JavaScript are languages I am comfortable with, the only things I will call out are those that I think are of any interest. Always happy to answer questions though. Just leave a comment.

Easy Reduce

// easy-reduce.spec.ts

import { reduceBool, reduceNumbers, reduceString } from "./easy-reduce";

describe("easy reduce", () => {
  describe("reduceNumbers", () => {
    describe("should return the sum of all numbers in an array", () => {
      test("zero element array", () => {
        const given: number[] = [];
        expect(reduceNumbers(given)).toEqual(0);
      });

      test("one element array", () => {
        const given = [1];
        expect(reduceNumbers(given)).toEqual(1);
      });

      test("multiple element array", () => {
        const given = [1, 2, 3];
        expect(reduceNumbers(given)).toEqual(6);
      });
    });
  });

  describe("reduceString", () => {
    describe("should concatenate all the strings in an array", () => {
      test("zero element array", () => {
        const given: string[] = [];
        expect(reduceString(given)).toEqual("");
      });

      test("one element array", () => {
        const given = ["a"];
        expect(reduceString(given)).toEqual("a");
      });

      test("multiple element array", () => {
        const given = ["aaa", "bbb", "ccc"];
        expect(reduceString(given)).toEqual("aaabbbccc");
      });
    });
  });

  describe("reduceBool", () => {
    describe("should return true only if all elements in the array are true", () => {
      test("zero element array", () => {
        const given: boolean[] = [];
        expect(reduceBool(given)).toEqual(false);
      });

      test("one element array - truthy", () => {
        const given = [true];
        expect(reduceBool(given)).toEqual(true);
      });

      test("one element array - falsy", () => {
        const given = [false];
        expect(reduceBool(given)).toEqual(false);
      });

      test("multiple element array - truthy", () => {
        const given = [true, true];
        expect(reduceBool(given)).toEqual(true);
      });

      test("multiple element array - falsy", () => {
        const given = [false, false, false];
        expect(reduceBool(given)).toEqual(false);
      });

      test("multiple element array - mixed", () => {
        const given = [false, true, false, true, false, true];
        expect(reduceBool(given)).toEqual(false);
      });
    });
  });
});Code language: TypeScript (typescript)

And the associated implementation:

// easy-reduce.ts

export const reduceNumbers = (input: number[]): number =>
  input.reduce((acc, number) => acc + number, 0);

export const reduceString = (input: string[]): string =>
  input.reduce((acc, string) => acc + string, "");

export const reduceBool = (input: boolean[]): boolean => {
  return input.length === 0
    ? false
    : input.reduce((acc, bool) => acc && bool, true);
};

export default {
  reduceBool,
  reduceNumbers,
  reduceString,
};Code language: JavaScript (javascript)

Right, so the interesting things here…

Typed empty arrays

In TypeScript you need to tell the compiler what kind of array you are using if the array doesn’t contain any elements:

      test("zero element array", () => {
        const given = [];
        expect(reduceString(given)).toEqual("");
      });
Code language: PHP (php)

That would give an error:

TS7034: Variable 'given' implicitly has type 'any[]' in some locations where its type cannot be determined

To solve this, provide the type of array you are expecting:

const given: number[] = [];Code language: JavaScript (javascript)

If your array has at least one initial value then the type of the array can be inferred:

const given = [1]; // the compiler can figure out we meant an array of numbersCode language: JavaScript (javascript)

Short hand return syntax – aka Arrow function expressions.

Why bother writing more when less will do:

export const reduceNumbers = (input: number[]): number => {
  return input.reduce((acc, number) => {
    return acc + number;
  }, 0);
};Code language: JavaScript (javascript)

If the function can immediately return a value, you don’t need the return.

You can even return objects this way:

const myFunction = () => {
  return {
    a: 'b'
  }
}

// same as

const myFunction = () => ({
  a: "b",
});Code language: JavaScript (javascript)

Note the braces and parens.

Why then, use return inside reduceBool?

Sometimes, less is more. Sometimes, less is less.

export const reduceBool = (input: boolean[]): boolean => {
  return input.length === 0
    ? false
    : input.reduce((acc, bool) => acc && bool, true);
};

// vs

export const reduceBool = (input: boolean[]): boolean =>
  input.length === 0 ? false : input.reduce((acc, bool) => acc && bool, true);Code language: JavaScript (javascript)

Personally I find the second one much harder to parse in my head. There’s just too much going on in one line.

What is the meaning of input.reduce((acc, bool) => acc && bool, true)?

Yeah, sadly for an easy puzzle, this one came out a little complex.

The challenge here is to only return true if every value in the array is true. Otherwise, return false.

We specify an initial value of true for the accumulator:

input.reduce(
  (acc, bool) => 
      acc && bool, 
  true);
Code language: JavaScript (javascript)

The are many signatures for reduce.

The most common in my work is:

  • Argument 1: A function that gets the accumulator and the current value
  • Argument 2: The initial value of the accumulator

So here:

  • Argument 1: (acc, bool) => acc && bool
  • Argument 2: true

If input is a zero element array, the function won’t run.

However, once there is at least one element in the array then the function runs at least once.

Let’s imagine we have: input = [true, false]

On the first run, the function will look like this:

(true, true) => true && true

Looks true to me.

So we move on to the second element in the array, which is false:

(true, false) => true && false

The accumulator / first argument in the function is true because the previous invocation of the function returned true.

The second argument – the current element – is false because that’s the next value from our input array.

acc && bool therefore translates to true && false

We have ourselves a fairly common piece of boolean logic, specifically a boolean AND.

true && false will resolve to be false.

And seeing as we return that false, which then becomes the accumulator, we cannot ever get back to true.

If there was another value in input, and that so happened to be true, then we would get another invocation:

(false, true) => false && true

Same thing, different order, same outcome. Once we are false, we cannot ever get back to true.

So like I say, a slightly over complex piece of code to solve what seems like an easy code challenge.

But Chris, what about every?

I’m aware you can solve this problem with every:

export const reduceBool = (input: boolean[]): boolean =>
  input.length === 0 ? false : input.every((i) => i);Code language: JavaScript (javascript)

But we are trying to learn about reduce!

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.