How to modify the groupBy result in Lodash - javascript

How to modify my script, so in result there is no key "author" (I want to have only song and place)?
The songs' list looks like that (obviously I do not copy whole list)
const lp3 = [
{
author: 'Queen',
song: 'Bohemian Rhapsody',
place: 1,
change: 0
},
{
author: 'Deep Purple',
song: 'Child in time',
place: 2,
change: 2
},
and my script:
const exercise10 = _.groupBy(lp3, "author")
and the result is e.g.:
Pink Floyd': [
{
author: 'Pink Floyd',
song: 'Shine on you crazy diamond',
place: 12,
change: 4
},
{
author: 'Pink Floyd',
song: 'Comfortably numb',
place: 15,
change: 7
},
{ author: 'Pink Floyd', song: 'Hey you', place: 18, change: 11 },
{
author: 'Pink Floyd',
song: 'Another brick in the wall part II',
place: 21,
change: 10
}
],

Using Array.reduce() or lodash's _.reduce() you can destructure each object, and take out author, and then group the objects:
const lp3 = [{"author":"Queen","song":"Bohemian Rhapsody","place":1,"change":0},{"author":"Deep Purple","song":"Child in time","place":2,"change":2}]
const result = _.reduce(lp3, (acc, { author, ...o }) => {
if(!acc[author]) acc[author] = []
acc[author].push(o)
return acc
}, {})
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"></script>
If you want to use _.groupBy() you'll need to group the items, and then map the groups with _.mapValues(), and map the items to omit author:
const lp3 = [{"author":"Queen","song":"Bohemian Rhapsody","place":1,"change":0},{"author":"Deep Purple","song":"Child in time","place":2,"change":2}]
const result1 = _.mapValues(
_.groupBy(lp3, 'author'),
values => _.map(values, o => _.omit(o, 'author')),
)
// or using chaining
const result2 = _(lp3)
.groupBy(lp3, 'author') // create the groups
.mapValues(values => // map the values of the groups x
_.map(values, o => _.omit(o, 'author')) // map the items
)
.value()
console.log(result1)
console.log(result2)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js" integrity="sha512-WFN04846sdKMIP5LKNphMaWzU7YpMyCU245etK3g/2ARYbPK9Ub18eG+ljU96qKRCWh+quCY7yefSmlkQw1ANQ==" crossorigin="anonymous"></script>

Related

Get specific element from array of objects

I want to get result of all strength properties in the array for a specific name.
For example, I want to get strength for pachio & hunter only. The expected result should be,
['cool kids', 'suck bloods']
I only know how to filter one value.
const array = [{
name: 'pachio',
strength: 'cool kids'
}, {
name: 'hunter',
strength: 'suck bloods'
}, {
name: 'chloyi',
strength: 'cute'
}]
const result = array.filter(function(getName) {
return getName.name == 'pachio'; /*This_need_to_be_fix*/
});
const result2 = result.map((getStrength) => getStrength.strength);
console.log(result2) /*cool kids*/
You can also combine two methods
const array = [{
name: 'pachio',
strength: 'cool kids'
},
{
name: 'hunter',
strength: 'suck bloods'
},
{
name: 'chloyi',
strength: 'cute'
},
]
let ar = ['pachio', 'hunter']
let data = array.filter(el => ar.includes(el.name)).map(ob => ob.strength)
console.log(data)
You're already using the correct method, filter(), you simply need to add another condition to the function to also allow the name property value to be hunter:
const array = [{
name: 'pachio',
strength: 'cool kids'
}, {
name: 'hunter',
strength: 'suck bloods'
}, {
name: 'chloyi',
strength: 'cute'
}]
const result = array.filter(o => o.name == 'pachio' || o.name == 'hunter');
const result2 = result.map(o => o.strength);
console.log(result2)
If the list of allowed names gets longer, then you can use an array to contain them to keep the logic more clear:
const targetNames = ['pachio', 'hunter'];
const result = array.filter(o => targetNames.includes(o.name));
You can also use a second search array, if you want it to be more dynamic, e.g from an input.
const searchBy = ['pachio', 'hunter']
const array = [{
name: 'pachio',
strength: 'cool kids'
}, {
name: 'hunter',
strength: 'suck bloods'
}, {
name: 'chloyi',
strength: 'cute'
}]
const result = array.filter(o => searchBy.includes(o.name));
const result2 = result.map(o => o.strength);
console.log(result2)
This solution uses .filter like you and Rory have mentioned but adds .includes to check for the match.
Before answering, the next time you ask, please arrange the code with quotation marks where necessary.
A filter returns you all the values ​​that match the condition,
According to what you are asking for, you want anyone who is different from = "chloyi"
so...
const array = [
{
name: "pachio",
strength: "cool kids"
},
{
name: "hunter",
strength: "suck bloods"
},
{
name: "chloyi",
strength: "cute"
}
]
const result = array.filter(function (getName) {
return getName.name !== 'chloyi';
});
const result2 = result.map((getStrength) => getStrength.strength);
console.log(result2)

