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\$
4
  • 1
    \$\begingroup\$ could you please post the complete code? this one is missing the second parameter of the reduce(). \$\endgroup\$ Commented Sep 8, 2017 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\$ Commented Sep 8, 2017 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\$ Commented Sep 11, 2017 at 15:54
  • 1
    \$\begingroup\$ As @IgorSoloydenko says, at least provide a full function call. \$\endgroup\$ Commented Sep 11, 2017 at 16:22

1 Answer 1

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\$
3
  • \$\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\$ Commented Sep 11, 2017 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\$ Commented Sep 11, 2017 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\$ Commented Sep 11, 2017 at 19:14

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.