Last active
May 27, 2016 17:34
-
-
Save Xananax/7e1ef76a015f2169a13287461a32c910 to your computer and use it in GitHub Desktop.
Flatten Array
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/************ | |
* 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