How to check if array of array contain atleast one element?

I am facing one problem in javascript filter.
Suppose this is an array1-
const array1 = [
{
title: 'Stock market news',
symbols: ['SPY.US', 'GSPC.INDX', 'DJI.INDX', 'CL.COMM', 'IXIC.INDX', 'NQ.COMM', 'ES.COMM'],
},
{
title: 'Neil Young urges Spotify',
symbols: ['SPOT.US', '639.F', '639.XETRA']
},
{
title: 'Neil Young urges Spotify',
symbols: ['AAPl.US', '639.F', '639.XETRA']
}
]
And this is an array2
const array2 = [
{Code: "AAPL"},
{Code: 'SPOT'}
]
I have to filer array1 and remove an object that not complete the condition. The condition is if the array1 symbols contain at least one element of array2 Code. I mean if the array2 Code is match arry1 symbols field at least one element.
In the above example, the result should be-
const array1 = [
{
title: 'Neil Young urges Spotify',
symbols: ['SPOT.US', '639.F', '639.XETRA']
},
{
title: 'Neil Young urges Spotify',
symbols: ['AAPl.US', '639.F', '639.XETRA']
}
]
Because this two object contain AAPL and SPOT in symbols field. I think I can clear all the things.
I am trying in this way-
const filterData = array1.filter(function (array1El) {
return !array2.find(function (array2El) {
return array1El.symbols.includes(`${array2El.Code}.US`);
})
});
But it is not working. Please say me where I am wrong.
There are two issues:
Your !array2.find condition is backwards - you want to filter to include items for which array2.find does have a match, not items for which it doesn't.
'AAPl.US' !== 'AAPL.US' - make them the same case before comparing.
It'd also be clearer to use .some instead of .find.
const array1 = [
{
title: 'Stock market news',
symbols: ['SPY.US', 'GSPC.INDX', 'DJI.INDX', 'CL.COMM', 'IXIC.INDX', 'NQ.COMM', 'ES.COMM'],
},
{
title: 'Neil Young urges Spotify',
symbols: ['SPOT.US', '639.F', '639.XETRA']
},
{
title: 'Neil Young urges Spotify',
symbols: ['AAPl.US', '639.F', '639.XETRA']
}
]
const array2 = [
{Code: "AAPL"},
{Code: 'SPOT'}
]
const filterData = array1.filter(function (array1El) {
return array2.some(function (array2El) {
return array1El.symbols
.map(s => s.toLowerCase())
.includes(`${array2El.Code.toLowerCase()}.us`);
})
});
console.log(filterData);
Or create a Set of matching symbols first, which I'd prefer for lower complexity.
const array1 = [
{
title: 'Stock market news',
symbols: ['SPY.US', 'GSPC.INDX', 'DJI.INDX', 'CL.COMM', 'IXIC.INDX', 'NQ.COMM', 'ES.COMM'],
},
{
title: 'Neil Young urges Spotify',
symbols: ['SPOT.US', '639.F', '639.XETRA']
},
{
title: 'Neil Young urges Spotify',
symbols: ['AAPl.US', '639.F', '639.XETRA']
}
]
const array2 = [
{Code: "AAPL"},
{Code: 'SPOT'}
];
const codesToFind = new Set(array2.map(({ Code }) => Code.toLowerCase() + '.us'));
const filterData = array1.filter(
({ symbols }) => symbols.some(
sym => codesToFind.has(sym.toLowerCase())
)
);
console.log(filterData);
We could use a regex alternation approach here:
const array1 = [
{
title: 'Stock market news',
symbols: ['SPY.US', 'GSPC.INDX', 'DJI.INDX', 'CL.COMM', 'IXIC.INDX', 'NQ.COMM', 'ES.COMM'],
},
{
title: 'Neil Young urges Spotify',
symbols: ['SPOT.US', '639.F', '639.XETRA']
},
{
title: 'Neil Young urges Spotify',
symbols: ['AAPL.US', '639.F', '639.XETRA']
}
];
const array2 = [{Code: "AAPL"}, {Code: "SPOT"}];
var regex = new RegExp("\\b(?:" + array2.reduce((x, y) => x.Code + "|" + y.Code) + ")\\.US");
console.log(regex); // /\b(?:AAPL|SPOT)\.US/
var output = array1.filter(x => x.symbols.some(e => regex.test(e)));
console.log(output);
The strategy here is to form a regex alternation of stock symbols, one of which is required. We then filter the original array, using some() and the regex to ensure that any match has at least one required stock symbol.
If you're doing this search multiple times, it would be best to build a symbol index and query that.
For example...
const array1 = [{"title":"Stock market news","symbols":["SPY.US","GSPC.INDX","DJI.INDX","CL.COMM","IXIC.INDX","NQ.COMM","ES.COMM"]},{"title":"Neil Young urges Spotify","symbols":["SPOT.US","639.F","639.XETRA"]},{"title":"Neil Young urges Spotify","symbols":["AAPl.US","639.F","639.XETRA"]}]
const array2 = [{Code: "AAPL"},{Code: 'SPOT'}]
// utility function to write into the index
const writeIndex = (map, key, entry) => {
const normalisedKey = String.prototype.toUpperCase.call(key)
if (!map.has(normalisedKey)) map.set(normalisedKey, new Set())
map.get(normalisedKey).add(entry)
}
const symbolIndex = array1.reduce((map, entry) => {
// convert all symbols to tokens
// eg AAPL.US becomes ["AAPL.US", "AAPL", "US"]
const keys = entry.symbols.flatMap(symbol =>
[symbol, ...symbol.split(".")])
// add the entry for each symbol token
keys.forEach(key => writeIndex(map, key, entry))
return map
}, new Map())
// pull unique items out of the index for the given codes
const queryIndex = codes => Array.from(new Set(codes.flatMap(code =>
[...symbolIndex.get(code) ?? []]
)))
console.log(queryIndex(array2.map(({ Code }) => Code)))
.as-console-wrapper { max-height: 100% !important; }
Alternatively, you can use a nested some clause with a string includes check to see if the symbol contains your code.
const array1 = [{"title":"Stock market news","symbols":["SPY.US","GSPC.INDX","DJI.INDX","CL.COMM","IXIC.INDX","NQ.COMM","ES.COMM"]},{"title":"Neil Young urges Spotify","symbols":["SPOT.US","639.F","639.XETRA"]},{"title":"Neil Young urges Spotify","symbols":["AAPl.US","639.F","639.XETRA"]}]
const array2 = [
{Code: "AAPL"},
{Code: 'SPOT'}
]
const normalise = str => str.toUpperCase()
const normalisedCodes = array2.map(({ Code }) => normalise(Code))
const filtered = array1.filter(({ symbols }) =>
normalisedCodes.some(code =>
symbols.some(symbol => normalise(symbol).includes(code))
)
)
console.log(filtered)
.as-console-wrapper { max-height: 100% !important; }

