Need explanation: Why .reduce() returns wrongly ordered object

Hi im currently working on a project where i want to store and order ids in groups, which can also be sorted. So my datastructure looks like this:
// Key is group id and value is an array of item ids that belong to that group
const itemGroups = {
"1": [10, 11, 12],
"2": [13, 14, 15]
}
// Key is group id and value is an array of item ids that belong to that group
const itemGroups = {
"1": [10, 11, 12],
"2": [13, 14, 15]
}
I use the library sortablejs to sort these items & groups. But somehow im not able to sort the groups in the object with the following:
function sortGroups(oldIndex, newIndex) {
const groupIds = Object.keys(itemGroups)
const [id] = groupIds.splice(oldIndex, 1)

groupIds.splice(newIndex, 0, id)
// The groupIds are correctly sorted here

return groupIds.reduce((acc, groupId) => {
acc[groupId] = itemGroups[groupId]

return acc
}, {})
// But after the reduce, The new order hasn't changed
}
function sortGroups(oldIndex, newIndex) {
const groupIds = Object.keys(itemGroups)
const [id] = groupIds.splice(oldIndex, 1)

groupIds.splice(newIndex, 0, id)
// The groupIds are correctly sorted here

return groupIds.reduce((acc, groupId) => {
acc[groupId] = itemGroups[groupId]

return acc
}, {})
// But after the reduce, The new order hasn't changed
}
I checked to see if .reduce() actually iterates in order of the array. And it does. But somehow the second pair gets inserted in the beginning of the object. Can someone explain to me whats happening here? Its really confusing.
13 Replies
Zoë
Zoë12mo ago
function sortObject(groups) {
return Object.keys(groups)
.sort((a,b) => a-b)
.reduce((acc, key) => {
acc[key] = groups[key].sort((a,b) => a-b);
return acc;
}, {});
}
sortObject({
"2": [15,13, 14],
"1": [12,10, 11],
}); //=> {"1": [10, 11, 12], "2": [13, 14, 15]}
function sortObject(groups) {
return Object.keys(groups)
.sort((a,b) => a-b)
.reduce((acc, key) => {
acc[key] = groups[key].sort((a,b) => a-b);
return acc;
}, {});
}
sortObject({
"2": [15,13, 14],
"1": [12,10, 11],
}); //=> {"1": [10, 11, 12], "2": [13, 14, 15]}
Is this what you're after? Also I wouldn't do this. I'd make the object an array and then move the key into a property [{key:1,value:[10,11,12]},{key:2,value:[13,14,15]}]
MarkBoots
MarkBoots12mo ago
If that is not what you're were after, please give an example of the input and output.
bob_merlin
bob_merlin12mo ago
First and foremost i wanted an explanation why my version doesn't work. I used .splice() instead of .sort() on the keys array because i think it would be easier to read compared to a sort callback that moves 1 item to a new index (i dont even know how to do that in a .sort() method with only using oldIndex & newIndex 😞 ) . Input is what i wrote in my code example: oldIndex & newIndex (which i get from sortable.js). So the object entry on oldIndex should be moved to newIndex (in the object). And the output of that function should be an object with the new order.
MarkBoots
MarkBoots12mo ago
Ah okay, now your question makes a bit more sense for me. (not familiar with sortableJS.) So that means it's not really an order, but more of a reposition of a single entry in an object.
bob_merlin
bob_merlin12mo ago
exactly
MarkBoots
MarkBoots12mo ago
objects are strange when it comes to order. when constructing it takes the order of creation of the entries. Unfortunatly im not on my desktop now, so a bit hard to figure out
bob_merlin
bob_merlin12mo ago
the weird thing is, like i wrote in the code comments, that the groupIds are in correct order after the splicing. But as soon as .reduce() runs over the keys array, the resulting object still has the old order Hm...i even tried it like this with the same result:
function moveItem(oldIndex, newIndex) {
const groupIds = Object.keys(itemGroups)
const [id] = groupIds.splice(oldIndex, 1)

groupIds.splice(newIndex, 0, id)

const ordered = groupIds.reduce((acc, id) => {
acc.set(id, itemGroups[id])

return acc
}, new Map())

return Object.fromEntries(ordered)
}
function moveItem(oldIndex, newIndex) {
const groupIds = Object.keys(itemGroups)
const [id] = groupIds.splice(oldIndex, 1)

groupIds.splice(newIndex, 0, id)

const ordered = groupIds.reduce((acc, id) => {
acc.set(id, itemGroups[id])

return acc
}, new Map())

return Object.fromEntries(ordered)
}
The Map object holds key-value pairs and remembers the original insertion order of the keys. Any value (both objects and primitive values) may be used as either a key or a value.
The Map object holds key-value pairs and remembers the original insertion order of the keys. Any value (both objects and primitive values) may be used as either a key or a value.
Sadly it will get stored as JSON so i can only use Objects or Arrays. I would really prefer to store them as Record<number, number[]>. But maybe i have to go with something like:
const itemGroups = [
[1, [10, 11, 12]],
[2, [13, 14, 15]]
]
const itemGroups = [
[1, [10, 11, 12]],
[2, [13, 14, 15]]
]
MarkBoots
MarkBoots12mo ago
you could still do what @z- ::theProblemSolver:: suggested
[
{
key:1,
value:[10,11,12]
},
{
key:2,
value:[13,14,15]
}
]
[
{
key:1,
value:[10,11,12]
},
{
key:2,
value:[13,14,15]
}
]
easy accessable, easy to filter and order
bob_merlin
bob_merlin12mo ago
Oh sry, i didn't see that you edited your comment. Yeah maybe your suggested datastructure would be better. I didn't want to invest time refactoring the codebase so that the data structure works with the new sortable.js feature. But it seems like there is no other way around it 😄 yeah i think i have to do that 😔 Thanks for your help 🙂
MarkBoots
MarkBoots12mo ago
good luck!
Rägnar O'ock
Rägnar O'ock12mo ago
If you care about the order of the elements you should not be using objects as the order the keys are stored in is not guaranteed to be stable. If you need to order stuff use an array.
bob_merlin
bob_merlin12mo ago
@Rägnar O'ock yeah ive read a couple of stackoverflow issues about this topic and as far as i understood it, the order should be somewhat guaranteed since ES6. But seems like thats not enough for my use case. So i will change the structure
Zoë
Zoë12mo ago
Additionally you can store the order separately. I’m currently working on a small site and I have a bunch of data and the ability to reorder that data. Rather than moving around the data I just have an array of IDs that have had their orders changed and I map through that and then append the rest (the order array only tracks the top elements that have been moved. Because elements are appended to the bottom by the server) It also means that when one client makes a change all that needs to be sent to other clients is this array of IDs
Want results from more Discord servers?
Add your server