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

1

1

(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?');

ninjagecko

Posted 2018-10-13T16:16:48.097

Reputation: 181

Do you mean Array.prototype.group instead of Array.group? – tsh – 2018-10-13T16:19:33.807

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 – 2018-10-13T16:21:45.550

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 – 2018-10-13T16:24:59.960

You had mentioned that the submission cannot pollute the globalThis (window in browser / global in node). Is pollute this valid through? – tsh – 2018-10-13T16:28:28.757

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 – 2018-10-13T16:31:54.227

Is group() operate in-place and return this, or return an new array without modifing this? – tsh – 2018-10-13T16:31:57.070

@tsh: returns a new array – ninjagecko – 2018-10-13T16:34:30.137

Let us continue this discussion in chat.

– tsh – 2018-10-13T16:36:00.213

So as to possibly avoid the need for such clarifications, I'm suggesting using the sandbox next time! :-)

– Erik the Outgolfer – 2018-10-13T19:43:13.927

Why does x => x==x.toUpperCase() result in [['a','c']..] though x=>x+2 results in [[3], [1]..]? – guest271314 – 2018-10-13T21:19:25.543

Answers

2

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)
)

darrylyeo

Posted 2018-10-13T16:16:48.097

Reputation: 6 214

Hopefully I got the scoring right. :P – darrylyeo – 2018-10-13T19:11:28.850

A const variable declaration const k = r cannot be changed k=f, the value of a variable declared using let can be changed – guest271314 – 2018-10-13T21:45:04.757

full-length built-ins? – ninjagecko – 2018-10-14T17:46:59.013

@guest271314 Fixed, thank you! – darrylyeo – 2018-10-15T18:32:00.583

@ninjagecko Per your scoring criteria #2: "builtins count their full length as tiebreakers." – darrylyeo – 2018-10-15T18:32:46.680

1

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

tsh

Posted 2018-10-13T16:16:48.097

Reputation: 13 072

I love the use of NaN!==NaN – ninjagecko – 2018-10-13T17:41:01.457

Your latest edit seems to have broken the test cases. – ninjagecko – 2018-10-13T18:36:41.790

Using NaN rather than 0/0 to have lower score? – l4m2 – 2018-10-15T18:40:35.687