├── .gitattributes ├── .github └── workflows │ └── build.yml ├── .gitignore ├── .npmrc ├── LICENSE ├── README.md ├── index.html ├── package.json └── spec.emu /.gitattributes: -------------------------------------------------------------------------------- 1 | index.html -diff merge=ours 2 | spec.js -diff merge=ours 3 | spec.css -diff merge=ours 4 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Deploy spec 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v2 11 | - uses: actions/setup-node@v1 12 | with: 13 | node-version: '12.x' 14 | - run: npm install 15 | - run: npm run build 16 | - name: commit changes 17 | uses: elstudio/actions-js-build/commit@v3 18 | with: 19 | commitMessage: "fixup: [spec] `npm run build`" 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | # Only apps should have lockfiles 40 | yarn.lock 41 | package-lock.json 42 | npm-shrinkwrap.json 43 | pnpm-lock.yaml 44 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 ECMA TC39 and contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Proposal-array-grouping. 2 | 3 | 4 | 5 | A proposal to make grouping of items in an array easier. 6 | 7 | ```js 8 | const array = [1, 2, 3, 4, 5]; 9 | 10 | // groupBy groups items by arbitrary key. 11 | // In this case, we're grouping by even/odd keys 12 | array.groupBy(i => { 13 | return i % 2 === 0 ? 'even': 'odd'; 14 | }); 15 | 16 | // => { odd: [1, 3, 5], even: [2, 4] } 17 | ``` 18 | 19 | ## Champions 20 | 21 | - Justin Ridgewell ([@jridgewell](https://github.com/jridgewell/)) 22 | 23 | ## Status 24 | 25 | Current [Stage](https://tc39.es/process-document/): 2 26 | 27 | ## Motivation 28 | 29 | TODO 30 | 31 | ## Polyfill 32 | 33 | - A polyfill is available in the [core-js](https://github.com/zloirock/core-js) library. You can find it in the [ECMAScript proposals section](https://github.com/zloirock/core-js#array-grouping). 34 | 35 | ## Related 36 | 37 | - Lodash 38 | - [`_.groupBy`](https://lodash.com/docs/4.17.15#groupBy) ([850k downloads/week](https://www.npmjs.com/package/lodash.groupby)) 39 | 40 | [![Typing SVG](https://readme-typing-svg.herokuapp.com?color=%2318f9ee&size=22&lines=Thanks+for+visiting)](https://git.io/typing-svg) 41 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Array Grouping

Stage 2 Draft / October 30, 2021

Array Grouping

2256 | 2257 | 2258 |

1 Scope

2259 |

2260 | This is the spec text of the Array Grouping proposal in ECMAScript. 2261 |

2262 |
2263 | 2264 | 2265 |

2 Properties of the Array Prototype Object (22.1.3)

2266 | 2267 | 2268 | 2269 |

2.1 Array.prototype.groupBy ( callbackfn [ , thisArg ] )

2270 | Note 1
2271 |

callbackfn should be a function that accepts three arguments and returns a value that is coercible to a property. groupBy calls callbackfn once for each element in the array, in ascending order, and constructs a new object of arrays, where the property key is the return value and array is filled with the items that return that property key.

2272 |

If a thisArg parameter is provided, it will be used as the this value for each invocation of callbackfn. If it is not provided, undefined is used instead.

2273 |

callbackfn is called with three arguments: the value of the element, the index of the element, and the object being traversed.

2274 |

groupBy does not directly mutate the object on which it is called but the object may be mutated by the calls to callbackfn.

2275 |

The range of elements processed by groupBy is set before the first call to callbackfn. Elements which are appended to the array after the call to groupBy begins will not be visited by callbackfn. If existing elements of the array are changed their value as passed to callbackfn will be the value at the time groupBy visits them; elements that are deleted after the call to groupBy begins and before being visited are still visited and are either looked up from the prototype or are undefined.

2276 |

The return value of groupBy is an object that does not inherit from Object.prototype.

2277 |
2278 |

When the groupBy method is called with one or two arguments, the following steps are taken:

