Skip to content

Instantly share code, notes, and snippets.

@cellog
Created October 7, 2018 03:01
Show Gist options
  • Save cellog/5fc37e932004970a1c466fc3d4b58801 to your computer and use it in GitHub Desktop.
Save cellog/5fc37e932004970a1c466fc3d4b58801 to your computer and use it in GitHub Desktop.
flatten array, alternate implementations with jest tests and in-browser performance test suite
https://codesandbox.io/s/73w2kq8mvj
// optimized for maintainability and extensibility (developer time is optimized)
// this is nearly 5x as many CPU cycles as the iterative version, and
// because the reducer is pure, 2x as slow as a mutable version
export function flattenWithReduce(arr) {
if (!Array.isArray(arr)) return [arr];
return arr.reduce((flat, sub) => [...flat, ...flattenWithReduce(sub)], []);
}
// compromise: optimized for maintainability and CPU cycles
export function mutableFlattenWithReduce(arr) {
return arr.reduce((flat, sub) => {
if (Array.isArray(sub)) {
flat.concat(mutableFlattenWithReduce(sub));
} else {
flat.push(sub);
}
return flat;
}, []);
}
// optimized for very deeply nested arrays and stack overflow
// also dramatically more performant, about 30% faster than the faster reduce version
// if the functionality is unlikely to be changed, this is the best long-term option
export function flattenIterative(arr) {
const ret = [];
const processing = [arr];
while (processing.length) {
const first = processing.shift();
if (Array.isArray(first)) {
processing.unshift.apply(processing, first);
} else {
ret.push(first);
}
}
return ret;
}
// optimized for total file size and code loading time
// this is brittle, and only works with integers!
// it's also slow, only 20% faster than the slowest flattenWithReduce
export function shortFlatten(arr) {
return ("" + arr).split(",").map(_ => +_);
}
import {
flattenIterative,
shortFlatten,
flattenWithReduce,
mutableFlattenWithReduce
} from "./flatten"
const tests = flatten => () => {
test("basic", () => {
expect(flatten([])).toEqual([])
})
test("flattened array already", () => {
expect(flatten([1, 2, 3, 4, 5, 6])).toEqual([1, 2, 3, 4, 5, 6])
})
test("nested array", () => {
expect(flatten([[1, 2, [3]], 4])).toEqual([1, 2, 3, 4])
})
test("deeply nested array", () => {
expect(
flatten([
[
[
[
[
[
[
[
[
[
[
[
[
[
[
[
[
[
[
[
[
[
[
[
[
[
[
[
[
[
[
[
[
[
[
[
[
[
[
[
[
[
[
[
[
[
[
[
[
[
[
[
[
[
[
[
[
[
[
[
[
1,
2,
[
3
]
],
4
]
]
]
]
]
]
]
]
]
]
]
]
]
]
]
]
]
]
]
]
]
]
]
]
]
]
]
]
]
]
]
]
]
]
]
]
]
]
]
]
]
]
]
]
]
]
]
]
]
]
]
]
]
]
]
]
]
]
]
])
).toEqual([1, 2, 3, 4])
})
}
describe("shortFlatten", tests(shortFlatten))
describe("flattenIterative", tests(flattenIterative))
describe("flattenWithReduce", tests(flattenWithReduce))
describe("mutableFlattenWithReduce", tests(mutableFlattenWithReduce))
import {
flattenIterative,
flattenWithReduce,
shortFlatten,
mutableFlattenWithReduce
} from "./flatten";
import measure from "./performance";
let iterPerf = false,
reducePerf = false,
mutableReducePerf = false,
shortPerf = false;
function render(iter, reduce, mutable, short) {
document.getElementById("app").innerHTML = `
<h1>Array flatten performance</h1>
<div>
${
iter
? `<div>
<h2>Iterative flatten</h2>
<div><b>${iter} ms</b></div>
</div>`
: ""
}
${
mutable
? `<div>
<h2>Reduce flatten (mutable)</h2>
<div><b>${mutable} ms</b></div>
</div>`
: ""
}
${
short
? `<div>
<h2>Short flatten</h2>
<div><b>${short} ms</b></div>
</div>`
: ""
}
${
reduce
? `<div>
<h2>Reduce flatten</h2>
<div><b>${reduce} ms</b></div>
</div>`
: ""
}
</div>
`;
}
measure(() => {
flattenIterative([
1,
[3, [2, 3, [4], 5], [[[4], [5, 6, [7], 8, 9, [[[[4, 3]]]]]]]]
]);
})
.then(ms => {
iterPerf = ms;
render(iterPerf, reducePerf, mutableReducePerf, shortPerf);
})
.then(() => {
measure(() => {
flattenWithReduce([
1,
[3, [2, 3, [4], 5], [[[4], [5, 6, [7], 8, 9, [[[[4, 3]]]]]]]]
]);
}).then(ms => {
reducePerf = ms;
render(iterPerf, reducePerf, mutableReducePerf, shortPerf);
measure(() => {
shortFlatten([
1,
[3, [2, 3, [4], 5], [[[4], [5, 6, [7], 8, 9, [[[[4, 3]]]]]]]]
]);
}).then(ms => {
shortPerf = ms;
render(iterPerf, reducePerf, mutableReducePerf, shortPerf);
measure(() => {
mutableFlattenWithReduce([
1,
[3, [2, 3, [4], 5], [[[4], [5, 6, [7], 8, 9, [[[[4, 3]]]]]]]]
]);
}).then(ms => {
mutableReducePerf = ms;
render(iterPerf, reducePerf, mutableReducePerf, shortPerf);
});
});
});
});
render();
export default function measure(func) {
return new Promise((resolve, reject) => {
try {
const start = performance.now();
for (let i = 0; i < 1000000; i++) {
func();
}
resolve(performance.now() - start);
} catch (e) {
reject(e);
}
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment