My input is an array like so:
[7, 7, 7, 7, 4, 4, 5, 5, 5, 1, 9, 2, 7, 7]
I want to group together the numbers and add them, but by neighbors, not by total in the array. So the output would be:
['7:4', '4:2', '5:3', 1, 9, 2, '7:2']
I've attempted a few different methods using reduce
, and gotten close but using the built-in Javascript methods I end up counting ALL in the array, not by neighbors.
const firstArray = [7, 7, 7, 7, 4, 4, 5, 5, 5, 1, 9, 2, 7, 7];
const masterArray = [];
const unique = new Set (numberArray); // Set {7, 4, 5, 1, 9, 2, 7}
unique.forEach(u => {
masterArray.push(numberArray.filter(e => e === u));
});
console.log(masterArray);
Set is obviously wrong to use here because that gets the unique values and counts them, but I want to do it by neighbor only. So then I think I should be using a reduce
but I run into the same problem.
My input is an array like so:
[7, 7, 7, 7, 4, 4, 5, 5, 5, 1, 9, 2, 7, 7]
I want to group together the numbers and add them, but by neighbors, not by total in the array. So the output would be:
['7:4', '4:2', '5:3', 1, 9, 2, '7:2']
I've attempted a few different methods using reduce
, and gotten close but using the built-in Javascript methods I end up counting ALL in the array, not by neighbors.
const firstArray = [7, 7, 7, 7, 4, 4, 5, 5, 5, 1, 9, 2, 7, 7];
const masterArray = [];
const unique = new Set (numberArray); // Set {7, 4, 5, 1, 9, 2, 7}
unique.forEach(u => {
masterArray.push(numberArray.filter(e => e === u));
});
console.log(masterArray);
Set is obviously wrong to use here because that gets the unique values and counts them, but I want to do it by neighbor only. So then I think I should be using a reduce
but I run into the same problem.
8 Answers
Reset to default 3
var test = [7, 7, 7, 7, 4, 4, 5, 5, 5, 1, 9, 2, 7, 7];
console.log(
test.reduce((acc, element) => {
if (acc.length && acc[acc.length - 1].key == element) {
acc[acc.length - 1].count++;
} else {
acc.push({ key: element, count: 1 });
}
return acc;
}, []).map(element => `${element.key}:${element.count}`)
);
So the logic first reduces the number to an array, tracking the last key and count. So long as the key is the same, the count is incremented. Once the key changes, a new object is pushed to start the next run. After the reduce is done, a map is performed to convert the key and counts into the desired strings.
You'll need to keep track of the last number iterated over in a persistent variable, as well as the number of occurrences of the last number which gets incremented:
const arr = [7, 7, 7, 7, 4, 4, 5, 5, 5, 1, 9, 2, 7, 7];
let lastNum = arr[0];
let count = 0;
const results = [];
const doPush = () => {
results.push(
count === 1
? lastNum
: `${lastNum}:${count}`
);
};
for (const num of arr) {
if (num !== lastNum) {
doPush();
lastNum = num;
count = 1;
} else count++;
}
doPush();
console.log(results);
You could reduce the array by having a look to the last value a the index before the actual value.
const
array = [7, 7, 7, 7, 4, 4, 5, 5, 5, 1, 9, 2, 7, 7],
result = array.reduce((r, value, i, { [i - 1]: last }) => {
let count = 0;
if (last === value) count = (+r.pop().toString().split(':')[1] || 0) + 1;
return [...r, count ? `${value}:${count}`: value];
}, []);
console.log(result);
Here is a solution that uses a recursive function to group the neighbors, and then Array.prototype.map()
to format with a colon:
const arr = [7, 7, 7, 7, 4, 4, 5, 5, 5, 1, 9, 2, 7, 7],
output = (function groupNeighbors([first, ...rest], output = [], last) {
last === first ? output[output.length - 1].push(first) : output.push([first]);
return rest.length ? groupNeighbors(rest, output, first) : output;
})(arr).map(({ [0]: num, length }) => length > 1 ? [num, length].join(':') : num);
console.log(output);
As it uses recursion, it is limited in terms of input array length, you can find the stack size limits per browser here: Browser Javascript Stack size limit
Variant with Array.prototype.reduce()
(slightly shortest, no recursion, unlimited input length):
const arr = [7, 7, 7, 7, 4, 4, 5, 5, 5, 1, 9, 2, 7, 7],
output = arr
.reduce(
(acc, cur, i, { [i - 1]: last }) =>
(cur === last ? acc[acc.length - 1].push(cur) : acc.push([cur])) && acc,
[]
)
.map(({ [0]: num, length }) => length > 1 ? [num, length].join(':') : num);
console.log(output);
Yes, the proper choice here is reduce
:
const countDuplicatesQuantity = (arr) => {
const duplicatesObj = arr.reduce((duplicates, item) => {
duplicates[item] = duplicates[item] ? duplicates[item] + 1 : 1;
return duplicates;
}, {});
return Object.keys(duplicatesObj).map(
(key) => `${key}:${duplicatesObj[key]}`,
);
};
You wanted reduce, how about twice? :) (I don't know if I've done something stupid here.)
First reduce finds where the values change in the array, second uses that to build new array.
const firstArray = [7, 7, 7, 7, 4, 4, 5, 5, 5, 1, 9, 2, 7, 7];
const masterArray = firstArray
.reduce((acc, v, i, all) => all[i + 1] !== v ? acc.concat(i) : acc, [])
.reduce(
(acc, v, i, all) => {
const range = v - (i === 0 ? -1 : all[i - 1]);
return acc.concat(range > 1 ? firstArray[v] + ':' + range : firstArray[v]);
}, []
);
console.log(masterArray);
Using array.reduce:
const firstArray = [7, 7, 7, 7, 4, 4, 5, 5, 5, 1, 9, 2, 7, 7];
var newArray = [];
var count = 0;
firstArray.reduce((acc, cur, index) => {
if (acc == cur) {
count++;
}
if (acc != cur || index+1 == firstArray.length) {
newArray.push(acc + ":" + count);
count=1
}
return cur;
})
console.log(newArray);
my way ... I felt that there could be "simpler"
const firstArray = [7, 7, 7, 7, 4, 4, 5, 5, 5, 1, 9, 2, 7, 7];
const result = firstArray.reduceRight((r,v)=>
{
let [n,c] = !r.length ? [-v,0] : isNaN(r[0]) ? r[0].split(':') : [r[0],1]
if (n==v) r[0] = `${n}:${++c}`
else r.unshift(v)
return r
},[])
console.log( JSON.stringify(result) )
// [ "7:4", "4:2", "5:3", 1, 9, 2, "7:2" ]
.as-console-wrapper { max-height: 100% !important; top: 0; }