# (Javascript) itertools.groupBy, e.g. [0,0,1,1,0] -> [[0,0],[1,1],[0]]

(language-specific challenge)

## Specification:

Language: ECMAScript, any version[1]

Challenge: Somewhat like python's itertools.groupBy: Your function Array.group(f) should take as input an "equivalence function" f whose output defines our notion of an "equivalence key" for array elements (like a sort key). The return value of .group(f) should be an array of arrays in the same order, split whenever the value of the keyfunc parameter changes (in the === sense).

Object.defineProperties(Array.prototype, {
group: {
value: function(f) {/*
CODE GOES HERE
*/}
}
});


## Examples / test cases:

> ['a','c','E','G','n'].group(x => x==x.toUpperCase())
[['a','c'], ['E','G'], ['n']]

> [1,1,'1'].group(x=>x)
[[1,1],['1']]

> [3,1,'1',10,5,5].group(x=>x+2)
[[3], [1], ['1'], [10], [5,5]]

> [{a:1,b:2,c:3}, {b:2,a:1,c:0}, {c:5}].group(x => JSON.stringify([x.a, x.b]))
[[{a:1,b:2,c:3},{b:2,a:1,c:0}], [{c:5}]]

> [].group(x=>x)
[]


## Scoring criteria:

score = # of characters that replace comment above (lower is better), with easements that:

1. whitespace and statement-separating semicolons don't count towards total[2]
2. variable names and keywords each count as exactly 1 characters towards total per use[3]; this includes builtins, so Array.map.apply would be 5 'characters' (however, builtins count their full length as tiebreakers)
3. -5 bonus points if f being undefined has same result as the identity function x=>x
4. you may modify the construct function(f) {...} for recursion/combinator purposes while keeping it as a function respecting rule #5, but must subtract from your score the extra characters incurred (i.e. imagine the scoring began earlier)

## Disallowances:

1. no side-effects which affect anything other than your code (such as modifying this, window, the prototype, etc.); as a corollary, all definitions must stay local to the .group function (e.g. x=5 would be disallowed, but var x=5 is fine)

minor notes:

[1] draft specs fine if in any major browser, proprietary extensions fine if in 2+ major browsers;
[2] as long as you don't try to compile a few megabytes-long whitespace ;-)
[3] as long as reflection is not used to abuse code-in-variable-names ;-)

## Errata:

Answer may do anything reasonable with regards to sparse arrays. e.g. arr=[1,1]; arr[4]=1; arr -> [1, 1, empty × 2, 1]. Possibilities include ignoring them (like .map and friends do), or treating them as undefined (like an incremental for-loop would), or anything reasonable. For example arr.group(x=>x) might return [[1,1],[undefined,undefined],[1]], or [[1,1,1]], or even more dubious things like [[1,1],null,[1]] if it makes sense. You may also assume sparse arrays do not exist.

edit: #2 now applies to keywords.

You may find the following snippet useful to estimate score (the rules supercede this snippet though):

var code = ......;
console.log('score estimate:', code.replace(/\w+/g,'X').replace(/[\s;]/g,'').replace(/X(?=X)/g,'X ').length, '-5?');

• Do you mean Array.prototype.group instead of Array.group? – tsh Oct 13 '18 at 16:19
• Should one just submit the function body or a function (which should assigned to Array#group later) or the full shim including assignment? What if one want change the signature of the function? (e.g, function name, more (optional) parameters, default value for f) – tsh Oct 13 '18 at 16:21
• Yes, I was writing it as shorthand for Array.prototype.group. You can change the signature of the function as long as it still conforms to the test cases; for example, adding the parameter group(f,state) { will incur a mere -2 penalty to score (since it is 2 'characters' longer, the comma and variable name). Submit whatever seems cleanest. – ninjagecko Oct 13 '18 at 16:24
• You had mentioned that the submission cannot pollute the globalThis (window in browser / global in node). Is pollute this valid through? – tsh Oct 13 '18 at 16:28
• No, it is invalid to pollute this. Thanks for checking. I should clarify; I will edit the wording of rule #5 since no one has answered this yet. – ninjagecko Oct 13 '18 at 16:31

# JavaScript, 47 points (53 tiebreaker)

Array.prototype.group = function(f){
const r = []
let k = r
for(const i of this)
k !== (k = f ? f(i) : i) ? r.push(a = [i]) : a.push(i)
return r
}

• Body: +52
• Identity comparison: -5
• Full-length built-ins: +6

### Try it:

Array.prototype.group = function(f){
const r = []
const k = r
for(const i of this)
k!==(k=f?f(i):i)?r.push(a=[i]):a.push(i)
return r
}

console.log(
['a','c','E','G','n'].group(x => x==x.toUpperCase()),
[1,1,'1'].group(x=>x),
[3,1,'1',10,5,5].group(x=>x+2),
[{a:1,b:2,c:3}, {b:2,a:1,c:0}, {c:5}].group(x => JSON.stringify([x.a, x.b])),
[].group(x=>x)
)

# JavaScript, 48 points (56 tiebreaker)

More traditional code golf-y solution.

Array.prototype.group = function(f,r=[],k=r){
this.map(i=>k!==(k=f?f(i):i)?r.push(a=[i]):a.push(i))
return r
}

• Signature: +9
• Body: +44
• Identity comparison: -5
• Full-length built-ins: +8

### Try it:

Array.prototype.group = function(f,r=[],k=r){
this.map(i=>k!==(k=f?f(i):i)?r.push(a=[i]):a.push(i))
return r
}

console.log(
['a','c','E','G','n'].group(x => x==x.toUpperCase()),
[1,1,'1'].group(x=>x),
[3,1,'1',10,5,5].group(x=>x+2),
[{a:1,b:2,c:3}, {b:2,a:1,c:0}, {c:5}].group(x => JSON.stringify([x.a, x.b])),
[].group(x=>x)
)

• Hopefully I got the scoring right. :P – darrylyeo Oct 13 '18 at 19:11
• A const variable declaration const k = r cannot be changed k=f, the value of a variable declared using let can be changed – guest271314 Oct 13 '18 at 21:45
• full-length built-ins? – ninjagecko Oct 14 '18 at 17:46
• @guest271314 Fixed, thank you! – darrylyeo Oct 15 '18 at 18:32
• @ninjagecko Per your scoring criteria #2: "builtins count their full length as tiebreakers." – darrylyeo Oct 15 '18 at 18:32

# JavaScript (Node.js), 90 bytes, 82 points

return((o,p,O)=>this.map(v=>(O=o,(o=f?f(v):v)===O?p.push(v):p=[v])))(0/0).filter(x=>x.pop)


Try it online!

90 - 3 (this counts as 1 bytes) - 5 (default f as x=>x) = 82 points

• I love the use of NaN!==NaN – ninjagecko Oct 13 '18 at 17:41
• Your latest edit seems to have broken the test cases. – ninjagecko Oct 13 '18 at 18:36
• Using NaN rather than 0/0 to have lower score? – l4m2 Oct 15 '18 at 18:40