I help run the discord server Devcord and spend much of my time helping novice programmers with technical difficulties. Starting now, I'll be formalizing any help I give there into short blog posts here so the information isn't buried under a sea of conversations.
Here I had a user with a list of email addresses to be sorted by domain.
const emails = [
"alice@gmail.com"
"bob@gmail.com"
"carol@yahoo.ca"
"doug@hotmail.com"
"ellie@protonmail.com"
];
While there are many ways to tackle this problem, my preferred is the functional model using Array.prototype.reduce
const sortedEmails = emails.reduce((groups, email) => {
const splitString = email.split('@');
const account = splitString[0];
const domain = splitString[1];
if(!groups[domain]) groups[domain] = [];
groups[domain].push(account);
return groups;
}, {});
The reduce function iterates each element of the array and passes the return object to the next iteration. The accumulator
(named groups
here) is set initially as a {}
empty object.
For each email, we break it into variables for each the account and the domain. If our current domain isn't already one of our groups, initialize it as an empty array.
Then add the new account name to the group and return groups
to pass it into the next iteration.
{
"gmail.com": ["alice", "bob"],
"yahoo.ca": ["carol"],
"hotmail.com": ["doug"],
"protonmail.com": ["ellie"]
}
It should be noted that this code isn't completely suitable for production environments. While almost every email address has only one @
symbol and this will work for all of those, there are valid email addresses that have multiple. Parsing the entire specification of valid email addresses is outside the scope of this article.
Top comments (9)
Hi! Thank you for the article. Please, let me share my point.
I'm not feeling very confident with functional-like practices, because I started applying them not a long ago. But if I got it right, a callback function we pass to .reduce method should be pure and mutating of input arguments it's not a good practice.
I would love to suggest the following implementation:
Thank you. Sorry if I got you wrong.
The accumulator used in a reduce function is a temporary variable created by the reduce function. The only scope that has access to it is the current iteration of reduce, so creating a new variable each time is unnecessary.
If we were to mutate the
addresses
argument, we would run into the problems you're hinting at so that indeed is bad practice, but none of those issues exist with the reduce-accumulator.how do you combine [name] with [host] to become email?
Great article. It’s a great example of how to utilize
.reduce
but it also reminds me why I’d rather see a helper library like lodash or immutableJS since it’s shorter, more readable, and has already been tested.But seriously awesome job of proving the value of reduce (especially since it’s basically the cornerstone of functional programming) :)
I'm really not sure how lodash or immutable are shorter or more readable. Immutable just has its own wrapper method for its own classes, and Lodash's implementation is almost identical to JS.
It's better to use the native javascript method because the performance of that will improve over time with improvements to the runtime engines and development of javascript as a whole. Lodash's performance depends on the version of the library you are using at the time.
Forgive me for not being more clear in my first comment. I didn't mean that you should replace native
.reduce
with lodash's.reduce
. I meant that.groupBy
is a drop in replacement for your custom reduce.That's 3 lines instead of 8 and (to me) it's more scan-able/readable since I see
"groupBy"
and I immediately know that we're categorizing by email provider.Btw, I often use native
.reduce
. But when I can reach out to a method name that more clearly communicates my goals, then I do that since it's more in line with self-documenting code. Again, those are just my opinions. What are your thoughts?Oh yes -- I understand now.
I'm a strong believer in refactoring code for reusability, so if I needed to group more arrays by one of their fields I would absolutely adopt a specialized and well-named function that does that. In this case, Lodash is a perfectly acceptable alternative.
This looks like a good use case for array destructuring.
const [account, domain] = email.split('@')
Nice article.
Yes you're right — that's a better way to write that