lodash sortBy then groupBy, is order maintained? - javascript

I'm having trouble figuring out from the lodash documentation if my assumption about sorting and grouping is correct.
If I use sortBy, then use groupBy, do the arrays produced by groupBy maintain the sort order of items?
For example, say I have the following array:
var testArray = [[5,6],[1,3],[5,4],[5,1]]
And I would like to group these by their first element, but also have them sorted by their second element within these groups. So, in lodash I assume I can do the following:
_.chain(testArray)
.sortBy(function (item) { return item[1]; })
.groupBy(function (item) { return item[0]; })
.value()
Which ends up producing what I would expect it to:
{
1: [[1,3]]
5: [[5,1],[5,4],[5,6]]
}
Is this just coincidence? Is there anything about how sortBy and groupBy work that ensures this ordering of the grouped arrays? The documentation says that sortBy is a stable sort, does that in the same way apply to groupBy? Is there any reason I should not assume this will work every time?

It's not. Here's example, where order is not retained:
const data = [
{
item: 'item1',
group: 'g2'
}, {
item: 'item2',
group: 'g3'
}, {
item: 'item3',
group: 'g1'
}, {
item: 'item4',
group: 'g2'
}, {
item: 'item5',
group: 'g3'
}
]
const groupedItems = _(data).groupBy(item => item.group).value()
In this case one would expect that group order would be: g2, g3, g1 - reality is that they are sorted g1, g2, g3.
You can re-sort them with original array though.
const groupedItems = _(data)
.groupBy(item => item.group)
.sortBy(group => data.indexOf(group[0]))
.value()
This will ensure original order of items.

The current implementation of _.groupBy is:
// An internal function used for aggregate "group by" operations.
var group = function(behavior) {
return function(obj, iteratee, context) {
var result = {};
iteratee = cb(iteratee, context);
_.each(obj, function(value, index) {
var key = iteratee(value, index, obj);
behavior(result, value, key);
});
return result;
};
};
// Groups the object's values by a criterion. Pass either a string attribute
// to group by, or a function that returns the criterion.
_.groupBy = group(function(result, value, key) {
if (_.has(result, key)) result[key].push(value); else result[key] = [value];
});
Basically it iterates through each of the items in the collection in order (if the collection is array-like, which it would be after a sortBy), and pushes them to an array based on their key value.
So yes, I'm not sure if this is an "official" characteristic of _.groupBy, but it does preserve the order of array-like collections, and that's probably unlikely to change.

Function groupBy returns object. Object doesn't save property order.
Does JavaScript Guarantee Object Property Order?
But group arrays saves order, because thay are added with push function.

Related

convert 2 arrays into key value object, one array hold the keys, they second is nested array of matching indexed values

