From 714a5ab8b107d0bc43d19de79cd498c58db325b2 Mon Sep 17 00:00:00 2001 From: Beaudan Date: Fri, 11 Jan 2019 13:48:58 +1100 Subject: [PATCH] Update consolidateLists function to take a selector function and updated tests --- libloki/service_nodes.js | 20 ++++++++++++------- libloki/test/service_nodes_test.js | 32 ++++++++++++++++++++++++++++-- 2 files changed, 43 insertions(+), 9 deletions(-) diff --git a/libloki/service_nodes.js b/libloki/service_nodes.js index 58698bd7e..1c0ff8aaf 100644 --- a/libloki/service_nodes.js +++ b/libloki/service_nodes.js @@ -4,30 +4,36 @@ (function () { window.libloki = window.libloki || {}; - function consolidateLists(lists, threshold = 1){ + function consolidateLists(lists, threshold, selector = (x) => x){ if (typeof threshold !== 'number') { throw Error('Provided threshold is not a number'); } + if (typeof selector !== 'function') { + throw Error('Provided selector is not a function'); + } // calculate list size manually since `Set` // does not have a `length` attribute let numLists = 0; const occurences = {}; + const values = {}; lists.forEach(list => { numLists += 1; list.forEach(item => { - if (!(item in occurences)) { - occurences[item] = 1; + const key = selector(item); + if (!(key in occurences)) { + occurences[key] = 1; + values[key] = item; } else { - occurences[item] += 1; + occurences[key] += 1; } }); }); const scaledThreshold = numLists * threshold; - return Object.entries(occurences) - .filter(keyValue => keyValue[1] >= scaledThreshold) - .map(keyValue => keyValue[0]); + return Object.keys(occurences) + .filter(key => occurences[key] >= scaledThreshold) + .map(key => values[key]); } window.libloki.serviceNodes = { diff --git a/libloki/test/service_nodes_test.js b/libloki/test/service_nodes_test.js index 1d72d0b92..59636743a 100644 --- a/libloki/test/service_nodes_test.js +++ b/libloki/test/service_nodes_test.js @@ -17,13 +17,22 @@ describe('ServiceNodes', () => { ); }); + it('should throw when provided a non-function selector', () => { + [1, 'a', 0xffffffff, { really: 'not a function' }].forEach(x => { + assert.throws(() => + libloki.serviceNodes.consolidateLists([], 1, x), + 'Provided selector is not a function' + ) + }); + }); + it('should return an empty array when the input is an empty array', () => { - const result = libloki.serviceNodes.consolidateLists([]); + const result = libloki.serviceNodes.consolidateLists([], 1); assert.deepEqual(result, []); }); it('should return the input when only 1 list is provided', () => { - const result = libloki.serviceNodes.consolidateLists([['a', 'b', 'c']]); + const result = libloki.serviceNodes.consolidateLists([['a', 'b', 'c']], 1); assert.deepEqual(result, ['a', 'b', 'c']); }); @@ -36,6 +45,25 @@ describe('ServiceNodes', () => { assert.deepEqual(result.sort(), ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']); }); + it('should use the selector to identify the elements', () => { + const result = libloki.serviceNodes.consolidateLists([ + [{ id: 1, val: 'a'}, { id: 2, val: 'b'}, { id: 3, val: 'c'}, { id: 8, val: 'h'}], + [{ id: 4, val: 'd'}, { id: 5, val: 'e'}, { id: 6, val: 'f'}, { id: 7, val: 'g'}], + [{ id: 7, val: 'g'}, { id: 8, val: 'h'}], + ], 0, x => x.id); + const expected = [ + { id: 1, val: 'a'}, + { id: 2, val: 'b'}, + { id: 3, val: 'c'}, + { id: 4, val: 'd'}, + { id: 5, val: 'e'}, + { id: 6, val: 'f'}, + { id: 7, val: 'g'}, + { id: 8, val: 'h'}, + ]; + assert.deepEqual(result.sort((a, b) => a.val > b.val), expected); + }); + it('should return the intersection of all lists when threshold is 1', () => { const result = libloki.serviceNodes.consolidateLists([ ['a', 'b', 'c', 'd'],