Javascript - Get unique and sorted array - javascript

I have an array with duplicate items. I want to filter that array to return only unique items, but that items have to be sorted based on how many times they were in initial array.
const initialArr = [
{
id: 1
},
{
id: 1
},
{
id: 2
},
{
id: 1
},
{
id: 3
},
{
id: 3
},
];
const expectedSortedResult = [
{
id: 1
},
{
id: 3
},
{
id: 2
}
]

Try to always post your attempt, no matter how far away from the solution it is.
You should research the following (and I solved it with these too):
Reduce (create object, groupBy and create __count property): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce
Convert this back to an array with Object.values(), Followed by
Sort (sort by __count): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
Then you will need to delete that count property if you don't want it in your output, you can do this with Map: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
const initialArr = [
{id: 1},
{id: 1},
{id: 2},
{id: 1},
{id: 3},
{id: 3},
];
const output = Object.values(initialArr.reduce((aggObj, item) => {
if (aggObj[item.id]){
aggObj[item.id].__count += 1
}
else{
aggObj[item.id] = item;
aggObj[item.id].__count = 1
}
return aggObj;
}, {}))
.sort((a,b) => b.__count - a.__count)
.map(a => {delete a.__count; return a});
console.log(output);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Related

Comparing array elements against the rest of the array

The question might be a bit vague, but I'll explain the result I'm expecting to get with an example.
Say I have the following array made out of objects with the following shape:
[
{
id: 1,
value: 10
},
{
id: 2,
value: 100
},
{
id: 3,
value: 10
},
{
id: 4,
value: 10
},
{
id: 5,
value: 1000
},
]
This array might contain hundrends, maybe thousands of entries, but for simplicity, I'll keep it small.
What I'm trying to achieve is compare the value property of every object with the other value properties and assign a new property duplicate with a boolean value to that specific object.
Given the example above, I would expect to receive an array with the following members:
[
{
id: 1,
value: 10,
duplicate: true
},
{
id: 2,
value: 100
},
{
id: 3,
value: 10,
duplicate: true
},
{
id: 4,
value: 10,
duplicate: true
},
{
id: 5,
value: 1000
},
]
Whats the most optimal way I could implement this behavior ?
Thank you.
I'd do a single pass through the array remembering the first seen entry with a given value in a Map, marking that first entry (and any others) as duplicates if it's present, like this:
const map = new Map();
for (const entry of array) {
const previous = map.get(entry.value);
if (previous) {
previous.duplicate = entry.duplicate = true;
} else {
map.set(entry.value, entry);
}
}
Live Example:
const array = [
{
id: 1,
value: 10
},
{
id: 2,
value: 100
},
{
id: 3,
value: 10
},
{
id: 4,
value: 10
},
{
id: 5,
value: 1000
},
];
const map = new Map();
for (const entry of array) {
const previous = map.get(entry.value);
if (previous) {
previous.duplicate = entry.duplicate = true;
} else {
map.set(entry.value, entry);
}
}
console.log(array);
You can do this by first determining which are the duplicates, and then setting the 'duplicate' attribute.
counts = items.reduce((counter, item) => {
if (counter[item.value] != null) {
counter[item.value] += 1;
} else {
counter[item.value] = 1;
}
return counter;
}, {});
After this, you can go over your items, and if the count is >=2, set the 'duplicate' attribute.
items.forEach((item) => {
if (counter[item.value] > 1) {
item['duplicate'] = true;
}
});
You can use Array.map and Array.filter for that.
const input = [
{ id: 1, value: 10 },
{ id: 2, value: 100 },
{ id: 3, value: 10 },
{ id: 4, value: 10 },
{ id: 5, value: 1000 }
]
const output = input.map(entry => {
if (input.filter(x => x.value === entry.value).length > 1) {
return {
duplicate: true,
...entry
}
}
return entry
})
console.log(output)
I would create a map with value as the key, and a list of ids as the values, than after iterating over the whole map and creating the new mapping, unpack it back tothe desired form, and add duplicated for keys with more than one value.
I think this will help you. arr is your array.
arr.forEach(e=> {
const dublicatedDataLenth = arr.filter(a => a.value == e.value).length;
if(dublicatedDataLenth > 1){
e.dublicate = true;
}
})
It should be what you are looking for.
A copy from myself with a single loop and an object for storing seen values.
This approach returns a new array and does not mutate the given data.
var data = [{ id: 1, value: 10 }, { id: 2, value: 100 }, { id: 3, value: 10 }, { id: 4, value: 10 }, { id: 5, value: 1000 }],
result = data.map((seen => ({ ...o }) => {
if (o.value in seen) {
o.duplicate = true;
if (seen[o.value]) {
seen[o.value].duplicate = true;
seen[o.value] = false;
}
} else seen[o.value] = o;
return o;
})({}));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Filter an array of objects by a N-length map of props with 3 states for each prop

