From 3d80e6505f7efa48b5afa7f1630f1712d0e2f621 Mon Sep 17 00:00:00 2001 From: Bryan Housel Date: Wed, 27 Mar 2019 16:18:41 -0400 Subject: [PATCH] Remove lodash chunk, groupBy (re: #6087) --- modules/actions/add_member.js | 8 ++-- modules/actions/join.js | 9 ++--- modules/actions/merge.js | 10 ++--- modules/actions/merge_polygon.js | 31 +++++++------- modules/behavior/copy.js | 13 +++--- modules/core/history.js | 6 +-- modules/operations/continue.js | 9 +++-- modules/renderer/features.js | 8 ++-- modules/services/osm.js | 17 ++++---- modules/svg/lines.js | 16 +++----- modules/ui/raw_membership_editor.js | 6 +-- modules/util/array.js | 45 +++++++++++++++++++++ modules/util/index.js | 3 ++ test/spec/util/array.js | 63 ++++++++++++++++++++++++----- test/spec/util/keybinding.js | 2 +- 15 files changed, 164 insertions(+), 82 deletions(-) diff --git a/modules/actions/add_member.js b/modules/actions/add_member.js index 941f795ea..fd414d0fb 100644 --- a/modules/actions/add_member.js +++ b/modules/actions/add_member.js @@ -1,7 +1,5 @@ -import _groupBy from 'lodash-es/groupBy'; - -import { utilObjectOmit } from '../util'; import { osmJoinWays, osmWay } from '../osm'; +import { utilArrayGroupBy, utilObjectOmit } from '../util'; export function actionAddMember(relationId, member, memberIndex, insertPair) { @@ -63,12 +61,12 @@ export function actionAddMember(relationId, member, memberIndex, insertPair) { graph = graph.replace(tempWay); var tempMember = { id: tempWay.id, type: 'way', role: member.role }; var tempRelation = relation.replaceMember({id: insertPair.originalID}, tempMember, true); - groups = _groupBy(tempRelation.members, function(m) { return m.type; }); + groups = utilArrayGroupBy(tempRelation.members, 'type'); groups.way = groups.way || []; } else { // Add the member anywhere, one time. Just push and let `osmJoinWays` decide where to put it. - groups = _groupBy(relation.members, function(m) { return m.type; }); + groups = utilArrayGroupBy(relation.members, 'type'); groups.way = groups.way || []; groups.way.push(member); } diff --git a/modules/actions/join.js b/modules/actions/join.js index 34fd6f918..8d004efbe 100644 --- a/modules/actions/join.js +++ b/modules/actions/join.js @@ -1,9 +1,7 @@ -import _groupBy from 'lodash-es/groupBy'; - import { actionDeleteWay } from './delete_way'; import { osmIsInterestingTag, osmJoinWays } from '../osm'; import { geoPathIntersections } from '../geo'; -import { utilArrayIntersection } from '../util'; +import { utilArrayGroupBy, utilArrayIntersection } from '../util'; // Join ways at the end node they share. @@ -18,8 +16,9 @@ export function actionJoin(ids) { function groupEntitiesByGeometry(graph) { var entities = ids.map(function(id) { return graph.entity(id); }); - return Object.assign({ line: [] }, - _groupBy(entities, function(entity) { return entity.geometry(graph); }) + return Object.assign( + { line: [] }, + utilArrayGroupBy(entities, function(entity) { return entity.geometry(graph); }) ); } diff --git a/modules/actions/merge.js b/modules/actions/merge.js index 2f1e2038c..bca5bbe49 100644 --- a/modules/actions/merge.js +++ b/modules/actions/merge.js @@ -1,14 +1,14 @@ -import _groupBy from 'lodash-es/groupBy'; - -import { utilArrayUniq } from '../util'; +import { utilArrayGroupBy, utilArrayUniq } from '../util'; export function actionMerge(ids) { function groupEntitiesByGeometry(graph) { var entities = ids.map(function(id) { return graph.entity(id); }); - return Object.assign({ point: [], area: [], line: [], relation: [] }, - _groupBy(entities, function(entity) { return entity.geometry(graph); })); + return Object.assign( + { point: [], area: [], line: [], relation: [] }, + utilArrayGroupBy(entities, function(entity) { return entity.geometry(graph); }) + ); } diff --git a/modules/actions/merge_polygon.js b/modules/actions/merge_polygon.js index 0b2bc23f6..5c0aee36f 100644 --- a/modules/actions/merge_polygon.js +++ b/modules/actions/merge_polygon.js @@ -1,27 +1,26 @@ -import _groupBy from 'lodash-es/groupBy'; - import { geoPolygonContainsPolygon } from '../geo'; import { osmJoinWays, osmRelation } from '../osm'; -import { utilObjectOmit } from '../util'; +import { utilArrayGroupBy, utilObjectOmit } from '../util'; export function actionMergePolygon(ids, newRelationId) { function groupEntities(graph) { var entities = ids.map(function (id) { return graph.entity(id); }); - return Object.assign({ - closedWay: [], - multipolygon: [], - other: [] - }, _groupBy(entities, function(entity) { - if (entity.type === 'way' && entity.isClosed()) { - return 'closedWay'; - } else if (entity.type === 'relation' && entity.isMultipolygon()) { - return 'multipolygon'; - } else { - return 'other'; - } - })); + var geometryGroups = utilArrayGroupBy(entities, function(entity) { + if (entity.type === 'way' && entity.isClosed()) { + return 'closedWay'; + } else if (entity.type === 'relation' && entity.isMultipolygon()) { + return 'multipolygon'; + } else { + return 'other'; + } + }); + + return Object.assign( + { closedWay: [], multipolygon: [], other: [] }, + geometryGroups + ); } diff --git a/modules/behavior/copy.js b/modules/behavior/copy.js index 48da18c10..c07dff505 100644 --- a/modules/behavior/copy.js +++ b/modules/behavior/copy.js @@ -1,16 +1,17 @@ -import _groupBy from 'lodash-es/groupBy'; -import _map from 'lodash-es/map'; - import { event as d3_event } from 'd3-selection'; + import { uiCmd } from '../ui'; +import { utilArrayGroupBy } from '../util'; export function behaviorCopy(context) { function groupEntities(ids, graph) { var entities = ids.map(function (id) { return graph.entity(id); }); - return Object.assign({relation: [], way: [], node: []}, - _groupBy(entities, function(entity) { return entity.type; })); + return Object.assign( + { relation: [], way: [], node: [] }, + utilArrayGroupBy(entities, 'type') + ); } @@ -21,7 +22,7 @@ export function behaviorCopy(context) { descendants = descendants || {}; if (entity.type === 'relation') { - children = _map(entity.members, 'id'); + children = entity.members.map(function(m) { return m.id; }); } else if (entity.type === 'way') { children = entity.nodes; } else { diff --git a/modules/core/history.js b/modules/core/history.js index ab941b1de..d6151b267 100644 --- a/modules/core/history.js +++ b/modules/core/history.js @@ -1,6 +1,5 @@ import _cloneDeep from 'lodash-es/cloneDeep'; import _cloneDeepWith from 'lodash-es/cloneDeepWith'; -import _groupBy from 'lodash-es/groupBy'; import _forEach from 'lodash-es/forEach'; import { dispatch as d3_dispatch } from 'd3-dispatch'; @@ -12,7 +11,8 @@ import { coreGraph } from './graph'; import { coreTree } from './tree'; import { osmEntity } from '../osm/entity'; import { uiLoading } from '../ui'; -import { utilArrayDifference, utilArrayUnion, utilObjectOmit, utilRebind, utilSessionMutex } from '../util'; +import { utilArrayDifference, utilArrayGroupBy, utilArrayUnion, + utilObjectOmit, utilRebind, utilSessionMutex } from '../util'; export function coreHistory(context) { @@ -517,7 +517,7 @@ export function coreHistory(context) { var childNodesLoaded = function(err, result) { if (!err) { - var visibleGroups = _groupBy(result.data, 'visible'); + var visibleGroups = utilArrayGroupBy(result.data, 'visible'); var visibles = visibleGroups.true || []; // alive nodes var invisibles = visibleGroups.false || []; // deleted nodes diff --git a/modules/operations/continue.js b/modules/operations/continue.js index 505aeade5..560003fcb 100644 --- a/modules/operations/continue.js +++ b/modules/operations/continue.js @@ -1,15 +1,16 @@ -import _groupBy from 'lodash-es/groupBy'; - import { t } from '../util/locale'; import { modeDrawLine } from '../modes'; import { behaviorOperation } from '../behavior'; +import { utilArrayGroupBy } from '../util'; export function operationContinue(selectedIDs, context) { var graph = context.graph(); var entities = selectedIDs.map(function(id) { return graph.entity(id); }); - var geometries = Object.assign({ line: [], vertex: [] }, - _groupBy(entities, function(entity) { return entity.geometry(graph); })); + var geometries = Object.assign( + { line: [], vertex: [] }, + utilArrayGroupBy(entities, function(entity) { return entity.geometry(graph); }) + ); var vertex = geometries.vertex[0]; diff --git a/modules/renderer/features.js b/modules/renderer/features.js index 4c5613b06..28b9a8cd3 100644 --- a/modules/renderer/features.js +++ b/modules/renderer/features.js @@ -1,10 +1,8 @@ -import _groupBy from 'lodash-es/groupBy'; - import { dispatch as d3_dispatch } from 'd3-dispatch'; import { osmEntity } from '../osm'; import { utilRebind } from '../util/rebind'; -import { utilArrayUnion, utilQsString, utilStringQs } from '../util'; +import { utilArrayGroupBy, utilArrayUnion, utilQsString, utilStringQs } from '../util'; export function rendererFeatures(context) { @@ -276,8 +274,8 @@ export function rendererFeatures(context) { features.gatherStats = function(d, resolver, dimensions) { var needsRedraw = false; - var type = _groupBy(d, function(ent) { return ent.type; }); - var entities = [].concat(type.relation || [], type.way || [], type.node || []); + var types = utilArrayGroupBy(d, 'type'); + var entities = [].concat(types.relation || [], types.way || [], types.node || []); var currHidden, geometry, matches, i, j; for (i = 0; i < _keys.length; i++) { diff --git a/modules/services/osm.js b/modules/services/osm.js index 1bcd0f082..c2b75090c 100644 --- a/modules/services/osm.js +++ b/modules/services/osm.js @@ -1,7 +1,5 @@ -import _chunk from 'lodash-es/chunk'; import _cloneDeep from 'lodash-es/cloneDeep'; import _forEach from 'lodash-es/forEach'; -import _groupBy from 'lodash-es/groupBy'; import _throttle from 'lodash-es/throttle'; import rbush from 'rbush'; @@ -13,7 +11,11 @@ import osmAuth from 'osm-auth'; import { JXON } from '../util/jxon'; import { geoExtent, geoVecAdd } from '../geo'; import { osmEntity, osmNode, osmNote, osmRelation, osmWay } from '../osm'; -import { utilArrayUniq, utilRebind, utilIdleWorker, utilTiler, utilQsString } from '../util'; + +import { + utilArrayChunk, utilArrayGroupBy, utilArrayUniq, utilRebind, + utilIdleWorker, utilTiler, utilQsString +} from '../util'; var tiler = utilTiler(); @@ -509,13 +511,14 @@ export default { // GET /api/0.6/[nodes|ways|relations]?#parameters loadMultiple: function(ids, callback) { var that = this; + var groups = utilArrayGroupBy(utilArrayUniq(ids), osmEntity.id.type); - _forEach(_groupBy(utilArrayUniq(ids), osmEntity.id.type), function(v, k) { + Object.keys(groups).forEach(function(k) { var type = k + 's'; // nodes, ways, relations - var osmIDs = v.map(function(id) { return osmEntity.id.toOSM(id); }); + var osmIDs = groups[k].map(function(id) { return osmEntity.id.toOSM(id); }); var options = { skipSeen: false }; - _chunk(osmIDs, 150).forEach(function(arr) { + utilArrayChunk(osmIDs, 150).forEach(function(arr) { that.loadFromAPI( '/api/0.6/' + type + '?' + type + '=' + arr.join(), function(err, entities) { @@ -620,7 +623,7 @@ export default { if (!this.authenticated()) return; // require auth } - _chunk(toLoad, 150).forEach(function(arr) { + utilArrayChunk(toLoad, 150).forEach(function(arr) { oauth.xhr( { method: 'GET', path: '/api/0.6/users?users=' + arr.join() }, wrapcb(this, done, _connectionID) diff --git a/modules/svg/lines.js b/modules/svg/lines.js index dffd8a353..77f28bdf3 100644 --- a/modules/svg/lines.js +++ b/modules/svg/lines.js @@ -1,19 +1,15 @@ -import _groupBy from 'lodash-es/groupBy'; import _flatten from 'lodash-es/flatten'; -import _forOwn from 'lodash-es/forOwn'; import _map from 'lodash-es/map'; import { range as d3_range } from 'd3-array'; import { - svgMarkerSegments, - svgPath, - svgRelationMemberTags, - svgSegmentWay, - svgTagClasses + svgMarkerSegments, svgPath, svgRelationMemberTags, + svgSegmentWay, svgTagClasses } from './index'; import { osmEntity, osmOldMultipolygonOuterMember } from '../osm'; +import { utilArrayGroupBy } from '../util'; import { utilDetect } from '../util/detect'; @@ -190,7 +186,6 @@ export function svgLines(projection, context) { var getPath = svgPath(projection, graph); var ways = []; - var pathdata = {}; var onewaydata = {}; var sideddata = {}; var oldMultiPolygonOuters = {}; @@ -207,9 +202,10 @@ export function svgLines(projection, context) { } ways = ways.filter(getPath); - pathdata = _groupBy(ways, function(way) { return way.layer(); }); + var pathdata = utilArrayGroupBy(ways, function(way) { return way.layer(); }); - _forOwn(pathdata, function(v, k) { + Object.keys(pathdata).forEach(function(k) { + var v = pathdata[k]; var onewayArr = v.filter(function(d) { return d.isOneWay(); }); var onewaySegments = svgMarkerSegments( projection, graph, 35, diff --git a/modules/ui/raw_membership_editor.js b/modules/ui/raw_membership_editor.js index 37320eb78..971337b26 100644 --- a/modules/ui/raw_membership_editor.js +++ b/modules/ui/raw_membership_editor.js @@ -1,5 +1,3 @@ -import _groupBy from 'lodash-es/groupBy'; - import { event as d3_event, select as d3_select @@ -19,7 +17,7 @@ import { osmEntity, osmRelation } from '../osm'; import { services } from '../services'; import { svgIcon } from '../svg'; import { uiCombobox, uiDisclosure } from './index'; -import { utilDisplayName, utilNoAuto, utilHighlightEntities } from '../util'; +import { utilArrayGroupBy, utilDisplayName, utilNoAuto, utilHighlightEntities } from '../util'; export function uiRawMembershipEditor(context) { @@ -119,7 +117,7 @@ export function uiRawMembershipEditor(context) { }); // Dedupe identical names by appending relation id - see #2891 - var dupeGroups = Object.values(_groupBy(result, 'value')) + var dupeGroups = Object.values(utilArrayGroupBy(result, 'value')) .filter(function(v) { return v.length > 1; }); dupeGroups.forEach(function(group) { diff --git a/modules/util/array.js b/modules/util/array.js index a3b6411ad..02d8dd827 100644 --- a/modules/util/array.js +++ b/modules/util/array.js @@ -44,3 +44,48 @@ export function utilArrayUnion(a, b) { export function utilArrayUniq(a) { return Array.from(new Set(a)); } + +// Splits array into chunks of given chunk size +// var a = [1,2,3,4,5,6,7]; +// utilArrayChunk(a, 3); +// [[1,2,3],[4,5,6],[7]]; +export function utilArrayChunk(a, chunkSize) { + if (!chunkSize || chunkSize < 0) return [a.slice()]; + + var result = new Array(Math.ceil(a.length / chunkSize)); + return Array.from(result, function(item, i) { + return a.slice(i * chunkSize, i * chunkSize + chunkSize); + }); +} + + +// Groups the items of the Array according to the given key +// `key` can be passed as a property or as a key function +// +// var pets = [ +// { type: 'Dog', name: 'Spot' }, +// { type: 'Cat', name: 'Tiger' }, +// { type: 'Dog', name: 'Rover' }, +// { type: 'Cat', name: 'Leo' } +// ]; +// +// utilArrayGroupBy(pets, 'type') +// { +// 'Dog': [{type: 'Dog', name: 'Spot'}, {type: 'Dog', name: 'Rover'}], +// 'Cat': [{type: 'Cat', name: 'Tiger'}, {type: 'Cat', name: 'Leo'}] +// } +// +// utilArrayGroupBy(pets, function(item) { return item.name.length; }) +// { +// 3: [{type: 'Cat', name: 'Leo'}], +// 4: [{type: 'Dog', name: 'Spot'}], +// 5: [{type: 'Cat', name: 'Tiger'}, {type: 'Dog', name: 'Rover'}] +// } +export function utilArrayGroupBy(a, key) { + return a.reduce(function(acc, item) { + var group = (typeof key === 'function') ? key(item) : item[key]; + (acc[group] = acc[group] || []).push(item); + return acc; + }, {}); +} + diff --git a/modules/util/index.js b/modules/util/index.js index bc6283c28..992360c9c 100644 --- a/modules/util/index.js +++ b/modules/util/index.js @@ -1,7 +1,10 @@ +export { utilArrayChunk } from './array'; export { utilArrayDifference } from './array'; +export { utilArrayGroupBy } from './array'; export { utilArrayIntersection } from './array'; export { utilArrayUnion } from './array'; export { utilArrayUniq } from './array'; + export { utilAsyncMap } from './util'; export { utilCallWhenIdle } from './call_when_idle'; export { utilCleanTags } from './clean_tags'; diff --git a/test/spec/util/array.js b/test/spec/util/array.js index 05c9bf7f1..d97062c54 100644 --- a/test/spec/util/array.js +++ b/test/spec/util/array.js @@ -1,5 +1,5 @@ -describe('iD.utilArrayDifference', function() { - it('returns set difference', function() { +describe('iD.utilArray', function() { + it('utilArrayDifference returns difference of two Arrays', function() { var a = [1, 2, 3]; var b = [4, 3, 2]; expect(iD.utilArrayDifference([], [])).to.eql([]); @@ -8,10 +8,8 @@ describe('iD.utilArrayDifference', function() { expect(iD.utilArrayDifference(a, b)).to.have.members([1]); expect(iD.utilArrayDifference(b, a)).to.have.members([4]); }); -}); -describe('iD.utilArrayIntersection', function() { - it('returns set intersection', function() { + it('utilArrayIntersection returns intersection of two Arrays', function() { var a = [1, 2, 3]; var b = [4, 3, 2]; expect(iD.utilArrayIntersection([], [])).to.eql([]); @@ -20,10 +18,8 @@ describe('iD.utilArrayIntersection', function() { expect(iD.utilArrayIntersection(a, b)).to.have.members([2, 3]); expect(iD.utilArrayIntersection(b, a)).to.have.members([2, 3]); }); -}); -describe('iD.utilArrayUnion', function() { - it('returns set union', function() { + it('utilArrayUnion returns union of two Arrays', function() { var a = [1, 2, 3]; var b = [4, 3, 2]; expect(iD.utilArrayUnion([], [])).to.eql([]); @@ -32,12 +28,57 @@ describe('iD.utilArrayUnion', function() { expect(iD.utilArrayUnion(a, b)).to.have.members([1, 2, 3, 4]); expect(iD.utilArrayUnion(b, a)).to.have.members([1, 2, 3, 4]); }); -}); -describe('iD.utilArrayUniq', function() { - it('returns unique values', function() { + it('utilArrayUniq returns unique values in an Array', function() { var a = [1, 1, 2, 3, 3]; expect(iD.utilArrayUniq([])).to.eql([]); expect(iD.utilArrayUniq(a)).to.have.members([1, 2, 3]); }); + + it('utilArrayChunk returns array split into given sized chunks', function() { + var a = [1, 2, 3, 4, 5, 6, 7]; + // bad chunkSizes, just copy whole array into a single chunk + expect(iD.utilArrayChunk(a)).to.eql([[1, 2, 3, 4, 5, 6, 7]]); + expect(iD.utilArrayChunk(a), -1).to.eql([[1, 2, 3, 4, 5, 6, 7]]); + expect(iD.utilArrayChunk(a), 0).to.eql([[1, 2, 3, 4, 5, 6, 7]]); + // good chunkSizes + expect(iD.utilArrayChunk(a, 2)).to.eql([[1, 2], [3, 4], [5, 6], [7]]); + expect(iD.utilArrayChunk(a, 3)).to.eql([[1, 2, 3], [4, 5, 6], [7]]); + expect(iD.utilArrayChunk(a, 4)).to.eql([[1, 2, 3, 4], [5, 6, 7]]); + }); + + describe('utilArrayGroupBy', function() { + var pets = [ + { type: 'Dog', name: 'Spot' }, + { type: 'Cat', name: 'Tiger' }, + { type: 'Dog', name: 'Rover' }, + { type: 'Cat', name: 'Leo' } + ]; + + it('groups by key property', function() { + var expected = { + 'Dog': [{type: 'Dog', name: 'Spot'}, {type: 'Dog', name: 'Rover'}], + 'Cat': [{type: 'Cat', name: 'Tiger'}, {type: 'Cat', name: 'Leo'}] + }; + expect(iD.utilArrayGroupBy(pets, 'type')).to.eql(expected); + }); + + it('groups by key function', function() { + var expected = { + 3: [{type: 'Cat', name: 'Leo'}], + 4: [{type: 'Dog', name: 'Spot'}], + 5: [{type: 'Cat', name: 'Tiger'}, {type: 'Dog', name: 'Rover'}] + }; + var keyFn = function(item) { return item.name.length; }; + expect(iD.utilArrayGroupBy(pets, keyFn)).to.eql(expected); + }); + + it('undefined key function', function() { + var expected = { + undefined: pets + }; + expect(iD.utilArrayGroupBy(pets)).to.eql(expected); + }); + }); + }); diff --git a/test/spec/util/keybinding.js b/test/spec/util/keybinding.js index 0b12c4040..dfa5c4942 100644 --- a/test/spec/util/keybinding.js +++ b/test/spec/util/keybinding.js @@ -1,4 +1,4 @@ -describe('utilKeybinding', function() { +describe('iD.utilKeybinding', function() { var keybinding, spy, input; beforeEach(function () {