Reputation: 965
Working with an object array like:
const data = [
{count: 400, value: "Car Wash Drops"},
{count: 48, value: "Personal/Seeding"},
{count: 48, value: "Personal/Seeding"},
];
I am wanting to map
to an array with an additional identifier for duplicate values:
const expected = [
["Car Wash Drops", 400],
["Personal/Seeding (1)", 48],
["Personal/Seeding (2)", 48],
];
So far, I have a map function to map the values accordingly, but am unsure of how to proceed with appending the identifier only for duplicates.
data.map(d => [`${d.value}`, d.count]);
results in:
[
["Car Wash Drops", 400],
["Personal/Seeding", 48],
["Personal/Seeding", 48],
]
I have utilized the index as well with, but it adds the index on every value:
data.map((d, i) => [`${d.value} ${i}`, d.count]);
results in:
[
["Car Wash Drops (0)", 400],
["Personal/Seeding (1)", 48],
["Personal/Seeding (2)", 48],
]
Upvotes: 4
Views: 1674
Reputation: 3081
The answer was already given, with all due respect to the author of the answer, I argue there might some improvements can be made on the answer, both syntactically and performance wise:
reduce
should be avoided if the source is very big, though I always prefer reduce
if the source is relatively small (data.length<1000). Try to use POF (plain old for :) ) as it is the fastest option.
The ES6 Map is a nice helper when we deal with key-value pairs, but I prefer POO (plain old object :) ) if it is possible and it will make our code more aesthetic.
I will provide my solution to the problem (it will run O(n) and used extra O(n) space for key-value pairs in the worst case, it does two-passes on the source so, the second pass is needed to put 1 where we missed when we first meet it on the first pass):
let data = [
{count: 400, value: "Car Wash Drops"},
{count: 48, value: "Personal/Seeding"},
{count: 48, value: "Personal/Seeding"},
{count: 300, value: "Operators/Management"},
{count: 48, value: "Personal/Seeding"}
];
const map = {};
for (let i=0;i<data.length;i+=1) {
map[ data[i].value ] = map[ data[i].value ]+1 || 0;
data[i].value = map[data[i].value]?data[i].value+` (${map[data[i].value]+1})`:data[i].value;
}
for (let i=0;i<data.length;i+=1) {
data[i].value = map[data[i].value]?data[i].value+` (1)`:data[i].value;
}
console.log(data.map(o=>[o.value,o.count]));
Or more concise version using ES6 of
operator [link to of]:
let data = [
{count: 400, value: "Car Wash Drops"},
{count: 48, value: "Personal/Seeding"},
{count: 48, value: "Personal/Seeding"},
{count: 300, value: "Operators/Management"},
{count: 48, value: "Personal/Seeding"}
];
const map = {};
for (let d of data) {
map[d.value] = map[d.value]+1 || 0;
d.value = map[d.value]?d.value+` (${map[d.value]+1})`:d.value;
}
for (let d of data) {
d.value = map[d.value]?d.value+` (1)`:d.value;
}
console.log(data.map(o=>[o.value,o.count]));
Upvotes: 1
Reputation: 17190
Using your approach, you can use filter()
inside the map to check how much elements on the original array have the same value of the current analyzed one, using this condition you can choice what to return as the new value:
const data = [
{count: 400, value: "Car Wash Drops"},
{count: 48, value: "Personal/Seeding"},
{count: 48, value: "Personal/Seeding"},
];
let res = data.map((x, idx) =>
{
if (data.filter(y => y.value === x.value).length > 1)
return [`${x.value} (${idx})`, x.count];
else
return [`${x.value}`, x.count];
});
console.log(res);
The performance of the previous approach could be improved if we use some()
instead of filter()
, like this:
const data = [
{count: 400, value: "Car Wash Drops"},
{count: 48, value: "Personal/Seeding"},
{count: 48, value: "Personal/Seeding"},
{count: 300, value: "Operators/Management"},
{count: 48, value: "Personal/Seeding"}
];
let res = data.map((x, idx) =>
{
if (data.some((y, j) => y.value === x.value && idx !== j))
return [`${x.value} (${idx})`, x.count];
else
return [`${x.value}`, x.count];
});
console.log(res);
And could be improved even more if we previously create a Map with the counter of times an element appears in the original array. Like this:
const data = [
{count: 400, value: "Car Wash Drops"},
{count: 48, value: "Personal/Seeding"},
{count: 48, value: "Personal/Seeding"},
{count: 300, value: "Operators/Management"},
{count: 48, value: "Personal/Seeding"}
];
let counters = data.reduce((res, {value}) =>
{
res.set(value, res.has(value) ? res.get(value) + 1 : 1);
return res;
}, new Map());
let res = data.map((x, idx) =>
{
return [
`${x.value}` + (counters.get(x.value) > 1 ? `(${idx})` : ""),
x.count
];
});
console.log(res);
Upvotes: 3
Reputation: 12990
You can maintain a mapping from values you've seen to how many of them you've seen. Then it will be easier to get the right number based on whether there's a duplicate or not:
const data = [
{count: 400, value: "Car Wash Drops"},
{count: 48, value: "Personal/Seeding"},
{count: 48, value: "Personal/Seeding"},
];
let valueCounts = data.reduce((a, c) => {
a[c.value] = a[c.value] || {current: 1, total: 0};
a[c.value].total += 1;
return a;
}, {});
const expected = data.map(({count, value}) => {
if (valueCounts[value].total === 1) return [value, count];
return [`${value} (${valueCounts[value].current++})`, count];
});
console.log(expected);
Upvotes: 0
Reputation: 413737
You can pass a second argument to .map()
after the callback. That value will be used as the value of this
when the callback is invoked. Thus you can pass through an object that you can use to accumulate a count of repeats:
data.map(function(d, i) {
if (!(d.value in this))
this[d.value] = 0;
else
this[d.value] += 1;
return [
d.value + (this[d.value] ? " (" + this[d.value] + ")" : ""),
d.count
];
}, {});
By starting with an empty object, the callback can keep track of how many times it's seen each d.value
string. When it sees a repeat, it can add the qualifier to the string.
Now, this doesn't do exactly what you asked, because it only looks at one value at a time. Thus the first time it sees "Personal/Seeding"
it does not know it's a duplicate, so it's not modified with the qualifier. The second time through it is of course.
Doing what you asked, if this isn't good enough, would require making a complete pass through the array first to get a final count of duplicates for each string.
Upvotes: -1