2279 |
  1. Let O be ? ToObject(this value).
  2. Let len be ? LengthOfArrayLike(O).
  3. If IsCallable(callbackfn) is false, throw a TypeError exception.
  4. Let k be 0.
  5. Let groups be a new empty List.
  6. Repeat, while k < len
    1. Let Pk be ! ToString(𝔽(k)).
    2. Let kValue be ? Get(O, Pk).
    3. Let propertyKey be ? ToPropertyKey(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)).
    4. Perform ! AddValueToKeyedGroup(groups, propertyKey, kValue).
    5. Set k to k + 1.
  7. Let obj be ! OrdinaryObjectCreate(null).
  8. For each Record { [[Key]], [[Elements]] } g of groups, do
    1. Let elements be ! CreateArrayFromList(g.[[Elements]]).
    2. Perform ! CreateDataPropertyOrThrow(obj, g.[[Key]], elements).
  9. Return obj.
2280 | Note 2
2281 |

The groupBy function is intentionally generic; it does not require that its this value be an Array object. Therefore it can be transferred to other kinds of objects for use as a method.

2282 |
2283 |
2284 |
2285 | 2286 | 2287 | 2288 |

2.2 Array.prototype.groupByMap ( callbackfn [ , thisArg ] )

2289 | Note 1
2290 |

callbackfn should be a function that accepts three arguments and returns any value. groupByMap calls callbackfn once for each element in the array, in ascending order, and constructs a new Map of arrays, where the key is the return value and array is filled with the items that return that key.

2291 |

If a thisArg parameter is provided, it will be used as the this value for each invocation of callbackfn. If it is not provided, undefined is used instead.

2292 |

callbackfn is called with three arguments: the value of the element, the index of the element, and the object being traversed.

2293 |

groupByMap does not directly mutate the object on which it is called but the object may be mutated by the calls to callbackfn.

2294 |

The range of elements processed by groupByMap is set before the first call to callbackfn. Elements which are appended to the array after the call to groupByMap begins will not be visited by callbackfn. If existing elements of the array are changed their value as passed to callbackfn will be the value at the time groupByMap visits them; elements that are deleted after the call to groupByMap begins and before being visited are still visited and are either looked up from the prototype or are undefined.

2295 |

The return value of groupByMap is a Map.

2296 |
2297 |

When the groupByMap method is called with one or two arguments, the following steps are taken:

2298 |
  1. Let O be ? ToObject(this value).
  2. Let len be ? LengthOfArrayLike(O).
  3. If IsCallable(callbackfn) is false, throw a TypeError exception.
  4. Let k be 0.
  5. Let groups be a new empty List.
  6. Repeat, while k < len
    1. Let Pk be ! ToString(𝔽(k)).
    2. Let kValue be ? Get(O, Pk).
    3. Let key be ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »).
    4. If key is -0𝔽, set key to +0𝔽.
    5. Perform ! AddValueToKeyedGroup(groups, key, kValue).
    6. Set k to k + 1.
  7. Let map be ! Construct(%Map%).
  8. For each Record { [[Key]], [[Elements]] } g of groups, do
    1. Let elements be ! CreateArrayFromList(g.[[Elements]]).
    2. Let entry be the Record { [[Key]]: g.[[Key]], [[Value]]: elements }.
    3. Append entry as the last element of map.[[MapData]].
  9. Return map.
2299 | Note 2
2300 |

The groupBy function is intentionally generic; it does not require that its this value be an Array object. Therefore it can be transferred to other kinds of objects for use as a method.

2301 |
2302 |
2303 | 2304 | 2305 |

2.3 2306 | AddValueToKeyedGroup ( 2307 | groups: a List of Records that have [[Key]] and [[Elements]] fields, 2308 | key: an ECMAScript language value, 2309 | value: an ECMAScript language value, 2310 | ) 2311 |

2312 |
  1. If groups contains a Record g such that ! SameValueZero(g.[[Key]], key) is true, then
    1. Assert: exactly one element of groups meets this criteria.
    2. Append value as the last element of g.[[Elements]].
  2. Else,
    1. Let group be the Record { [[Key]]: key, [[Elements]]: « value » }.
    2. Append group as the last element of groups.
2313 |
2314 |
2315 |
2316 |

A Copyright & Software License

2317 | 2318 |

Copyright Notice

2319 |

© 2021 Justin Ridgewell

2320 | 2321 |

Software License

2322 |

