Reputation: 15698
I am interested in understanding what is happening inside this algorithm. Let's consider the following dataset:
const data = [
{
emp_code: "a001",
company_code: "company_a",
name: "abx",
details: [],
details_dtypes: []
},
{
emp_code: "b002",
company_code: "company_b",
name: "xbz ",
details: [],
details_dtypes: []
},
{
emp_code: "a002",
company_code: "company_a",
name: "xbz ",
details: [],
details_dtypes: []
},
{
emp_code: "b003",
company_code: "company_b",
name: "xbz ",
details: [],
details_dtypes: []
}
];
Now, if I wanted to condense this data to an object {}
, where each key is a unique company_code
and its corresponding value is an array []
of emp_codes
for that company, I could do something like:
let result = data.reduce((r, c) => {
r[c.company_code] = [...(r[c.company_code] || []), c.emp_code];
return r;
}, {});
In the above, we explicitly return the final object, which seems very clear.
Now, I've recently discovered that you can shorten this up with some syntactic sugar like so:
let result = data.reduce(
(r, c) =>
(r[c.company_code] = [...(r[c.company_code] || []), c.emp_code]) && r,
{}
);
The results are identical, but I'm not sure how that's possible. My understanding of reduce()
is that there is an inner-loop that iterates over each item and allows you to apply certain logic to formulate your final result.
I see that they're using a &&
operator. My hypothesis is that the logic between the parenthesis ()
executes like a traditional reduce()
method and when complete, we simply return the result, hence && r
.
But something about that just seems wrong. Is the inner-loop still happening? If it is how are they able to isolate it like that without simply executing the condition after the first iteration, thus by returning r
. Are they returning r
for each iteration of the loop?
Here's a sandbox that shows both algorithms working: https://codesandbox.io/s/hooks-with-reduce-nested-data-goee1. Many thanks to your contributions :)
Upvotes: 2
Views: 436
Reputation: 628
Let's disassemble this line:
(r[c.company_code] = [...(r[c.company_code] || []), c.emp_code]) && r
Assignment in JS always return assigned element, and logical AND operator (&&
) follows rules:
expr1 && expr2
If expr1 can be converted to true, returns expr2
; else, returns expr1
.
So basically it's same as
if(r[c.company_code] = [...(r[c.company_code] || []), c.emp_code])
return r
And since arrays ([...(r[c.company_code] || []), c.emp_code]
) in JS are converted to true when parsed to boolean (same as objects, functions and symbols by the way), it always return r.
Upvotes: 1
Reputation: 24945
Problem here is brevity. When you reduce your code too much, you loose readability. That's the issue. Lets reverse the process and make your code more traditional.
let result = data.reduce(
(r, c) => (r[c.company_code] = [...(r[c.company_code] || []), c.emp_code]) && r,
{}
);
Line 1, 3 and 4 is fairly simple. Tricky part is line 2.
(r, c) => (r[c.company_code] = [...(r[c.company_code] || []), c.emp_code]) && r,
This is equivalent to:
function processData(accumulator, currentItem) {
if ( accumulator[currentItem.compony_code] === undefined ) {
accumulator[currentItem.compony_code] = [];
}
accumulator[currentItem.compony_code].push(currentItem.emp_code);
return accumulator;
}
Now coming to your hypothesis, about ()
, its a hack that creates an expression and allows you to do non-expression (assignment in this case) tasks.
Upvotes: 1
Reputation: 371019
With &&
, if all expressions involved are truthy, it evaluates to the value of the final expression - the one that comes after the &&
. So, if you have code like:
function foo() {
<some expression>;
return bar;
}
you could also write it like
function foo() {
return <some expression> && bar;
}
or, with the implicit return of an arrow function:
const foo = () => <some expression> && bar;
which is equivalent to
const foo = () => {
<some expression>;
return bar;
};
So yes, for your code, with && r
at the end of the arrow function, that means that r
is being implicitly returned on every iteration.
All that said, this is pretty much the opposite of syntactic sugar. This is the sort of thing you would often see if you run the code through a minifier, whose goal is to reduce amount of text in the Javascript as much as possible. But minified code is often very hard to read, and this is no exception, as your question shows. If using implicit return
requires the comma operator or exploiting &&
as evaluating to something other than a boolean, the code is probably too confusing to read easily, and explicit return
is probably the better choice.
You could also consider pushing to an existing array in the accumulator object, thereby avoiding having to create many unnecessary intermediate arrays which only get spreaded later:
let result = data.reduce((r, c) => {
if (!r[c.company_code]) {
r[c.company_code] = [];
}
r[c.company_code].push(c.emp_code);
return r;
}, {});
That's the code I would prefer. It's somewhat longer, but it's a lot easier to read when every statement/logical branch is on its own line.
Upvotes: 3