2
\$\begingroup\$

When building objects using reduce, I often have crappy code like this:

function mapArticlesByTagAndId(articles) {
    return articles.reduce((collector = {}, article) => {
        const { tag, id } = article;
        if(!collector[tag]) collector[tag] = {};

        collector[tag][id] = article;
        return collector;
    })
}

I would love to one line this, but I can't figure out a way to do it. If only assigning a property to undefined created an object =P [Obviously this would cause different problems haha]

\$\endgroup\$
  • 1
    \$\begingroup\$ could you please post the complete code? this one is missing the second parameter of the reduce(). \$\endgroup\$ – Igor Soloydenko Sep 8 '17 at 16:17
  • 2
    \$\begingroup\$ More specifically "code like this" is not good enough for Code Review. Please post real code that accomplishes a stated task, as per the requirements in the help center and How to Ask. \$\endgroup\$ – 200_success Sep 8 '17 at 16:58
  • \$\begingroup\$ 200, this is real code. The context could be literally anything and it wouldn't change how one would structure this code. I could rename myArr to articles, then as far as anyone knows it's real code. The only difference in context would be performance, which I'm not picky about. \$\endgroup\$ – Jason McCarrell Sep 11 '17 at 15:54
  • 1
    \$\begingroup\$ As @IgorSoloydenko says, at least provide a full function call. \$\endgroup\$ – 200_success Sep 11 '17 at 16:22
2
\$\begingroup\$

Here's one variant which gets rid of the explicit if at the cost of potential useless reassignment. This, however, does not take away the necessity of ensuring that the k key has a collection object in place...

const result =
  myArr.reduce((collector, { k, j, v }) => {
    collector[k] = collector[k] || [];
    collector[k][j] = v;
    return collector;
  }, {});

Assuming, ES6 syntax is fine with you, here's another variant. Please notice that it the spread operator (... will result in copying the ...collector[k] item-by-item). I.e. this is less performant and may be an issue for large objects.

const result =
  myArr.reduce((collector, { k, j, v }) => {
   collector[k] = collector[k] ? [...collector[k], v] : [ v ];
   return collector;
  }, {});

which may be compacted further:

const result =
  myArr.reduce((collector, { k, j, v }) =>
    (collector[k] = collector[k] ? [...collector[k], v] : [ v ], collector),
    {});

Update 1

const result =
  myArr.reduce((collector, { k, j, v }) =>
    (collector[k] = !collector[k] ? [v] : (collector[k].push(v), collector[k])),
    {});
\$\endgroup\$
  • \$\begingroup\$ Thanks for all the options! These are options I already knew of, although truth be told I think was intrinsically avoiding the third one, because of all the newly created objects, even though it would technically create a one line reduce function, which is what I was ideally looking for. If there was a clean way to do the last option, without create a new object during each iteration, that would be ideal, however I worry that may not be possible. \$\endgroup\$ – Jason McCarrell Sep 11 '17 at 15:52
  • \$\begingroup\$ @user1272 I'll take a look into it more, at the moment still unsure whether I'll be able to improve it our not. \$\endgroup\$ – Igor Soloydenko Sep 11 '17 at 15:54
  • \$\begingroup\$ @JasonMcCarrell what do you think about the updated version? I reversed the condition to be !collector[k] for readability. (collector[k].push(v), collector[k]) will push a new value into the array and return the array itself, the JS's comma operator is handy here: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… We can not omit the ,collector[k] part because the previous expression (.push()) returns the length of the array after the insertion, rather than the array itself \$\endgroup\$ – Igor Soloydenko Sep 11 '17 at 19:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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