All Software contained in this document ("Software") is protected by copyright and is being made available under the "BSD License", included below. This Software may be subject to third party rights (rights from parties other than Ecma International), including patent rights, and no licenses under such third party rights are granted under this license even if the third party concerned is a member of Ecma International. SEE THE ECMA CODE OF CONDUCT IN PATENT MATTERS AVAILABLE AT https://ecma-international.org/memento/codeofconduct.htm FOR INFORMATION REGARDING THE LICENSING OF PATENT CLAIMS THAT ARE REQUIRED TO IMPLEMENT ECMA INTERNATIONAL STANDARDS.

2323 | 2324 |

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

2325 | 2326 |
    2327 |
  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
  2. 2328 |
  3. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
  4. 2329 |
  5. Neither the name of the authors nor Ecma International may be used to endorse or promote products derived from this software without specific prior written permission.
  6. 2330 |
2331 | 2332 |

THIS SOFTWARE IS PROVIDED BY THE ECMA INTERNATIONAL "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ECMA INTERNATIONAL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

2333 | 2334 |
2335 |
-------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "proposal-array-grouping", 4 | "description": "A proposal to make grouping of array items easier", 5 | "scripts": { 6 | "start": "npm run build-loose -- --watch", 7 | "build": "npm run build-loose -- --strict", 8 | "build-loose": "ecmarkup --verbose spec.emu index.html" 9 | }, 10 | "homepage": "https://github.com/tc39/proposal-array-grouping#readme", 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/tc39/proposal-array-grouping.git" 14 | }, 15 | "license": "MIT", 16 | "devDependencies": { 17 | "ecmarkup": "^7.0.3" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /spec.emu: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
  7 | title: Array Grouping
  8 | status: proposal
  9 | stage: 2
 10 | contributors: Justin Ridgewell
 11 | location: https://tc39.es/proposal-array-grouping/
 12 | 
13 | 14 | 15 |

Scope

16 |

17 | This is the spec text of the Array Grouping proposal in ECMAScript. 18 |

19 |
20 | 21 | 22 |

Properties of the Array Prototype Object (22.1.3)

23 | 24 | 25 | 26 |

Array.prototype.groupBy ( _callbackfn_ [ , _thisArg_ ] )

27 | 28 |

_callbackfn_ should be a function that accepts three arguments and returns a value that is coercible to a property. `groupBy` calls _callbackfn_ once for each element in the array, in ascending order, and constructs a new object of arrays, where the property key is the return value and array is filled with the items that return that property key.

29 |

If a _thisArg_ parameter is provided, it will be used as the *this* value for each invocation of _callbackfn_. If it is not provided, *undefined* is used instead.

30 |

_callbackfn_ is called with three arguments: the value of the element, the index of the element, and the object being traversed.

31 |

`groupBy` does not directly mutate the object on which it is called but the object may be mutated by the calls to _callbackfn_.

32 |

The range of elements processed by `groupBy` is set before the first call to _callbackfn_. Elements which are appended to the array after the call to `groupBy` begins will not be visited by _callbackfn_. If existing elements of the array are changed their value as passed to _callbackfn_ will be the value at the time `groupBy` visits them; elements that are deleted after the call to `groupBy` begins and before being visited are still visited and are either looked up from the prototype or are *undefined*.

33 |

The return value of `groupBy` is an object that does not inherit from _Object.prototype_.

34 |
35 |

When the `groupBy` method is called with one or two arguments, the following steps are taken:

36 | 37 | 1. Let _O_ be ? ToObject(*this* value). 38 | 1. Let _len_ be ? LengthOfArrayLike(_O_). 39 | 1. If IsCallable(_callbackfn_) is *false*, throw a *TypeError* exception. 40 | 1. Let _k_ be 0. 41 | 1. Let _groups_ be a new empty List. 42 | 1. Repeat, while _k_ < _len_ 43 | 1. Let _Pk_ be ! ToString(𝔽(_k_)). 44 | 1. Let _kValue_ be ? Get(_O_, _Pk_). 45 | 1. Let _propertyKey_ be ? ToPropertyKey(? Call(_callbackfn_, _thisArg_, « _kValue_, 𝔽(_k_), _O_ »)). 46 | 1. Perform ! AddValueToKeyedGroup(_groups_, _propertyKey_, _kValue_). 47 | 1. Set _k_ to _k_ + 1. 48 | 1. Let _obj_ be ! OrdinaryObjectCreate(*null*). 49 | 1. For each Record { [[Key]], [[Elements]] } _g_ of _groups_, do 50 | 1. Let _elements_ be ! CreateArrayFromList(_g_.[[Elements]]). 51 | 1. Perform ! CreateDataPropertyOrThrow(_obj_, _g_.[[Key]], _elements_). 52 | 1. Return _obj_. 53 | 54 | 55 |