merge partially duplicated arrays in javascript (lodash)

I have a large javascript array of some people Bought a car in different years.
the simplified array is like this:
const owners = [{
name: "john",
hasCar: true,
yearBought: 2002
}, {
name: "john",
hasCar: true,
yearBought: 2005
}, {
name: "mary",
hasCar: true,
yearBought: 2015
}, {
name: "john",
hasCar: true,
yearBought: 2018
}]
if a person has more than one car (like John in this example), there is different objects for him with different years he has bought the car. I want to merge objects belongs to each individual person and the final result should be like this:
const owners = [{
name: "john",
hasCar: true,
yearBought: [2002, 2005, 2018]
}, {
name: "mary",
hasCar: true,
yearBought: 2018
}]
You could use reduce the array and group them based on name first. The accumulator is an object with each unique name as key. If the name already exists, use concat to push them into the array. Else, create a new key in the accumulator and set it to the current object. Then, use Object.values() to get the values of the array as an array
const owners = [{name:"john",hasCar:!0,yearBought:2002},{name:"john",hasCar:!0,yearBought:2005},{name:"mary",hasCar:!0,yearBought:2015},{name:"john",hasCar:!0,yearBought:2018}];
const merged = owners.reduce((r, o) => {
if(r[o.name])
r[o.name].yearBought = [].concat(r[o.name].yearBought, o.yearBought)
else
r[o.name] = { ...o };
return r;
},{})
console.log(Object.values(merged))
Just use reduce:
const owners = [{name:"john",hasCar:true,yearBought:2002},{name:"john",hasCar:true,yearBought:2005},{name:"mary",hasCar:true,yearBought:2015},{name:"john",hasCar:true,yearBought:2018}];
const newOwners = Object.values(owners.reduce((acc, curr) => {
acc[curr.name] = acc[curr.name] ? { ...acc[curr.name], yearBought: [].concat(acc[curr.name].yearBought, curr.yearBought) } : curr;
return acc;
}, {}));
console.log(newOwners);
I hope this help using PURE lodash functions.. it looks clean and readable.
var array = [{
name: "john",
hasCar: true,
yearBought: 2002
}, {
name: "john",
hasCar: true,
yearBought: 2005
}, {
name: "mary",
hasCar: true,
yearBought: 2015
}, {
name: "john",
hasCar: true,
yearBought: 2018
}]
function mergeNames(arr) {
return Object.values(_.chain(arr).groupBy('name').mapValues((g) => (_.merge(...g, {
yearBought: _.map(g, 'yearBought')
}))).value());
}
console.log(mergeNames(array));
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.11/lodash.min.js"></script>
Thanks :)
You can group the items with a unique key (name in your case), then map the groups, and merge the items in each group:
const { flow, partialRight: pr, groupBy, map, mergeWith, concat, isUndefined } = _
const mergeDuplicates = (isCollected, key) => flow(
pr(groupBy, key), // group by the unique key
pr(map, group => mergeWith({}, ...group,
(o, s, k) => isCollected(k) && !isUndefined(o) ? concat(o, s) : s
)) // merge each group to a new object
)
const owners = [{name:"john",hasCar:true,yearBought:2002},{name:"john",hasCar:true,yearBought:2005},{name:"mary",hasCar:!0,yearBought:2015},{name:"john",hasCar:true,yearBought:2018}]
const isCollected = key => key === 'yearBought'
const result = mergeDuplicates(isCollected, 'name')(owners)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>
And the lodash/fp version:
const { flow, groupBy, map, mergeAllWith, cond, nthArg, concat } = _
const mergeDuplicates = (isCollected, key) => flow(
groupBy(key),
map(mergeAllWith(cond([[
flow(nthArg(2), isCollected),
concat,
nthArg(1)
]])))
)
const owners = [{name:"john",hasCar:!0,yearBought:2002},{name:"john",hasCar:!0,yearBought:2005},{name:"mary",hasCar:!0,yearBought:2015},{name:"john",hasCar:!0,yearBought:2018}]
const isCollected = key => key === 'yearBought'
const result = mergeDuplicates(isCollected, 'name')(owners)
console.log(result)
<script src='https://cdn.jsdelivr.net/g/lodash#4(lodash.min.js+lodash.fp.min.js)'></script>

