0

I am receiving the following array from an API response:

[ 
  {group: '1', , value: 'a'}
  {group: '1', , value: 'b'}
  {group: '2', , value: 'c'}
  {group: '2', , value: 'd'}
]  

I want to convert it to the following (want order of groups as received, values can be ordered in any way):

[
  {group: '1', values: ['b', 'a'] },
  {group: '2', values: ['c', 'd'] },
]

Which Javascript function will be the most efficient to convert it?

I am able to do this by:

let result = [];

data.reduce((groupNumber, input) => {
  if (!groupNumber[input.group]) {
     groupNumber[input.group] = {group: input.group, values: []};
     result.push(groupNumber[input.group]);
  }
  groupNumber[input.group].values.push(input);
  return groupNumber;
}, {});

Is reduce the correct function to be used here? Is there a more efficient approach?

Secondly, will reduce preserve the order in the result? If I am actually receiving the data with group 1 entries first, group 2 next, and so on, can I rely on result being ordered similarly?

Note that I only care about the order of the groups, not about the values inside the group.

user2684198
  • 812
  • 1
  • 10
  • 18
  • @CertainPerformance The title may be a little confusing. The example correctly explains what I want to achieve (basically, convert an array to an array of array) – user2684198 Nov 15 '19 at 07:21
  • Oh wait, I see it now, sorry – CertainPerformance Nov 15 '19 at 07:23
  • Check [this answer](https://stackoverflow.com/a/52853037/3082296) from the duplicate if you want to preserve the order – adiga Nov 15 '19 at 07:26
  • 1
    @adiga His current code actually does preserve the order, because he's pushing to a *separate* array inside the `.reduce` callback. – CertainPerformance Nov 15 '19 at 07:28
  • @CertainPerformance you are right. They are using accumulator as a mapper. Sorry OP – adiga Nov 15 '19 at 07:29
  • Could we no use a Map or a Set here? – mplungjan Nov 15 '19 at 07:30
  • @CertainPerformance should I reopen the question? It is POB / not reproducible since OP's code is fine / the duplicate has plenty of alternatives – adiga Nov 15 '19 at 07:31
  • My bad, I should have explained this. I do not care about the order inside the group. I only care that the group numbers are in the same order as received. – user2684198 Nov 15 '19 at 07:33
  • @adiga Yeah, half is moderately POB, but the other half is objectively answerable but trivial ("Yes."). I wouldn't object to someone reopening but wouldn't do it myself – CertainPerformance Nov 15 '19 at 07:38

2 Answers2

0

I would save my reply because we have a great explanation why your approach is great, because you have O(N) computational complexity (Thanks for the great comments to @CertainPerformance) and you iterate your array just one time.

Testing whether reduce method preserve order of object keys:

It looks like reduce method is not preserving order of keys. Here we see that keys are sorted in order how then iterated through source array myArray, however in Chrome browser this behavior is not reproducible(keys are sorted):

var myArray = [{letter: 'c'}, {letter:'e'}, {letter: 'e'}, {letter:'b'}, {letter: 'c'}, {letter:'a'}, {letter:'b'}, {letter:'a'}, {letter: 'd'}, {letter: 'd'}, {letter: 'd'}, {letter: 'd'}];
var myOrderedKeys = myArray.reduce((a, {letter}) => {
a[letter] = a[letter] || letter;
a[letter] = letter;

return a;
}, {})

console.log(myOrderedKeys);

Another example where we have more than one letter:

let notSimpleLetters = [{letter: 'ce'}, {letter:'ec'}, {letter: 'ec'}, {letter:'bw'}, {letter: 'ce'}, {letter:'aw'}, {letter:'ba'}, {letter:'aa'},
                    {letter: 'df'}, {letter: 'cb'}, {letter: 'dc'}, {letter: 'da'}];
let notSimpleKeys = notSimpleLetters.reduce((a, {letter}) => {
a[letter] = a[letter] || letter;
a[letter] = letter;

return a;
}, {})

console.log(notSimpleKeys);
StepUp
  • 36,391
  • 15
  • 88
  • 148
  • 3
    It doesn't answer either of the questions (of whether `.reduce` is appropriate, or whether it preserves the property order), *and* does not produce the right output for different orderings of `data`. – CertainPerformance Nov 15 '19 at 07:28
  • @CertainPerformance thank you for clarification. I've updated my answer. I would be really happy to get any notes. Thanks!:) – StepUp Nov 15 '19 at 08:13
  • 2
    OP's current code *does* actually preserve key order. Reducing into an object using numeric keys will not preserve the order *of the properties on the object*, but OP isn't iterating over the object later, so it's OK. (though, this is confusing, which is why I'd personally avoid `reduce` here) – CertainPerformance Nov 15 '19 at 08:18
  • 2
    Object property order *can* be relied on, if the properties are non-numeric. The specification does not say this *yet*, but it will soon, and all engines implement it anyway. – CertainPerformance Nov 15 '19 at 08:18
  • 2
    Well, in OP's code, at least the order of the `group` objects in the array is proper, but the `values` property does need to be fixed to be an array of strings rather than an array of objects – CertainPerformance Nov 15 '19 at 08:26
  • @CertainPerformance I am sorry, maybe it is a dumb question, however, in my view, `values: ['b', 'a']` is array of strings, not array of objects? – StepUp Nov 15 '19 at 08:30
  • 2
    Yep. OP's code is preserving the desired *order*, even over numeric keys, but his `value` properties are malformed. Your code fixes the `value` properties but doesn't preserve the `group` order because each `group` is numeric. – CertainPerformance Nov 15 '19 at 08:33
  • @CertainPerformance please, see my updated answer. now it outputs the desired result which is shown in OP's question. – StepUp Nov 15 '19 at 08:39
  • 2
    `.sort((a, b) => (a > b ? -1 : 1));` works if you know in advance whether the `group` in the data is in ascending or descending order, but that's probably not the case, and also won't work for, eg, an original order of `{ group: 3 }, { group: 1 }, { group: 2 }`. It also increases computational complexity to `O(n log n)` from `O(n)`. I'd prefer to stick to OP's current method (of pushing to a completely separate array, while also using an object for easy lookup) – CertainPerformance Nov 15 '19 at 08:46
0

Actually either by using reduce() or any other Array methods, the order will be always preserved as they will be executed from index 0 till the end of the array.

But reduce() is mainly used to accumulate the array elements into a single element, it's not the best method for doing such thing but it can be used as well.

Note that your actual code using reduce() isn't returning the right output.

In my opinion I think Array.map() method is better in this case, but it should be used in combination with Array.filter() to remove duplicate elements that will be kept with map():

var result = data.map(v => {
  v.values = data.filter(e => e.group == v.group).map(x => x.value);
  delete v.value;
  return v;
}).filter(x => !x.values.some(e => !e));

Demo:

let data = [{
  group: '1',
  value: 'a'
}, {
  group: '1',
  value: 'b'
}, {
  group: '2',
  value: 'c'
}, {
  group: '2',
  value: 'd'
}];


var result = data.map(v => {
  v.values = data.filter(e => e.group == v.group).map(x => x.value);
  delete v.value;
  return v;
}).filter(x => !x.values.some(e => !e));

console.log(result);
cнŝdk
  • 31,391
  • 7
  • 56
  • 78