I'm doing a complex filter, for which I have an initial list of objects with unique ids.
And a map with N properties with a list of corresponding object copies for each prop; and three states for each prop: idle: 0, show: 1, hide: 2.
For now I managed to do this with lodash's differenceBy and intersectionBy. My filter function takes in an array of objects and mutates the array, by checking and filtering the array with every map prop.
But concerning efficiency and growing number of complexity, should this kind of problem be solved differently?
For example:
If this filter is applied to a big array of hex colors (length 100, 1000 or more)
colors [1, 2, 3, ...1000]
And prop map has a growing number of props, like tags, by which a user can mark colors and show/hide them on filter. Or at some point new prop states will be added.
prop1 0, 1, 2, ...10
prop2 0, 1, 2, ...10
prop3 0, 1, 2, ...10
...
prop100 0, 1, 2, ...10
Should this kind of problem be solved via graph or matrix algorithms or some other method respectively? And, if yes, to what I should look into?
My code for optimisation and efficiency concerns:
const propMap = [
{ name: 'prop1', value: 0, items: [] },
{ name: 'prop2', value: 1, items: [ { id: 1}, { id: 2} ] },
{ name: 'propN', value: 2, items: [ { id: 2} ] },
];
const someArr = [
{ id: 1}, { id: 2}, { id: 3}, { id: 4},{ id: 5},
]
function filterByPropMap (arr) {
// Filter hidden from array
propMap.forEach(prop => {
if (prop.value === 2) {
arr = _.differenceBy(arr, prop.items, 'id');
}
});
// Filter intersecting objects to show
propMap.forEach(prop => {
if (prop.value === 1) {
arr = _.intersectionBy(arr, prop.items, 'id');
}
});
return [...arr];
}
console.log(filterByPropMap(someArr));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js"></script>
In general, you very often don't need Lodash. Consider the following, which uses only vanilla ES6.
It returns { id: 1 } twice, because it looks like the deduplication is an unintended side effect of your code. At least you never do so explicitly.
const propMap = [
{ name: 'prop1', value: 0, items: [] },
{ name: 'prop2', value: 1, items: [ { id: 1}, { id: 2} ] },
{ name: 'propN', value: 2, items: [ { id: 2} ] },
];
const someArr = [
{ id: 1}, { id: 2}, { id: 3}, { id: 4},{ id: 1},
];
function filterByPropMap(arr) {
const hiddenItems = propMap
.filter(p => p.value === 2)
.map(p => p.items)
.flat();
const intersectingItems = propMap
.filter(p => p.value === 1)
.map(p => p.items)
.flat();
const isEqual = (a, b) => a.id === b.id;
return arr
.filter(v => !hiddenItems.some(h => isEqual(h, v)) &&
intersectingItems.some(i => isEqual(i, v)));
}
console.log(filterByPropMap(someArr));

Compare two javascript array of objects and merge data