combine each irritation of valueArr it's inner member to a matching key from keysArr,
Index will always match each other.
the result I'm looking for: the key for each object and the id is equal to "internalid"
But i will work it out if it's not clear
const valuesArr =
["10","9","Item","Bank","2","true","true","Result7","5675"],
["9","1","Expenses","OthExpense","4","true","true","9999","9999"],
["8","8","Expenses","OthAsset","6","false","false","6666","77777"],
["7","8","Expenses","AcctPay","4","true","true","666","7777"],
["6","123","123","123","123","123","123","123","123"],
["5","123","123","123","123","123","123","123","123"],
["4","Test1","Item","OthCurrAsset","2","Result5","Result6","ytrytrytryrty","32432"],
["2","Result1","Result2","Result3","Result4","Result5","Result6","Result7","Result8"],
["1","Test1","Test12","Test13","Test14","Test15","Test16","Test17","Test18"]]
const keysArr =
["internalid",
"custrecord_st_segment_transaction_type",
"custrecord_st_segment_sublist",
"custrecord_st_segment_account_type",
"custrecord_st_segment_subsidiary",
"custrecord_st_segment_department",
"custrecord_st_segment_class",
"custrecord_st_segment_location",
"custrecord_st_segment_custom_segment"]
result:
{"1":{"id":"1","values":{"internalid":"1","custrecord_st_segment_transaction_type":"Test1","custrecord_st_segment_sublist":"Test12","custrecord_st_segment_account_type":"Test13","custrecord_st_segment_subsidiary":"Test14","custrecord_st_segment_department":"Test15","custrecord_st_segment_class":"Test16","custrecord_st_segment_location":"Test17","custrecord_st_segment_custom_segment":"Test18"}},
"2":{"id":"2","values":{"internalid":"2","custrecord_st_segment_transaction_type":"Result1","custrecord_st_segment_sublist":"Result2","custrecord_st_segment_account_type":"Result3","custrecord_st_segment_subsidiary":"Result4","custrecord_st_segment_department":"Result5","custrecord_st_segment_class":"Result6","custrecord_st_segment_location":"Result7","custrecord_st_segment_custom_segment":"Result8"}},
"4":{"id":"4","values":{"internalid":"4","custrecord_st_segment_transaction_type":"Test1","custrecord_st_segment_sublist":"Item","custrecord_st_segment_account_type":"OthCurrAsset","custrecord_st_segment_subsidiary":"2","custrecord_st_segment_department":"Result5","custrecord_st_segment_class":"Result6","custrecord_st_segment_location":"ytrytrytryrty","custrecord_st_segment_custom_segment":"32432"}},
"5":{"id":"5","values":{"internalid":"5","custrecord_st_segment_transaction_type":"123","custrecord_st_segment_sublist":"123","custrecord_st_segment_account_type":"123","custrecord_st_segment_subsidiary":"123","custrecord_st_segment_department":"123","custrecord_st_segment_class":"123","custrecord_st_segment_location":"123","custrecord_st_segment_custom_segment":"123"}},
"6":{"id":"6","values":{"internalid":"6","custrecord_st_segment_transaction_type":"123","custrecord_st_segment_sublist":"123","custrecord_st_segment_account_type":"123","custrecord_st_segment_subsidiary":"123","custrecord_st_segment_department":"123","custrecord_st_segment_class":"123","custrecord_st_segment_location":"123","custrecord_st_segment_custom_segment":"123"}},
"7":{"id":"7","values":{"internalid":"7","custrecord_st_segment_transaction_type":"8","custrecord_st_segment_sublist":"Expenses","custrecord_st_segment_account_type":"AcctPay","custrecord_st_segment_subsidiary":"4","custrecord_st_segment_department":"true","custrecord_st_segment_class":"true","custrecord_st_segment_location":"666","custrecord_st_segment_custom_segment":"7777"}},
"8":{"id":"8","values":{"internalid":"8","custrecord_st_segment_transaction_type":"8","custrecord_st_segment_sublist":"Expenses","custrecord_st_segment_account_type":"OthAsset","custrecord_st_segment_subsidiary":"6","custrecord_st_segment_department":"false","custrecord_st_segment_class":"false","custrecord_st_segment_location":"6666","custrecord_st_segment_custom_segment":"77777"}},
"9":{"id":"9","values":{"internalid":"9","custrecord_st_segment_transaction_type":"1","custrecord_st_segment_sublist":"Expenses","custrecord_st_segment_account_type":"OthExpense","custrecord_st_segment_subsidiary":"4","custrecord_st_segment_department":"true","custrecord_st_segment_class":"true","custrecord_st_segment_location":"9999","custrecord_st_segment_custom_segment":"9999"}},
"10":{"id":"10","values":{"internalid":"10","custrecord_st_segment_transaction_type":"9","custrecord_st_segment_sublist":"Item","custrecord_st_segment_account_type":"Bank","custrecord_st_segment_subsidiary":"2","custrecord_st_segment_department":"true","custrecord_st_segment_class":"true","custrecord_st_segment_location":"Result7","custrecord_st_segment_custom_segment":"5675"}}]
I think the OP is asking how to "zip" two arrays, where one has keys and one has values. If so, Object.fromEntries() is very useful.
A simple zip, goes like this:
// a simple zip
function zip(keys, values) {
return Object.fromEntries(
keys.map((key, index) => [key, values[index]])
);
}
The OP appears to want the lead element in the values array to be a specially named field in the new object as well as the key of the resulting object. Here, applying the simple zip...
function zip(keys, values) {
return Object.fromEntries(
keys.map((key, index) => [key, values[index]])
);
}
// the value array contains an "id" element at the start
// produce an object that looks like { id: number, values: {...} }
function objectFromValues(keys, values) {
return { id: values[0], values: zip(keys, values.slice(1)) }
}
const valuesArr = [
[1, "A", "B", "C"],
[2, "D", "E", "F"]
];
const keys = ["keyA", "keyB", "keyC"];
const result = valuesArr.reduce((acc, values) => {
acc[values[0]] = objectFromValues(keys, values);
return acc;
}, {});
console.log(result)

