Count array of strings with reduce - javascript

I am trying to count how many identical values there are in an array of strings using reduce but this.
const arrayOfStrings = ["63955a83c1a9a4d5365b686c", "63955e3e478e25a859a6e7bb", "63955e3e478e25a859a6e7bb"]
const result = arrayOfStrings.reduce((prev, curr) => {
prev[curr] = prev[curr] || [{
id: curr,
amount: 0
}];
prev[curr][curr.amount] += 1
return prev;
}, [])
I'm currently getting a strange array, but it's not even close to what I want, I'm expecting something like this:
[
{
id: "63955a83c1a9a4d5365b686c",
amount: 1
},
{
id: "63955e3e478e25a859a6e7bb",
amount: 2
}
]

You need to initialise your reduce with an object rather than an array.
Update the amount by assigning the value to acc[c].amount.
Extract the values (the nested objects) with Object.values.
const arr = ["63955a83c1a9a4d5365b686c", "63955e3e478e25a859a6e7bb", "63955e3e478e25a859a6e7bb"];
const out = arr.reduce((prev, curr) => {
prev[curr] ??= { id: curr, amount: 0 };
prev[curr].amount += 1;
return prev;
}, {});
console.log(Object.values(out));

You can use find inside reduce to search whether the value is already present in the array and if yes, then update the amount value, otherwise initialize a new object and push it in the array.
const arrayOfStrings = ["63955a83c1a9a4d5365b686c", "63955e3e478e25a859a6e7bb", "63955e3e478e25a859a6e7bb"]
const result = arrayOfStrings.reduce((prev, curr) => {
let o = prev.find((obj) => obj.id === curr)
o ? o.amount++ : prev.push({ id: curr, amount: 1 })
return prev;
}, [])
console.log(result)

Related

Create an array of object properties for each item that has the same key