Day/Hour Grouped Array from single date using Lodash and Moment

I'm trying to transform my array so that its easily used while rendering views
I have this sample code for now
let arr = [
{
date: d.setDate(d.getDate() - 1),
name: "john",
country: "AU",
text: "Hey"
},
{
date: d.setDate(d.getDate() - 1),
name: "jake",
country: "US",
text: "Hey"
},
{
date:d.setDate(d.getDate() - 3),
name: "jeff",
country: "US",
text: "Hey"
},
{
date:d.setDate(d.getDate() - 5),
name: "jared",
country: "US",
text: "Hey"
},
{
date:d.setDate(d.getDate() - 2),
name: "jane",
country: "UK",
text: "Hey"
}
]
let hours = _.groupBy(arr, (result) => moment(result['date']).startOf('hour').format("LT"));
let day = _.groupBy(arr, (result) => moment(result['date']).startOf('day').format("MMM Do YY"));
console.log(day)
What i want is to group the data by hours which is grouped by days from a single "date" string (i can do that seperately via _.groupby function but i want it to output a combined array.
The desired output should be something like this :
{
'Apr 15th 19': [
{'4 pm':[
{
date: 1555318593445,
name: 'ahmed',
country: 'AU',
text: 'Hey'
}
]
]
}....
Use _.flow() to create a function that groups by the day, and then maps each days values, and groups them by the hour:
const { flow, groupBy, mapValues } = _
const fn = flow(
arr => groupBy(arr, v => moment(v.date).startOf('day').format("MMM Do YY")),
groups => mapValues(groups, g => _.groupBy(g, v =>
moment(v.date).startOf('hour').format("LT")
))
)
const arr = [{"date":1555318593445,"name":"john","country":"AU","text":"Hey"},{"date":155531859300,"name":"jake","country":"US","text":"Hey"},{"date":1555316593445,"name":"jeff","country":"US","text":"Hey"},{"date":1555316593345,"name":"jared","country":"US","text":"Hey"},{"date":155531659400,"name":"jane","country":"UK","text":"Hey"}]
const result = fn(arr)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>
And the terser lodash/fp version:
const { flow, groupBy, mapValues } = _
const fn = flow(
groupBy(v => moment(v.date).startOf('day').format("MMM Do YY")),
mapValues(_.groupBy(v => moment(v.date).startOf('hour').format("LT")))
)
const arr = [{"date":1555318593445,"name":"john","country":"AU","text":"Hey"},{"date":155531859300,"name":"jake","country":"US","text":"Hey"},{"date":1555316593445,"name":"jeff","country":"US","text":"Hey"},{"date":1555316593345,"name":"jared","country":"US","text":"Hey"},{"date":155531659400,"name":"jane","country":"UK","text":"Hey"}]
const result = fn(arr)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js"></script>
<script src='https://cdn.jsdelivr.net/g/lodash#4(lodash.min.js+lodash.fp.min.js)'></script>
Comments Q&A:
How is flow different from chain?
Flow generates a function that runs the code in a sequence and passes the results of one function call to the next one. The lodash chain runs the code, but tries to make lazy evaluation, and combines several calls to a single one, which means that a chain of .map.filter will only iterate the array once.
However, the laziness comes with a price, you need to import the entire lodash module for it to work. While using flow, you can import just the functions that you need.
why mapvalues was the second step?
The result of the 1st group is an object { date: {}, date: {} ), and you need to group each the object values by itself. To do so you need to map the values of each date, and group them by the hour.

Javascript can't group objects with 2 values

I have this object data:
[ RowDataPacket {
id: 59,
steamid: '76561198220437096',
product_id: 23,
status: 1,
date: 2017-12-18T17:27:19.000Z,
message: null,
name: 'CS.MONEY',
amount: 100,
website: 'csgo500' },
RowDataPacket {
id: 60,
steamid: '76561198220437096',
product_id: 24,
status: 1,
date: 2017-12-18T17:27:19.000Z,
message: null,
name: 'CS.MONEY',
amount: 250,
website: 'csgo500' },
RowDataPacket {
id: 61,
steamid: '76561198220437096',
product_id: 23,
status: 1,
date: 2017-12-18T17:27:19.000Z,
message: null,
name: 'CS.MONEY',
amount: 100,
website: 'csgo500' },
RowDataPacket {
id: 62,
steamid: '76561198345348530',
product_id: 6,
status: 1,
date: 2017-12-18T20:05:55.000Z,
message: null,
name: 'wal gruche',
amount: 100,
website: 'csgoatse' }
Im trying to sort this data with steamid and website, i managed to sort this only by one value like this:
var groupedOrders = {};
row.forEach(function(item){
var list = groupedOrders[item.steamid];
if(list){
list.push(item);
} else{
groupedOrders[item.steamid] = [item];
}
});
My idea was to make two dimensional array but for some reason i cant do it like this:
var list = groupedOrders[item.steamid][item.website];
It throws me an error "Cant read property ... of undefined"
Now my code looks like this:
var groupedOrders = {};
row.forEach(function(item){
var list = groupedOrders[item.steamid][item.website];
if(list){
list.push(item);
} else{
groupedOrders[item.steamid][item.website] = [item];
}
});
Do you have any ideas how to fix this errors?
The problem is that var list = groupedOrders[item.steamid][item.website] is actually saying:
var temp = groupedOrders[item.steamid];
var list = temp[item.website];
There is no entry at groupedOrders[item.steamid] and so line one sets temp to undefined. The second line tries to index into undefined which is an error.
You would have to split the code out and essentially do the whole one-key grouping twice:
var outerList = groupedOrders[item.steamid];
if (!outerList)
outerList = groupedOrders[item.steamid] = {};
var innerList = outerList[item.website];
if (innerList)
innerList.push(item);
else
outerList[item.website] = [item];
(I have not tested this code but it is the right shape.)
The following works by creating a recursive groupBy grouping function for each of the fields supplied as an argument.
These dynamically created groupBy functions are then invoked one by one, passing the result between, starting with the supplied data.
Each groupBy function instance creates an object and adds properties to it corresponding to the key values for the field being grouped.
By calling these groupBy functions successively, we create a progressively more nested tree of objects, with groups at each successive level marked as being groups using a symbol.
The final result is a nest (a tree!) of objects, with keys corresponding to the field used for indexing at that level.
Finally, we flatten the nest and the final order is visible.
const flatten = o => Object.values(o).reduce((acc, c) => (Array.isArray(c) ? [...acc, ...c] : typeof c === 'object' ? [...acc, ...flatten(c)] : [...acc, c]), []);
const flow = (...fns) => data => fns.reduce((acc, c) => c(acc), data);
const GROUP = Symbol('group');
const asGroup = (result = []) => ((result[GROUP] = true), result);
const isGroup = o => o[GROUP];
const groupBy = field => (data, key) =>
data.reduce((acc, c) =>
((key = c[field]), (acc[key] ?
(acc[key].push(c), acc) :
((acc[key] = asGroup([c])), acc))), {});
const recurse = (test) => (transform) => o =>
test(o)
? transform(o)
: Object.entries(o).reduce(
(acc, [k, v]) => (test(v) ?
((acc[k] = transform(v)), acc) :
((acc[k] = recurse(test)(transform)(v)), acc)), {});
const group = (...fields) => flow(...fields.map(flow(groupBy, recurse(isGroup))), flatten);
const rows = asGroup([
{
id: 0,
steamid: '2',
website: 'a'
},
{
id: 1,
steamid: '2',
website: 'b'
},
{
id: 2,
steamid: '2',
website: 'a'
},
{
id: 3,
steamid: '1',
website: 'b'
},
{
id: 4,
steamid: '0',
website: 'b'
}
]);
console.log(JSON.stringify(group('steamid', 'website')(rows), null, 2));

Categories

Resources