1
\$\begingroup\$

(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?');
\$\endgroup\$
  • \$\begingroup\$ Do you mean Array.prototype.group instead of Array.group? \$\endgroup\$ – tsh Oct 13 '18 at 16:19
  • \$\begingroup\$ 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) \$\endgroup\$ – tsh Oct 13 '18 at 16:21
  • \$\begingroup\$ 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. \$\endgroup\$ – ninjagecko Oct 13 '18 at 16:24
  • \$\begingroup\$ You had mentioned that the submission cannot pollute the globalThis (window in browser / global in node). Is pollute this valid through? \$\endgroup\$ – tsh Oct 13 '18 at 16:28
  • \$\begingroup\$ 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. \$\endgroup\$ – ninjagecko Oct 13 '18 at 16:31
2
\$\begingroup\$

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

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

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

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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.