I'm merging two objects together to create a filter object. However I want to group the merged objects property values where the keys are the same.
So...
[{category: 'furniture'}, {category: 'mirrors'}, {availability: 'in_stock'}]
becomes
[{category: ['furniture', 'mirrors']}, {availability: 'in_stock'}]
any ideas?
With lodash you merge the entire array to a new object by spreading into _.mergeWith(). The customizer should use empty arrays as default values for the current values, and concat the values. Use _.map() to convert back to an array.
const data = [{category: 'furniture'}, {category: 'mirrors'}, {availability: 'in_stock'}];
const result = _.map(
_.mergeWith({}, ...data, (a = [], b = [], key) => a.concat(b)),
(val, key) => ({ [key]: val })
)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js" integrity="sha512-WFN04846sdKMIP5LKNphMaWzU7YpMyCU245etK3g/2ARYbPK9Ub18eG+ljU96qKRCWh+quCY7yefSmlkQw1ANQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
Using vanilla JS, reduce the array to a Map using the objects' keys as the keys of the Map, with an empty array as the value, and push the objects' values into the arrays. Use Array.from() to convert the Map to an array.
const data = [{category: 'furniture'}, {category: 'mirrors'}, {availability: 'in_stock'}];
const result = Array.from(
data.reduce((acc, obj) => {
Object.entries(obj)
.forEach(([key, val]) => {
if(!acc.has(key)) acc.set(key, [])
acc.get(key).push(val)
})
return acc
}, new Map()),
([key, val]) => ({ [key]: val })
)
console.log(result)
You can use reduce like this:
const data = [
{ category: 'furniture' },
{ category: 'mirrors' },
{ availability: 'in_stock' }
];
const result = data.reduce(
(a, x) => {
const key = Object.keys(x)[0]; // find the key of the current object
if (!a.tmp[key]) { // if the current key doesn't exist in the lookup object (tmp) yet ...
a.tmp[key] = []; // create an empty array in the lookup object for the current key
a.result.push({ [key]: a.tmp[key] }); // push the current object to the result
}
a.tmp[key].push(x[key]); // push the current value to the array
return a;
},
{ result: [], tmp: {} },
).result;
console.log(result);
I'm sure there are easier ways to achieve this, but that's the best I can come up with right now.
we can also achieve this by using forEach loop :
const input = [{category: 'furniture'}, {category: 'mirrors'}, {availability: 'in_stock'}];
const resultObj = {};
const resultArr = [];
input.forEach((obj) => {
resultObj[Object.keys(obj)[0]] = [];
})
input.forEach((obj) => {
resultObj[Object.keys(obj)[0]].push(obj[Object.keys(obj)[0]]);
resultArr.push(resultObj);
})
console.log([...new Set(resultArr)]);
Another one reduce solution
const arr = [{category: 'furniture', category2: 'furniture2'}, {category: 'mirrors'}, {availability: 'in_stock'}]
const result = Object.values(arr
.flatMap((obj) => Object.entries(obj))
.reduce((acc, [key, value]) => {
acc[key] = acc[key]
? {[key]: [...acc[key][key], value] }
: {[key]: [value] }
return acc;
}, {}));
console.log(result)
.as-console-wrapper{min-height: 100%!important; top: 0}
A generic implementation could achieve a merger of any kind of objects regardless of amount and kind of an(y) object's property names.
Since the result of such an implementation is an object, one needs additional treatment in order to cover the OP's requirement(s).
function mergeAndCollectItemEntries(result, item) {
// return the programmatically aggregated merger/result.
return Object
// get an item's entry array.
.entries(item)
// for each key-value pair ...
.reduce((merger, [key, value]) => {
// ... access and/or create a `key` specific array ...
// ... and push `value` into this array.
(merger[key] ??= []).push(value);
// return the programmatically aggregated merger/result.
return merger;
}, result);
}
const sampleData = [
{ category: 'furniture' },
{ category: 'mirrors' },
{ availability: 'in_stock' },
];
const mergedData = sampleData
.reduce(mergeAndCollectItemEntries, {});
const mergedDataList = Object
.entries(
sampleData
.reduce(mergeAndCollectItemEntries, {})
)
.map(entry => Object.fromEntries([entry]));
//.map(([key, value]) => ({ [key]: value }));
console.log({
sampleData,
mergedData,
mergedDataList,
});
console.log(
Object
.entries([
{ category: 'furniture', foo: 'baz' },
{ category: 'mirrors', bar: 'bizz' },
{ availability: 'in_stock', bar: 'buzz' },
].reduce(
mergeAndCollectItemEntries, {}
)
).map(
([key, value]) => ({ [key]: value })
//entry => Object.fromEntries([entry])
)
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
Another approach here with building an tracking object to merge the values.
Handle the cases of single value keep as string and multiple values as array per the expected output.
const merge = (arr, output = {}) => {
arr.forEach((item) => {
const [[key, val]] = Object.entries(item);
if (key in output) {
output[key] = Array.isArray(output[key])
? output[key].concat(val)
: [output[key]].concat(val);
} else {
output[key] = val;
}
});
return Object.entries(output).map(([key, val]) => ({ [key]: val }));
};
const data = [
{ category: "furniture" },
{ category: "mirrors" },
{ availability: "in_stock" },
];
console.log(merge(data));

Loop through array for each value from another array

How can I loop through this array:
const counts = [
"900,google.com",
"60,mail.yahoo.com",
"10,mobile.sports.yahoo.com",
"40,sports.yahoo.com",
"300,yahoo.com",
"10,stackoverflow.com",
"20,overflow.com",
"5,com.com",
"2,en.wikipedia.org",
"1,m.wikipedia.org",
"1,mobile.sports",
"1,google.co.uk",
];
taking each value from this array?
const uniqueDomains = [
'google.com',
'com',
'mail.yahoo.com',
'yahoo.com',
'mobile.sports.yahoo.com',
'sports.yahoo.com',
'stackoverflow.com',
'overflow.com',
'com.com',
'en.wikipedia.org',
'wikipedia.org',
'org',
'm.wikipedia.org',
'mobile.sports',
'sports',
'google.co.uk',
'co.uk',
'uk'
]
I need to find out if string from counts array includes string from uniqueDomains array.
Then push it to the empty object as a key value pairs, where value
is going to be the number in the beginning of the each string from counts array.
I tried this code but it give me wrong result in my object's values(since I am looping twice)
I need kind of avoid looping twice, but I am not sure how.
Example com is mentioned 8 time in counts array, which means result should be this {com: 1345}
Here is my code:
const finalObject = {}
uniqueDomains.forEach((dom) => {
counts.forEach((cnt) => {
if (cnt.includes(dom)) {
const num = parseInt(cnt);
sumArr.push(num);
const res = sumArr.reduce((acc, cur) => {
return acc + cur;
});
finalObject[dom] = res;
}
});
});
Theres not really any avoiding looping twice (at least), but you can certainly make your code a bit easieer by first turning the count array into an array of val & domain separately.
const countIdx = counts.map(x => {
const [val,domain] = x.split(",");
return {val:parseInt(val,10), domain}
});
Then its just a case of looping the uniqueDomain array and finding all the domaion which match and summing up the val
const result = uniqueDomains.reduce( (res, d) => {
const count = countIdx.filter(x => x.domain.includes(d)).reduce( (acc,x) => acc + x.val,0);
return {...res, [d]:count}
},{});
Live example follows:
const counts = [
"900,google.com",
"60,mail.yahoo.com",
"10,mobile.sports.yahoo.com",
"40,sports.yahoo.com",
"300,yahoo.com",
"10,stackoverflow.com",
"20,overflow.com",
"5,com.com",
"2,en.wikipedia.org",
"1,m.wikipedia.org",
"1,mobile.sports",
"1,google.co.uk",
];
const uniqueDomains = [
'google.com',
'com',
'mail.yahoo.com',
'yahoo.com',
'mobile.sports.yahoo.com',
'sports.yahoo.com',
'stackoverflow.com',
'overflow.com',
'com.com',
'en.wikipedia.org',
'wikipedia.org',
'org',
'm.wikipedia.org',
'mobile.sports',
'sports',
'google.co.uk',
'co.uk',
'uk'
]
const countIdx = counts.map(x => {
const [val,domain] = x.split(",");
return {val:parseInt(val,10), domain}
});
const result = uniqueDomains.reduce( (res, d) => {
const count = countIdx.filter(x => x.domain.includes(d)).reduce( (acc,x) => acc + x.val,0);
return {...res, [d]:count}
},{});
console.log(result);
Maybe try something like:
let finalObject = {}
uniqueDomains.forEach((dom) => {
finalObject[dom] = 0;
counts.forEach((cnt) => {
if (cnt.includes(dom)) {
finalObject[dom] += parseInt(cnt);
}
});
});

How to rename an object property values removing a specific object from the list

I have a list of object
let table = [{id:4,val:"21321"},{id:5,val:"435345"},{id:6,val:"345345"}]
I want to rename the id value after removing an object from the list which has a specific id value(for example id:5)
I am using array filter method
table.filter((element,index)=>{
if(element.id!==5){
element.id=index
return element
}else{
index+1
}
return null
})
I am expecting a return value
[{id: 0,val: "21321"},{id: 1,val: "345345"}]
but i am getting this
[{id: 0, val: "21321"},{id: 2, val: "345345"}]
Note: I know i can use filter method to remove the specific object and than use map method to rename the id value but i want a solution where i have to use only one arrow function
You can use array#reduce to update the indexes and remove elements with given id. For the matched id element, simply return the accumulator and for other, add new object with val and updated id.
const data = [{ id: 4, val: "21321" }, { id: 5, val: "435345" }, { id: 6, val: "345345" }],
result = data.reduce((res, {id, val}) => {
if(id === 5) {
return res;
}
res.push({id: res.length + 1, val});
return res;
}, []);
console.log(result)
This would do it:
let table = [[{id:4,val:"21321"},{id:5,val:"435345"},{id:6,val:"345345"}]];
let res=table[0].reduce((a,c)=>{if(c.id!=5){
c.id=a.length;
a.push(c)
}
return a}, []);
console.log([res])
The only way to do it with "a single arrow function" is using .reduce().
Here is an extremely shortened (one-liner) version of the same:
let res=table[0].reduce((a,c)=>(c.id!=5 && a.push(c.id=a.length,c),a),[]);
Actually, I was a bit premature with my remark about the "only possible solution". Here is a modified version of your approach using .filter() in combination with an IIFE ("immediately invoked functional expression"):
table = [[{id:4,val:"21321"},{id:5,val:"435345"},{id:6,val:"345345"}]];
res= [ (i=>table[0].filter((element,index)=>{
if(element.id!==5){
element.id=i++
return element
} }))(0) ];
console.log(res)
This IIFE is a simple way of introducing a persistent local variable i without polluting the global name space. But, stricly speaking, by doing that I have introduced a second "arrow function" ...
It probably is not the best practice to use filter and also alter the objects at the same time. But you would need to keep track of the count as you filter.
let table = [[{id:4,val:"21321"},{id:5,val:"435345"},{id:6,val:"345345"}]]
const removeReorder = (data, id) => {
var count = 0;
return data.filter(obj => {
if (obj.id !== id) {
obj.id = count++;
return true;
}
return false;
});
}
console.log(removeReorder(table[0], 5));
It is possible to achieve desired result by using reduce method:
const result = table.reduce((a, c) => {
let nestedArray = [];
c.forEach(el => {
if (el.id != id)
nestedArray.push({ id: nestedArray.length, val: el.val });
});
a.push(nestedArray);
return a;
}, [])
An example:
let table = [[{ id: 4, val: "21321" }, { id: 5, val: "435345" }, { id: 6, val: "345345" }]]
let id = 5;
const result = table.reduce((a, c) => {
let nestedArray = [];
c.forEach(el => {
if (el.id != id)
nestedArray.push({ id: nestedArray.length, val: el.val });
});
a.push(nestedArray);
return a;
}, [])
console.log(result);
The main issue in your code is filter method is not returning boolean value. Use the filter method to filter items and then use map to alter object.
let table = [
[
{ id: 4, val: "21321" },
{ id: 5, val: "435345" },
{ id: 6, val: "345345" },
],
];
const res = table[0]
.filter(({ id }) => id !== 5)
.map(({ val }, i) => ({ id: i, val }));
console.log(res)
Alternatively, using forEach with one iteration
let table = [[{id:4,val:"21321"},{id:5,val:"435345"},{id:6,val:"345345"}]]
const res = [];
let i = 0;
table[0].forEach(({id, val}) => id !== 5 && res.push({id: i++, val}));
console.log(res)

Find minimum level in group and combine

In my application similar levels are grouped into an array, I need to fetch the minimum level in that group and replace with that minimum level in all the object. I couldn't get the desired result.
Input
let results = [ [{LEVEL:2,NAME:"ADAMS"},{LEVEL:3,NAME:"JAMES"}],
[{LEVEL:4,NAME:"SHYAM"}],
[{LEVEL:6,NAME:"JIM"},{LEVEL:7,NAME:"ARUN"}]
]
Output
output = [{LEVEL:2,NAME:"ADAMS"},
{LEVEL:2,NAME:"JAMES"},
{LEVEL:4,NAME:"SHYAM"} ,
{LEVEL:6,NAME:"JIM"},
{LEVEL:6,NAME:"ARUN"}
]
Code
result = results.reduce((r, a) => {
a.forEach(({LEVEL,NAME}) => {
var min = r.find(q => q.LEVEL > LEVEL);
if (!min) r.push({LEVEL,NAME});
});
return r;
}, []);
console.log(result)
Iterate with Array.flatMap(), and map each sub array items into the minimum LEVEL value.
You can use Array.map() to get an arrays of LEVEL, and then spread them in Math.min() to get the minimum value (see the getMinLevel() function).
const results = [[{LEVEL:2,NAME:"ADAMS"},{LEVEL:3,NAME:"JAMES"}],[{LEVEL:4,NAME:"SHYAM"}],[{LEVEL:6,NAME:"JIM"},{LEVEL:7,NAME:"ARUN"}]]
const getMinLevel = arr => Math.min(...arr.map(o => o.LEVEL))
const result = results.flatMap(arr => {
const LEVEL = getMinLevel(arr)
return arr.map(o => ({
...o,
LEVEL
}))
})
console.log(result)
For each group, you're going to have to loop through twice. Once to find the min LEVEL, and then again to apply that min. I've used the forEach function here to iterate through these arrays, and I've used a bit of shorthand as well. (group[0] || {LEVEL: 0}) checks if 0 is a valid index of group, and , if not, just uses a default object with a LEVEL of 0 to avoid errors. min > result.LEVEL ? result.LEVEL : min; checks if min is greater than result.LEVEL. If so, it yields result.LEVEL. Otherwise, it yields the current min.
let results = [
[{LEVEL: 2, NAME: "ADAMS"}, {LEVEL: 3, NAME: "JAMES"}],
[{LEVEL: 4, NAME: "SHYAM"}],
[{LEVEL: 6, NAME: "JIM"}, {LEVEL: 7, NAME: "ARUN"}]
];
results.forEach((group) => {
var min = (group[0] || {LEVEL: 0}).LEVEL;
group.forEach((result) => {min = min > result.LEVEL ? result.LEVEL : min;});
group.forEach((result) => {result.LEVEL = min;});
});
console.log(results);
If you still want to do with reduce and forEach combination then I guess you can do as follows:
const names = [
[ {LEVEL:2,NAME:"ADAMS"}, {LEVEL:3,NAME:"JAMES"} ],
[ {LEVEL:4,NAME:"SHYAM"} ],
[ {LEVEL:6,NAME:"JIM"}, {LEVEL:7,NAME:"ARUN"} ],
];
const result = names.reduce((accumulator, elem) => {
elem.forEach(e => {
const smallestNumber = Math.min.apply( Math, elem.map(a => a.LEVEL) );
accumulator.push({LEVEL: smallestNumber, NAME: e.NAME});
});
return accumulator;
}, []);
console.log(result);
I hope this helps!

Push in an a new array and filtering redundant objects in javascript

I receive from an ajax call an array of object and some of them are the same, so i want want to push only unique objects in an another array.
receivedArray = [{name:italy, id:67},{name:italy, id:67},{name:france, id:89}]
and i want that :
myArray = [{name:italy, id:67},{name:france, id:89}]
how can i do that ?
Use reduce & findIndex method. findIndex will return the index of the object if the accumulator array already have an object where the id matches. If index is -1 which mean that accumulator array does not have that object.In that case add the array to the accumulator array
let receivedArray = [{
name: 'italy',
id: 67
}, {
name: 'italy',
id: 67
}, {
name: 'france',
id: 89
}]
let myArray = receivedArray.reduce(function(acc, curr) {
let findIndex = acc.findIndex(function(item) {
return item.id === curr.id;
})
if (findIndex === -1) {
acc.push(curr)
}
return acc;
}, [])
console.log(myArray)
You can use filter and Set to do something like this perhaps:
receivedArray = [{name:'italy', id:67},{name:'italy', id:67},{name:'france', id:89}]
mySet = new Set();
myArray = receivedArray.filter(e => {
if (mySet.has(e['id'])) {
return false;
} else {
mySet.add(e['id']);
return true;
}
})
console.log(myArray);
This can be easily solved in es6:
const receivedArray = [{name:'italy', id:67},{name:'italy', id:67},{name:'france', id:89}]
const newArr = receivedArray.filter((item, index, self) =>
index === self.findIndex((i) => (
i.id === item.id && i.name === item.name
))
)
console.log(newArr)

Categories