1
\$\begingroup\$

I have a working code that groups items by their type values. The types can be one or multiple, however they are always returned as an array.

To illustrate: if an item has types with values type1 and type2, it should be included in both type1 and type2 properties of the resulting grouped object.

Although the code works, I was wondering if it would be possible to rewrite it without using nested for loops, to make it more aligned with the principles of functional programming, ideally using reduce. Since the data sets are not too large, performance is a secondary consideration, but should be taken into account if possible.

The code is as follows. getTypes is added primarily to show how the types are extracted and ideally shouldn't be changed.

const items = [{
    name: 'First item',
    types: [{
        value: 'type1',
      },
      {
        value: 'type2',
      },
    ],
  },
  {
    name: 'Second item',
    types: [{
        value: 'type1',
      },
      {
        value: 'type3',
      },
    ],
  },
  {
    name: 'Third item',
    types: [{
        value: 'type1',
      },
      {
        value: 'type2',
      },
    ],
  },
];

const getTypes = item => item.types.map(type => type.value);

const groupedItems = {};

for (const item of items) {
  for (const type of getTypes(item)) {
    groupedItems[type] = [...(groupedItems[type] || []), item];
  }
}

console.log(JSON.stringify(groupedItems, null, 4));

Code output:

{
    "type1": [
        {
            "name": "First item",
            "types": [
                {
                    "value": "type1"
                },
                {
                    "value": "type2"
                }
            ]
        },
        {
            "name": "Second item",
            "types": [
                {
                    "value": "type1"
                },
                {
                    "value": "type3"
                }
            ]
        },
        {
            "name": "Third item",
            "types": [
                {
                    "value": "type1"
                },
                {
                    "value": "type2"
                }
            ]
        }
    ],
    "type2": [
        {
            "name": "First item",
            "types": [
                {
                    "value": "type1"
                },
                {
                    "value": "type2"
                }
            ]
        },
        {
            "name": "Third item",
            "types": [
                {
                    "value": "type1"
                },
                {
                    "value": "type2"
                }
            ]
        }
    ],
    "type3": [
        {
            "name": "Second item",
            "types": [
                {
                    "value": "type1"
                },
                {
                    "value": "type3"
                }
            ]
        }
    ]
}
\$\endgroup\$
2
  • \$\begingroup\$ Not quite sure why you want to change perfectly readable code to use reduce? JavaScript seems to lack "array comprehensions" ( developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… ) / "object comprehensions" (not sure about the latter term), so till it will get those using for loops looks perfect to me. \$\endgroup\$
    – Roman Susi
    Oct 12, 2019 at 11:10
  • \$\begingroup\$ @RomanSusi I was just wondering if it were possible to make this code more concise with reduce. \$\endgroup\$
    – Clarity
    Oct 13, 2019 at 10:46

1 Answer 1

2
\$\begingroup\$

Poor memory usage

Javascript has managed memory. That means it does all the hard work of allocating and releasing memory, which is great (managing memory manually is a real pain).

But this useful feature comes with a down sides...

  • Managed memory environments are inherently slow,
  • Managed memory environments encourage very poor memory usage patterns

Looking at your memory and CPU usage.

In your code you create a total of 9 arrays, keeping only 3 of them. (using the data example you provided)

Each time you add an item to a group you create a new array, that must be iterated over when you copy the existing group. groupedItems[type] = [...(groupedItems[type] || []), item]

There are only 6 type values yet you iterate over them 12 times. Once each with item.types.map(type => type.value); and then again with for (const type of getTypes(item))

For the 6 items you add to the 3 groups your code needed 16 iterations.

Less overhead

The example avoids copying the item.types array for each item and assigns a new array to new groups. If a group has been defined the item added is pushed rather than copy the whole array.

The resulting code only needs 6 iterations executing in half the time and using several times less memory

function groupTypes(items) {
    const groups = {};
    for (const item of items) {
        for (const {value: type} of item.types) {
            groups[type] ? groups[type].push(item) : groups[type] = [item];
        }
    }
    return groups;
}

Or slightly quicker by avoiding the copy of the type.value string to to the variable type

function groupTypes(items) {
    const grps = {};
    for (const item of items) {
        for (const type of item.types) {
            grps[type.value] ? grps[type.value].push(item) : grps[type.value] = [item];
        }
    }
    return grps ;
}
\$\endgroup\$
3
  • \$\begingroup\$ Thanks for the detailed explanation! Would the implementation change if we had no control over getTypes and just knew that it returns an array of type values? \$\endgroup\$
    – Clarity
    Oct 13, 2019 at 10:48
  • 1
    \$\begingroup\$ @Clarity In the first example you would replace the inner loop with for (const type of getTypes()) { If the function getTypes is inhouse you can suggest politely that such functions should be generators eg getTypes(item) { for(const {value} of item.types) { yield value } } saving the need to use a temp array to transport data. For more on generators developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… \$\endgroup\$
    – Blindman67
    Oct 13, 2019 at 11:32
  • \$\begingroup\$ Great, thanks, didn't know about performance benefits of generators in this case. \$\endgroup\$
    – Clarity
    Oct 14, 2019 at 6:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.