I’ve written the following function:
const trends = hits.reduce((arr, curr, index, array) => { if (arr.includes(curr)) return arr if (curr + 1 === array[index + 1]) arr.push(curr, array[index + 1]); return arr; }, []);
The point is so that if an array contains a sequence of numbers which increase by 1 then this returns a new array with these values.
For instance: [1, 2, 3, 6, 10]
would return [1, 2, 3]
.
The problem is: if there’s more than one sequence, I’d like to have it in a separate array (or in an array of subarrays). At this point, the function does the following [1, 2, 3, 6, 7, 8]
. I also can’t predict how many trends there might be. How can I accomplish this?
Advertisement
Answer
A straightforward approach, based on two conditions whose precedence can not be changed/swapped, which actually also reads what it does …
function collectItemSequences(list, item, idx, arr) { if ((item - 1) === arr[idx - 1]) { // in case of a predecessor ... // ... push item into the most recent sequence list. list[list.length - 1].push(item); } else if ((item + 1) === arr[idx + 1]) { // else, in case of a successor ... // ... create a new sequence list with its 1st item. list.push([ item ]); } return list; } console.log( [2, 4, 6, 8, 10, 12, 14].reduce(collectItemSequences, []) ); console.log( [2, 4, 5, 6, 8, 10, 11, 12, 14].reduce(collectItemSequences, []) ); console.log( [1, 2, 4, 5, 6, 8, 10, 11, 12, 14, 15].reduce(collectItemSequences, []) );
.as-console-wrapper { min-height: 100%!important; top: 0; }
Based on the above approach one could implement a more generic one which allows the configuration of how to compute a current item’s sequence predecessor respectively sequence successor …
function collectItemSequencesByConditions(collector, item, idx, arr) { const { getPredecessor, getSuccessor, list } = collector; if (getPredecessor(item) === arr[idx - 1]) { // push item into the most recent sequence list. list[list.length - 1].push(item); } else if (getSuccessor(item) === arr[idx + 1]) { // create a new sequence list with its 1st item. list.push([ item ]); } return collector; } const conditions = { getPredecessor: currentItem => currentItem - 2, getSuccessor: currentItem => currentItem + 2, }; console.log( [2, 4, 6, 8, 10, 12, 14].reduce( collectItemSequencesByConditions, { ...conditions, list: [] }, ).list ); console.log( [2, 4, 5, 6, 8, 10, 11, 12, 14].reduce( collectItemSequencesByConditions, { ...conditions, list: [] }, ).list ); console.log( [1, 2, 4, 5, 6, 8, 10, 11, 12, 14, 15].reduce( collectItemSequencesByConditions, { ...conditions, list: [] }, ).list );
.as-console-wrapper { min-height: 100%!important; top: 0; }
Edit
The OP’s Q
I set up two pair of conditions, one for item
- 1
, item+ 1
, second pair respectively for- 10
,+ 10
. The hits array was[22, 31, 32, 33, 42, 52]
. I turned yourconsole.logs
intoconst variable = hits.reduce...
so on. Then I returned both variables. The results were[31, 32, 33]
and[42, 52]
. The expected outcome for second is of course[22, 33, 42, 52]
.
Firstly the OP most probably meant [22, 32, 42, 52]
.
Secondly …
Nope, math is reliable. And the algorithm can’t be tricked. The rules which are applied for valid predecessors/successors are merciless. Thus the “expected outcome for” [22, 31, 32, 33, 42, 52]
and +/- 10
of cause is [42, 52]
and not [22, 32, 42, 52]
.
Why?.. The second value of [22, 31, 32, 33, 42, 52]
is 31
which breaks any possible sequence (the OP expected 22
, 32
). Thus it is not a valid predecessor/successor sequence.
Here are some test cases …
console.log( "for [22, 31, 32, 33, 42, 52] and [-1 , +1]", "nexpect: '[[31,32,33]]' ?", JSON.stringify([22, 31, 32, 33, 42, 52].reduce( collectItemSequencesByConditions, { getPredecessor: currentItem => currentItem - 1, getSuccessor: currentItem => currentItem + 1, list: [], } ).list) === '[[31,32,33]]' ); console.log( [22, 31, 32, 33, 42, 52].reduce( collectItemSequencesByConditions, { getPredecessor: currentItem => currentItem - 1, getSuccessor: currentItem => currentItem + 1, list: [], } ).list ); console.log( "for [22, 31, 32, 33, 42, 52] and [-10 , +10]", "nexpect: '[[42,52]]' ?", JSON.stringify([22, 31, 32, 33, 42, 52].reduce( collectItemSequencesByConditions, { getPredecessor: currentItem => currentItem - 10, getSuccessor: currentItem => currentItem + 10, list: [], } ).list) === '[[42,52]]' ); console.log( [22, 31, 32, 33, 42, 52].reduce( collectItemSequencesByConditions, { getPredecessor: currentItem => currentItem - 10, getSuccessor: currentItem => currentItem + 10, list: [], } ).list ); console.log( "for [21, 22, 32, 33, 42, 52] and [-10 , +10]", "nexpect: '[[22,32],[42,52]]' ?", JSON.stringify([21, 22, 32, 33, 42, 52].reduce( collectItemSequencesByConditions, { getPredecessor: currentItem => currentItem - 10, getSuccessor: currentItem => currentItem + 10, list: [], } ).list) === '[[22,32],[42,52]]' ); console.log( [21, 22, 32, 33, 42, 52].reduce( collectItemSequencesByConditions, { getPredecessor: currentItem => currentItem - 10, getSuccessor: currentItem => currentItem + 10, list: [], } ).list );
.as-console-wrapper { min-height: 100%!important; top: 0; }
<script> function collectItemSequencesByConditions(collector, item, idx, arr) { const { getPredecessor, getSuccessor, list } = collector; if (getPredecessor(item) === arr[idx - 1]) { // push item into the most recent sequence list. list[list.length - 1].push(item); } else if (getSuccessor(item) === arr[idx + 1]) { // create a new sequence list with its 1st item. list.push([ item ]); } return collector; } </script>