Get key name of a javascript object in an array of objects

I have some Entries, those entries have a Category assigned to them, and each entry has a numeric Value;
{
category: "cat1",
value: -100
}
This is my entry ^. I run a Loadash GroupBy to group all the categories.
const groups = _.groupBy(dataSource, (entry) => entry.category);
This snippet here returns like this:
{
"cat1": [
{
category: "cat1",
value: -100
},
{
category: "cat1",
value: +10
},
],
"cat2": [
{
category: "cat2",
value: -100
},
{
category: "cat2",
value: +40
},
//and so on...
}
The keys are the Category names. The objects are the related entries.
I need to run a consecutive Map with an embedded Reduce to reduce the arrays to an integer through the sum of each entry's value.
const categoryValues = _.map(groups, (element) => {
return {
categoryName: ???????,
//*I DONT KNOW WHAT GOES HERE ^,
//I NEED THE NAME OF THE CATEGORY TO BE HERE*
categoryValue: _.reduce(element,(acc, el) => el.value,0),
};
});
Thats because my graph api needs his dataset array to be formed by objects like this one:
{
"categoryName": "cat1", //*THIS IS MISSING*
"categoryValue": 999
}
How can I access the key name of each array? I need to know how the category is named, so that the graph will display its name.
What needs to be written where I put my question marks?
You can get the name of the category from the second parameter of the callback function. This callback function is called by the lodash library and when it calls map function 3 arguments (value, key, collection). map
const categoryValues = _.map(groups, (element, name) => {
return {
categoryName: name, //I DONT KNOW WHAT GOES HERE, I NEED THE NAME OF THE CATEGORY TO BE HERE*
categoryValue: _.reduce(element, (acc, el) => el.value, 0),
};
});
The documentation for map says:
Creates an array of values by running each element in collection thru
iteratee. The iteratee is invoked with three arguments: (value,
index|key, collection).
As you can see, the second parameter passed to the iteratee is the original key in the collection. So, just add another parameter to the iteratee and use that:
const categoryValues = _.map(groups, (element, name) => {
return {
categoryName: name,
categoryValue: _.reduce(element,(acc, el) => el.value,0),
};
});
// this is the simple way.
var arr = {
iamkeysA : [1,2,3],
iamkeysB : [4,5,6]
}
var keys_ = Object.keys(arr); // now you get the list of keys name.
console.log( keys_[0] );
console.log( keys_[1] );

Sort Object Elements in Javascript

I have an object like this:
{
"name":['John','Bob','Ram','Shyam'],
"marks":['64','22','80','32']
}
I have to sort the names. After I sort names, marks must be matched according to the names.
When I use sort() function, It only sorts the names. How can I sort my overall object?
One option is to alter your data structure to be more sortable. For example:
const data = {
names: ['John','Bob','Ram','Shyam'],
marks: ['64','22','80','32']
}
const people = data.names.map((name, i) => ({
name,
mark: data.marks[i]
}))
const sorted = people.sort((a, b) => a.name.localeCompare(b.name))
console.log(sorted)
A second option would be to keep an array of indexes, which you sort based on the data. This doesn't alter your original structures, but I don't think it is a good option, because it would be hard to keep both the names and marks arrays synced. For example:
const data = {
names: ['John','Bob','Ram','Shyam'],
marks: ['64','22','80','32']
}
const index = Array.from(Array(data.names.length).keys())
index.sort((a, b) => data.names[a].localeCompare(data.names[b]))
console.log(index)
// use the names & marks
index.forEach(i => {
console.log(`${data.names[i]} - ${data.marks[i]}`)
})
You can create another key by name rel and its value will be an array of objects , where each object has keys by name name & mark.
While creating this array you can use sort function to sort these objects in order
let obj = {
"name": ['John', 'Bob', 'Ram', 'Shyam'],
"marks": ['64', '22', '80', '32']
}
obj.rel = obj.name.map(function(e, i) {
return {
name: e,
mark: obj.marks[i]
}
}).sort((a, b) => a.name.localeCompare(b.name))
console.log(obj)

How to order list item and push something to the beginning of array

I want to order my dynamic data so that if it includes a particular string, push it to the top of the list item. Then push the rest in. I'm confused about how to go about this.
So I have something like
So if it includes overview, push it to the front and rest push after. I'm kind of confused about how to do this.
You can simply use reduce in combination with the unshift and push methods to return a new Array that organizes itself based on whether or not the value of each object includes "overview":
tree.reduce( (acc, cv) => !cv.value.includes( "overview" ) ?
(acc.push(cv), acc) :
(acc.unshift(cv), acc ), []);
//data setup
let tree = ["A","B","overview","C","D","overview","E","overview"].map(_=>Object({value:_}));
//result
let result = tree.reduce((acc, cv) =>
!cv.value.includes( "overview" ) ?
(acc.push(cv), acc) :
(acc.unshift(cv), acc ), []);
console.log( result);
Your code works to push anything with 'overview' to the front with the unshift call. You do not need the last push call as the array is modified in place with unshift.
You can use Array.prototype.sort() method
const values = [
{ value: 'foo' },
{ value: 'bar' },
{ value: 'baz' },
{ value: 'overview' },
]
function toFront(arr, search) {
return arr.sort(a => -a.value.includes(search))
}
console.log(toFront(values, 'overview'))
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
You could use reduce to populate two arrays, one for each category, and join them at the end:
// Input
const tree = [{value: "A"},{value:"B"},{value:"overview"},{value:"C"},{value:"D"},{value:"overview"},{value:"E"},{value:"overview"}];
// Transformation
const result = [].concat(...tree.reduce(
(acc, obj) => (acc[1-obj.value.includes("overview")].push(obj), acc),
[[], []]
));
console.log( result);

Filter a javascript tree without mutating the original array

I'm working on a react project where I need to filter an array of objects without mutating the original array
const array = [{
name: 'bar',
children: [{
name: 'foo',
children: [{
name: 'baz123',
}, {
name: 'baz',
}]
}]
}, {
name: 'shallowKey'
}, {
name: 'abc'
}];
For example, I want to only filter the concerned object and its children.
This is the jsfiddle
function filterData(data) {
var r = data.filter(function(o) {
if (o.children) o.children = filterData(o.children);
return o.name.length === 3;
})
return r;
}
I tried that function from a stackoverflow question, but is there a way to use that same functionality without mutating the data. Thanks
If you don't have any prototypes or functions involved within the objects a simple way to copy is to stringify original and parse it back to object
var r= JSON.parse(JSON.stringify(data)).filter(...
Array .filter() already creates a new array, you just need to fix the part where the o.children is mutated. To do that you could use .map() and simply copy all fields using Object.assign() or object spread and just assign children as the result passed through the same filter function:
function filterData(data) {
return data
.filter(obj => obj.name.length === 3) // filter array first
.map(obj => ({ // then re-map to new objects
...obj, // copy shallow fields
children: obj.children && filterData(obj.children) // filter children
}));
}
You can create a copy of your original array using a spread operator or Object.assign() function.
const arrayCopy= [...array] //spread operator
const arrayCopy = Object.assign({}, array);
Otherwise as Aaron suggested, using filter(), map(), reduce() function always returns a new array without mutating your original array.

Categories

Resources