I'm having a bad time comparing two array of objects on a key.
I would like to compare, substract value when the key matches and display negative value when not in my target array. Finally, I want to have all target objects (if key didn't match) inside my final array.
An exemple would save 1000 words :
const initial = [{id: 1, value: 47}, {id: 2, value: 20}, {id: 7, value: 13}];
const target = [{id: 1, value: 150}, {id: 3, value: 70}, {id: 40, value: 477}];
//Desired output
// [{id: 1, value: 103}, {id: 2, value: -20}, {id: 7, value: -13}, {id: 3, value: 70}, {id: 40, value: 477}];
let comparator = [];
initial.map(initia => {
let hasSame = target.find(targ => {
return initia.id === targ.id
});
if(hasSame){
initia.value -= hasSame.value
} else{
initia.value = -initia.value
}
});
console.log(initial);
I'm getting almost the result I want except that I don't know how to merge target values properly. Is it possible to merge this values without looping over target array once more? Or could I do that inside the find ?
I want to get advice to do this as clean as possible
Thanks you!
You could use a Map and collect same id with a wanted factor for summing.
As result take key/value as new properties.
var add = (map, factor) => ({ id, value }) => map.set(id, map.has(id)
? map.get(id) - value * factor
: value * factor
),
initial = [ {id: 1, value: 47 }, { id: 2, value: 20 }, { id: 7, value: 13 }],
target = [{ id: 1, value: 150 }, { id: 3, value: 70 }, { id: 40, value: 477 }],
map = new Map,
result;
initial.forEach(add(map, -1));
target.forEach(add(map, 1));
result = Array.from(map, ([id, value]) => ({ id, value }));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
If the intent is to avoid a nested find inside of the loop, you could reduce the two arrays in to a Set. They don't allow duplicate keys, so you would ensure a single value for each ID provided.
const valueSet = [...initial, ...target].reduce((total, obj) => {
total[obj.id] = !total[obj.id]
? -obj.value
: total[obj.id] -= obj.value
return total;
}, {});
const result = Object.keys(valueSet).map(key => ({ id: key, value: valueSet[key]}))
console.log(result);
Then you'd map back over the result to build out the intended array.

filter array of object using an array [duplicate]

This question already has an answer here:
Filter array of object from another array
(1 answer)
Closed 3 years ago.
I want to filter an array of objects using an array but I want the results on the basis of array index and the result should be repeated when the array index value is repeated.
const data = [{
id='1',
name:'x'
},
{
id='4',
name:'a'
},
{
id='2',
name:'y'
},
{
id='3',
name:'z'
}
]
cons idArray = [1,4,3,2,4,3,2]
I have tried following code and get the result only once
const filteredData = data.filter(arrayofObj => idArray.includes(arrayofObj.id))
console.log(filteredData)
expected output is
expected output is =
[{id = '1,name:'x'},{id='4',name:'a'},{
id='3',
name:'z'
},
{
id='2',
name:'y'
},{
id='4',
name:'a'
},
{
id='3',
name:'z'
},{
id='2',
name:'y'
}]
First convert data array into Object with id's as keys.
Second, use map method over idArray and gather objects from above object.
const data = [
{
id: "1",
name: "x"
},
{
id: "4",
name: "a"
},
{
id: "2",
name: "y"
},
{
id: "3",
name: "z"
}
];
const dataObj = data.reduce((acc, curr) => {
acc[curr.id] = { ...curr };
return acc;
}, {});
const idArray = [1, 4, 3, 2, 4, 3, 2];
const results = idArray.map(id => ({ ...dataObj[id] }));
console.log(results);
You could map with a Map.
const
data = [{ id: '1', name: 'x' }, { id: '4', name: 'a' }, { id: '2', name: 'y' }, { id: '3', name: 'z' }],
idArray = [1, 4, 3, 2, 4, 3, 2],
result = idArray.map(Map.prototype.get, new Map(data.map(o => [+o.id, o])));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

how to calculate a sum of an array in an object

I had a variable like that
const data = {
code: 1,
items: [
{ nickname: 1, name: [
{id : "A"},
{id : "B"}
]
},
{
nickname: 2, name: [
{id: "A"},
{id: "C"}
]
}
]
}
after that, I want to show how many characters: A:2, B:1, C:1
You can do that is following steps:
Use flatMap() on the array data.items
Inside flatMap() use map() to convert all the object to their id and return it from flatMap(). This way you will array ["A","B","A","C"]
Then use reduce() and get an object with count of all the letters.
const data = { code: 1, items: [ { nickname: 1, name: [ {id : "A"}, {id : "B"} ] }, { nickname: 2, name: [ {id: "A"}, {id: "C"} ] } ] }
const res = data.items.flatMap(x =>
x.name.map(a => a.id)
).reduce((ac,a) => (ac[a] = ac[a] + 1 || 1,ac),{});
console.log(res)
const data = {
code: 1,
items: [
{
nickname: 1,
name: [
{ id: "A" },
{ id: "B" }
]
},
{
nickname: 2,
name: [
{ id: "A" },
{ id: "C" }
]
}
]
};
const res = data.items.reduce((acc, next) => {
next.name.forEach(({ id }) => {
acc[id] = acc[id] + 1 || 1;
});
return acc;
}, {});
console.log(res);
You can do that in a single shot using reduce.
Reducing data.items will allow you to add to the accumulator (initially an empty object), the value of the currently looped name property item.
The result will be an object owning all the occurences of each encountered letter in the name property of each array.
Relevant lines explained:
data.items.reduce((acc, next) will call the reduce method on data.items. acc is the reduce accumulator (initially an empty object), next is the currently looped item of data.items.
next.name.forEach(({id}) in this line, we loop the name property of the currently looped item (data.items[n]). ({id}) is a short syntax to acquire the id property of the looped item in the foreach. It's equivalent to (item => item.id).
acc[id] = acc[id] + 1 || 1; tries to increase the property [id] of the accumulator (example: "A" of {}) by 1. If it does not exist, it sets the value to 1.
return acc; returns the accumulator.
You could iterate name and take id in a loop for assigning the count.
const
data = { code: 1, items: [{ nickname: 1, name: [{ id : "A" }, { id : "B" }] }, { nickname: 2, name: [{ id: "A" }, { id: "C" }] }] },
result = data.items.reduce(
(r, { name }) => (name.forEach(({ id }) => r[id] = (r[id] || 0 ) + 1), r),
{}
);
console.log(result);

Categories