The `groupBy` function is intentionally generic; it does not require that its *this* value be an Array object. Therefore it can be transferred to other kinds of objects for use as a method.

56 |
57 |
58 |
59 | 60 | 61 | 62 |

Array.prototype.groupByMap ( _callbackfn_ [ , _thisArg_ ] )

63 | 64 |

_callbackfn_ should be a function that accepts three arguments and returns any value. `groupByMap` calls _callbackfn_ once for each element in the array, in ascending order, and constructs a new Map of arrays, where the key is the return value and array is filled with the items that return that key.

65 |

If a _thisArg_ parameter is provided, it will be used as the *this* value for each invocation of _callbackfn_. If it is not provided, *undefined* is used instead.

66 |

_callbackfn_ is called with three arguments: the value of the element, the index of the element, and the object being traversed.

67 |

`groupByMap` does not directly mutate the object on which it is called but the object may be mutated by the calls to _callbackfn_.

68 |

The range of elements processed by `groupByMap` is set before the first call to _callbackfn_. Elements which are appended to the array after the call to `groupByMap` begins will not be visited by _callbackfn_. If existing elements of the array are changed their value as passed to _callbackfn_ will be the value at the time `groupByMap` visits them; elements that are deleted after the call to `groupByMap` begins and before being visited are still visited and are either looked up from the prototype or are *undefined*.

69 |

The return value of `groupByMap` is a Map.

70 |
71 |

When the `groupByMap` method is called with one or two arguments, the following steps are taken:

72 | 73 | 1. Let _O_ be ? ToObject(*this* value). 74 | 1. Let _len_ be ? LengthOfArrayLike(_O_). 75 | 1. If IsCallable(_callbackfn_) is *false*, throw a *TypeError* exception. 76 | 1. Let _k_ be 0. 77 | 1. Let _groups_ be a new empty List. 78 | 1. Repeat, while _k_ < _len_ 79 | 1. Let _Pk_ be ! ToString(𝔽(_k_)). 80 | 1. Let _kValue_ be ? Get(_O_, _Pk_). 81 | 1. Let _key_ be ? Call(_callbackfn_, _thisArg_, « _kValue_, 𝔽(_k_), _O_ »). 82 | 1. If _key_ is *-0*𝔽, set _key_ to *+0*𝔽. 83 | 1. Perform ! AddValueToKeyedGroup(_groups_, _key_, _kValue_). 84 | 1. Set _k_ to _k_ + 1. 85 | 1. Let _map_ be ! Construct(%Map%). 86 | 1. For each Record { [[Key]], [[Elements]] } _g_ of _groups_, do 87 | 1. Let _elements_ be ! CreateArrayFromList(_g_.[[Elements]]). 88 | 1. Let _entry_ be the Record { [[Key]]: _g_.[[Key]], [[Value]]: _elements_ }. 89 | 1. Append _entry_ as the last element of _map_.[[MapData]]. 90 | 1. Return _map_. 91 | 92 | 93 |

The `groupBy` function is intentionally generic; it does not require that its *this* value be an Array object. Therefore it can be transferred to other kinds of objects for use as a method.

94 |
95 |
96 | 97 | 98 |

99 | AddValueToKeyedGroup ( 100 | _groups_: a List of Records that have [[Key]] and [[Elements]] fields, 101 | _key_: an ECMAScript language value, 102 | _value_: an ECMAScript language value, 103 | ) 104 |

105 | 106 | 1. If _groups_ contains a Record _g_ such that ! SameValueZero(_g_.[[Key]], _key_) is *true*, then 107 | 1. Assert: exactly one element of _groups_ meets this criteria. 108 | 1. Append _value_ as the last element of _g_.[[Elements]]. 109 | 1. Else, 110 | 1. Let _group_ be the Record { [[Key]]: _key_, [[Elements]]: « _value_ » }. 111 | 1. Append _group_ as the last element of _groups_. 112 | 113 |
114 |
115 |
116 | --------------------------------------------------------------------------------