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 numbers
Code 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
!