Skip to content

Instantly share code, notes, and snippets.

@Xananax
Last active May 27, 2016 17:34
Show Gist options
  • Save Xananax/7e1ef76a015f2169a13287461a32c910 to your computer and use it in GitHub Desktop.
Save Xananax/7e1ef76a015f2169a13287461a32c910 to your computer and use it in GitHub Desktop.
Flatten Array
/************
* UTILITIES *
************/
function throwIfNotArray(arr){
if(isArray(arr)){return true;}
throw new Error(`\`${arr}\` is not an array`);
}
function isArray(arr){
return (arr && Object.prototype.toString.call(arr) === '[object Array]')
}
/*****************
* Let's have fun *
******************/
/**
* So, let's be honest.
* If this was real life, we'd be using a library
* So here's the pragmatic option. This is the `flatten`
* function out of underscore. I didn't do anything to it,
* beyond removing underscore dependencies and making it portable.
*/
export function pragmatic(arr){
var getLength = (obj) => obj && obj.length;
var isArguments = (obj) => obj && Object.prototype.hasOwnProperty.call(obj,'callee');
var flatten = function(input, shallow, strict, startIndex) {
var output = [], idx = 0;
for (var i = startIndex || 0, length = getLength(input); i < length; i++) {
var value = input[i];
if ((isArray(value) || isArguments(value))) {
if (!shallow) value = flatten(value, shallow, strict);
var j = 0, len = value.length;
output.length += len;
while (j < len) {
output[idx++] = value[j++];
}
} else if (!strict) {
output[idx++] = value;
}
}
return output;
};
const _ = {
flatten:function(array, shallow) {
return flatten(array, shallow, false);
}
}
return throwIfNotArray(arr) && _.flatten(arr);
}
/**
* Here's the `classical` version. This is the expected version,
* the one probably anyone expects to see as a flatten function.
* It's simple, it's readable.
*
*/
function _classical(arr,result){
let i = 0;
const {length} = arr;
while(i < length){
const val = arr[i++];
if(isArray(val)){
_classical(val,result)
}else{
result.push(val)
}
}
return result;
}
export function classical(arr){
return throwIfNotArray(arr) && _classical(arr,[]);
}
/**
* Short and sweet, at the expense of a bit of readability
* maybe.
* It's my personal favorite, because it's so simple it doesn't
* actually need to be a function, which means that in a lot of cases,
* I can implement it where I need it, without creating a new abstraction.
* I know in certain circles it can be considered malpractice to not abstract
* something that can be abstracted...but I have my reasons for not going DRY on occasions
*
*/
export function elegant(arr){
return throwIfNotArray(arr) &&
arr.reduce((previous,current)=>
previous.concat(isArray(current) && elegant(current) || current)
,[]);
}
/**
* Now the real fun begins. Here my goal is to make you shriek in horror,
* yet produce code that is technically correct and would pass the test.
*/
export function terriblyWrongButPassesTest(arr){
throwIfNotArray(arr)
let result = [];
arr.forEach(function(el,i){
if(el && el.length && typeof el[0]!=='undefined' && typeof el !== 'string'){
while(el.length){
const _el = el.shift();
if(_el && _el.length && typeof _el[0]!=='undefined' && typeof _el !== 'string'){
while(_el.length){
const __el = _el.shift();
result.push(__el);
}
}else{
result.push(_el);
}
}
}else{
result.push(el);
}
});
return result;
}
/**
* Yet another way to do things wrong, albeit
* you could invoke reasons for this one.
*/
export function extendingPrototypeOhNo(arr){
Array.prototype.flatten = function(){
let i = 0;
let {length} = this;
while(i < length){
const el = this[i];
if(isArray(el)){
this.splice(i,1,...el.flatten());
i+=el.length;
length+=el.length;
}else{
i++;
}
}
return this;
}
return arr.flatten();
}
/**
* This one is just functional "in style",
* while being obscure, and probably scoring low in perf tests.
* But it was fun to write.
*/
export function sortaFunctional(arr){
throwIfNotArray(arr);
const result = [];
(function flatten(arr){
if(!arr.length){return;}
const [head,...rest] = arr;
switch(isArray(head)){
case true: flatten(head);break;
case false: result.push(head);break;
default:break;
}
rest.length && flatten(rest);
})(arr);
return result;
}
/**
* Here's the last attempt at writing something terrible that passes tests.
* The funny thing is this would probably score quite ok in perf tests on arrays
* with a lot of nesting. If the arrays contain only strings, this would actually
* not be a bad choice, though purists would most likely facepalm.
*
*/
export function strangeButWhyNot(arr){
throwIfNotArray(arr);
return JSON.stringify(arr).replace(/\]+|\[+/g,'').split(',');
}
/***********
* TESTING! *
************/
const getInitial = () => [[1,2,[3]],[4,[5,6]],7];
const getFinal = () => [1,2,3,4,5,6,7];
const compare = (arr,compare) => arr.every((el,i)=>el==compare[i]);
const test = (fn) => {
const initial = getInitial();
const result = fn(initial);
const expected = getFinal();
if(!compare(result,expected)){
throw new Error(`${fn.name} is an invalid function, got \`[${result}]\`, expected \`[${expected}]\``);
}
console.log(`${fn.name} passed!`);
}
const tests = [
pragmatic
, classical
, elegant
, terriblyWrongButPassesTest
, extendingPrototypeOhNo
, sortaFunctional
, strangeButWhyNot
].forEach(test);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment