Solving a JavaScript Array Reduce Interview Question

  Рет қаралды 22,746

Wes Bos

Wes Bos

Күн бұрын

Solving a JS reduce function someone sent me. Give it a shot yourself. Code: codepen.io/wes...

Пікірлер: 72
@marcusaureo
@marcusaureo Жыл бұрын
There is new sugar in JavaScript, you can use Logical OR assignment as tallyArray[name] ||= { } or the same thing with Nullish coalescing assignment (??=)
@drw2102
@drw2102 Жыл бұрын
Learned something today: (the nullish coalescing assignment) ``` function addItUp(...arrays) { return arrays.flat().reduce((tally, { name, ...points }) => { console.log(`Working on ${name}`); console.log(points); tally[name] ??= {}; Object.entries(points).forEach(([key, val]) => { tally[name][key] ??= 0; tally[name][key] += val; }); return tally; }, {}); } ```
@carlosjimenez7197
@carlosjimenez7197 Жыл бұрын
I've learned something new I'd like to share, Optional chaining with bracket notations (it's just like optional chaining with dots but I didn't know it existed) function addItUp(...arraysOfData) { const data = arraysOfData.flat(); const tally = data.reduce((added, { name, ...player }) => { for (const [key, value] of Object.entries(player)) { added = { ...added, [name]: { ...added?.[name], [key]: (added?.[name]?.[key] || 0) + value, }, }; } return added; }, {}); return tally; }
@harshpatel7338
@harshpatel7338 Жыл бұрын
this works preety well too function responseArr(...rest) { const data = rest.flat(); const returnData = data.reduce((prev,curr) => { const{name,...rest} = curr prev[curr.name] = {...rest} return prev }, {}); return returnData; }
@QuangLe-hd2oc
@QuangLe-hd2oc Жыл бұрын
if tallyArray[name] doesn't exist, just copy everything from item (clone); there is no need to check every properties of the item and copy to a new object
@mazthespaz1
@mazthespaz1 Жыл бұрын
since each original array has objects with a different number of keys than the other array, don't you have to loop thru them? if objects in first array have keys a,b,c and objects in second array have keys b,c,d then the final objects in the output array should have keys a,b,c,d
@noctemcat_
@noctemcat_ Жыл бұрын
The shortest I could make it. Sometimes it's fun to write the shortest function you can make. The general idea is that "points" is actually an object and that means that we can change its values and later read what we had changed. Now we just need to check the value in the talllyMap and add it to points if it exist. Also used map instead of forEach because it's shorter lol const addItUp2 = (...arraysOfData) => { return arraysOfData.flat().reduce((tallyMap, { name, ...points }) => { Object.keys(points).map(key => points[key] += tallyMap[name]?.[key] ?? 0); return {...tallyMap, [name]: points}; }, {}) }; Disclaimer: Don't actually do this outside of silly code challenges
@uchennaofoma4624
@uchennaofoma4624 Жыл бұрын
It's the disclaimer for me 😂
@yassinedownpourz
@yassinedownpourz Ай бұрын
const addItUp = (...data) => data.flat().reduce((result, { name, ...item }) => { result[name] ??= {}; for (const [key, value] of Object.entries(item)) { result[name][key] = (result[name][key] ?? 0) + value; } return result; }, {});
@bmehder
@bmehder Жыл бұрын
I love these JS challenges. Thank you. May I have another?
@WesBos
@WesBos Жыл бұрын
More to come!
@bradb0t
@bradb0t Жыл бұрын
Understanding reduce's initialValue parameter and the fact that reduce can return multiple types of your choosing was key to my understanding of its power and utility. Lots of array methods return arrays or boolean values or length values, but reduce can return anything you want.
@WesBos
@WesBos Жыл бұрын
Very true - its always just shown adding numbers, but in reality reduce is just a big pot, and you can cook anything in it!
@andriimykhavko7424
@andriimykhavko7424 Жыл бұрын
data.reduce(function(tallyArray, item)) - here tallyArray is an argument of function and when you use structure such as tallyArray[name] = tallyArray[name] || {} (or simmilar structure) - you change an argument of function directly. This is bad practise.
@rtorcato
@rtorcato Жыл бұрын
To get the unique index of name you can create a set from the arrays. Then from your set of unique index you can reduce over the array to get your value. Another way to remove name from an object is to set name to undefined this will remove it from the object without having to create a new variable using the spread destructing.
@WesBos
@WesBos Жыл бұрын
Oh that's a neat option - figure out all the keys upfront, and then unique them with a set. Then you don't have to check if it's the first time you found a name,
@irobot8297
@irobot8297 Жыл бұрын
That would be too easy, I like Wes's approach, it makes you think. And I think setting name to undefined would mutate the original array, that's a bad practice though!
@markhuot824
@markhuot824 Жыл бұрын
I shortened up the `tallyArray[name][key]` using the same `||` pattern you used earlier. Other than that it's exactly the same way I would have written it! function addItUp(...arraysOfData) { const data = arraysOfData.flat(); const tally = data.reduce(function(tallyArray, item) { // first check if this person is new const { name, ...points } = item; tallyArray[name] = tallyArray[name] || {}; // Loop over each of their properties and add them up Object.entries(points).forEach(([key, val]) => { tallyArray[name][key] = tallyArray[name][key] || 0; tallyArray[name][key] += val; }) return tallyArray; }, {}); return tally; } You could get really fun with the reduce and forEach returns and wrap them in nested destructuring but that would make it much less readable IMO.
@RRickkert
@RRickkert Жыл бұрын
If you like reducing the code, you could also use tallyArray[name][key] = (tallyArray[name][key] || 0) + val
@taylorsabbag6962
@taylorsabbag6962 Жыл бұрын
How I'd re-write it: const arraysOfObjectsReducer = (...arraysOfObjs) => { return arraysOfObjs.flat().reduce((acc, obj) => { const { id, ...rest } = obj acc[id] = acc[id] || {} Object.entries(rest).forEach(([key, value]) => { acc[id][key] = (acc[id][key] || 0) + value }) return acc }, {}) } I don't think this is any less readable. I've just used some more generic terms and simplified the consolidating of the values. I've also just returned the chained function calls on the object instead of declaring two variables and then returning the last one.
@WesBos
@WesBos Жыл бұрын
Nice work! Very succinct without being hard to parse
@kristun216
@kristun216 Жыл бұрын
You can take it one step further and desttructure within the reduce callback
@taylorsabbag6962
@taylorsabbag6962 Жыл бұрын
@@kristun216 How do you mean?
@kristun216
@kristun216 Жыл бұрын
@@taylorsabbag6962 put your {id, ...rest} as the 2nd argument instead of obj
@taylorsabbag6962
@taylorsabbag6962 Жыл бұрын
@@kristun216 Sick, thanks
@LuisReyes-zs4uk
@LuisReyes-zs4uk Жыл бұрын
Nicely done. Learned a lot from this, thanks!
@IkraamDev
@IkraamDev Жыл бұрын
2 and half years ago I would be completely lost, now I find this very easy.
@dimgbamichael5566
@dimgbamichael5566 Жыл бұрын
This challenge solution is awesome. You might as well solve it in a lesser line of code function addItUp(...arraysOfData) { const data = arraysOfData.flat(); const tally = data.reduce(function(tallyArray, item) { // first check if this person is new const { name, ...points } = item; tallyArray[name] ??= []; tallyArray[name].push(item) return tallyArray; }, {}); return tally; } const result = addItUp(arr1, arr2); console.table(result)
@phillfreitas4183
@phillfreitas4183 Жыл бұрын
My values are not adding to the aggregation, this just bringing a string ;/
@evgenyzhithere7956
@evgenyzhithere7956 Жыл бұрын
ArraysOfData auto takes two const arrays ?
@oloyang431
@oloyang431 Жыл бұрын
This is what I came up with. Bear in mind that I'm really new to programming. In the end you get an object with key value pairs of the name and the number of goals. const myArrays = [...arr1, ...arr2]; const playerGoals = myArrays.reduce((total, person) => { const name = person.name; const goals = person.goals; if (total[name] == null) { total[name] = goals } else { total[name] = total[name] + goals } return total }, {}) console.log(playerGoals);
@frugodwill
@frugodwill Жыл бұрын
function scoreTotal(...arrayOfScores) { const totalArrays = arrayOfScores.flat(); return totalArrays.reduce((acc, item) => { const itemExists = acc.some((tempArrItem) => { return item.name === tempArrItem.name; }); if (itemExists) { const prevInstance = acc.find((x) => x.name === item.name); const indexInTempArr = acc.findIndex((x) => x.name === item.name); let tempObj = { ...item }; for (const [key, value] of Object.entries(prevInstance)) { if (key != "name") { if (tempObj[key]) { tempObj[key] = tempObj[key] + prevInstance[key]; } else { tempObj[key] = prevInstance[key]; } } } acc.splice(indexInTempArr, 1, tempObj); return acc; } else { acc.push(item); return acc; } }, []); }
@kizhissery
@kizhissery Жыл бұрын
using map const arr1 = {a:1,b:5,c:6,d:7,e:1} const arr2 = {a:5,b:77,c:70} const arr3 = {d:6,e:7,f:700} const fun = (...data)=> data.flat() //console.log(fun(arr1,arr2)) const data = fun(arr1,arr2,arr3) const reduce = data.reduce((acc,cur)=>{ Object.entries(cur).map(s => acc[s[0]]? acc[s[0]] += s[1] : acc[s[0]] = s[1]) return acc },{}) console.log(reduce)
@christian-schubert
@christian-schubert Жыл бұрын
Well. Well well well. Tried that myself and it gave me a throbbing headache - seeing your much more elegant and overall just better approach clearly doesn't quite help that much either. At some point I just didn't care about nesting or code readability as a whole for that matter any more, just wanted to get it over and done with. Also, scrolling through the comments section REALLY makes me wonder where I swerved off the road like that... However, it works as intended. IT WORKS AS INTENDED! ...that's the most important part, right? RIGHT? Also expanded the functionality a bit, so everything with matching keys where the value is not of type number gets shoved into an array (except for the "name" property). All right, here we go, brace for impact (apologies for any nausea symptoms caused): const result = addItUp(arr1, arr2); function addItUp(...arrs) { const combinedArr = arrs.flat(); const mutatedArr = combinedArr.reduce((acc, cur) => { const curInAcc = acc.find(obj => obj.name === cur.name); if (!curInAcc) { acc.push(cur); } else { // cur in acc const indexOfcurInAcc = acc.indexOf(curInAcc); const keysInAcc = Object.keys(curInAcc); const keysCur = Object.keys(cur); for (const keyInAcc of keysInAcc) { // // if curInAcc[key] in cur[key], mutate it const matchingKey = keysCur.find(keyCur => keyInAcc === keyCur); if (matchingKey) { if (typeof curInAcc[matchingKey] === "number") { // type number acc[indexOfcurInAcc][matchingKey] = acc[indexOfcurInAcc][matchingKey] + cur[matchingKey]; } else { // not type number if (!Array.isArray(curInAcc[matchingKey])) { // notArray if (matchingKey !== "name") { // notName curInAcc[matchingKey] = [curInAcc[matchingKey], cur[matchingKey]]; } } else { // Array curInAcc[matchingKey].push(cur[matchingKey]); } } } } for (const keyCur of keysCur) { // if cur[key] not in curInAcc, insert it const matchingKey = keysInAcc.find(key => key === keyCur); if (!matchingKey) { acc[indexOfcurInAcc][keyCur] = cur[keyCur]; } } } return acc; }, []); return mutatedArr; } console.log(result);
@plusquare
@plusquare Жыл бұрын
Wouldn't recommend using an object as an initial value of a reducer function, since with every iteration the a new object is created. It's better to use a map for this and convert it to an object later
@WesBos
@WesBos Жыл бұрын
Good idea! We don’t reach for maps often enough
@arnabchatterjee8556
@arnabchatterjee8556 Жыл бұрын
Ya I would also take the same approach... Initially creating an object and then through map manipulating the same object...
@YaohanChen
@YaohanChen Жыл бұрын
A new object is not created each iteration because objects are passed by reference (and a Map is just a specific kind of object). However I think it's actually less appropriate to modify the object in reduce, because the point of reduce is that you *return* the accumulated value to be used for the next iteration. To repeatedly modify an object in place, you could just use forEach or a loop instead. So at 5:51 instead of "tallyArray[item.name] = value", I would use "return {...tallyArray, [item.name]: value}".
@plusquare
@plusquare Жыл бұрын
@@YaohanChen you were twice as clever by half maps are optimised for getting,setting and iteration. they use less memory and are faster than objects objects should be known at author time. and treating them like python dictionaries is misuse objects are passed by reference, however in your code example returning a destructured object is creating a brand new object in each iteration since destructuring new properties into objects works the same as Object.assign
@brennenherbruck8740
@brennenherbruck8740 Жыл бұрын
If I pass in an array and want the items summed up, it should return the same data structure (array). I'd return this: `const tallyArray = Object.entries(tally).map(([key, val]) => ({name: key, ...val}))`
@patcoston
@patcoston Жыл бұрын
10:20 could you have coded tallyArray[name][key] += val; // instead?
@omomer3506
@omomer3506 Жыл бұрын
Can you talk about how passing async to a map or filter function doesn't act how you would think?
@ThomazMartinez
@ThomazMartinez Жыл бұрын
What are you pressing on vscode to show all those types?
@Alturic
@Alturic Жыл бұрын
I hate empty values, like the bones, so I’d like those to be 0.
@VidarrKerr
@VidarrKerr Жыл бұрын
6:24 LOL.
@yaserhasan6004
@yaserhasan6004 Жыл бұрын
Here is a solution done using only regular for loops I personally find it more readable and performant function addItUp(...data) { data = data.flat(); const formattedData = {}; for (let person of data) { const name = person.name; delete person.name; let propertiesSum = 0; for (let key in person) { propertiesSum += person[key]; } if (formattedData[name] === undefined) { formattedData[name] = propertiesSum; } else { formattedData[name] += propertiesSum; } } return formattedData; }
@minimovzEt
@minimovzEt Жыл бұрын
There's a high probability that the performance gain comes from not using the rest operator, rest operator has a heavy overhead, it can add up a lot of milliseconds just for one execution, if you are looping on an array of 500 items, it can add up to 100ms just for the rest operator calls (on a laptop throttled down cpu for example), imagine processing a table with rest operator on a big table like 10000 items.
@heidialameda-mcneal603
@heidialameda-mcneal603 Жыл бұрын
AGT 2023
@jdsoteldo
@jdsoteldo Жыл бұрын
i would imagine in a real life setting where other devs would be working on this, the method wouldn’t be doing so much, it’d better to break it down to smaller functions
@minimovzEt
@minimovzEt Жыл бұрын
I had to do something that looked like this last month and it was much more complex, after i was done with the code, sonar asked me to break it up in multiple functions because of complexity, but after i was done with it, turned out it was much harder to read the code because it made into a spaghetti, some times breaking up code in functions is not the best, it's better for readability if you can read your code like a book instead of needing to jump around the entire code to see what it is doing.
@cyphermediaschool
@cyphermediaschool Жыл бұрын
Hey , can you create a React Js playlist
@BobbyBundlez
@BobbyBundlez Жыл бұрын
the only line I don't understand is 'total[name] = total[name] || {}'
@BobbyBundlez
@BobbyBundlez Жыл бұрын
why an empty object can someone explain?
@minimovzEt
@minimovzEt Жыл бұрын
@@BobbyBundlez if for example total['jim bob'] is not yet declared (ie: created), it will be an undefined value, you can't operate on an undefined value like total['jim bob'].points = 0 because it's not an object yet, that line is basically saying "Hey, if jim bob doesn't exist yet, create an object in it's place so i can change it's properties"
@BobbyBundlez
@BobbyBundlez Жыл бұрын
@@minimovzEt OHHH I get it now. I also at first thought the final objects looked like this: {name: 'john', score: 19} NOW i see that the final result is an object of objects lol.... so -----> {john: { score:123} } etc. so we are first making an object with the name and then an empty one for each person. so {john: {} } this makes sense. Thank you!
@hardwired66
@hardwired66 Жыл бұрын
Thank you
@ГенаПетров-н5ы
@ГенаПетров-н5ы Жыл бұрын
Using typescript this is a real challenge
@BobbyBundlez
@BobbyBundlez Жыл бұрын
(...arraysOfData: any) LOL
@elmotareal
@elmotareal Жыл бұрын
Hmm, i wonder what chat gpt has to say about this?
@ThomasGiles
@ThomasGiles Жыл бұрын
Oh wow, this seems a bit overblown to me. Doesn’t seem like anything is actually “reducing.” Reducing to that tally array isn’t actually useful is it?
@taylorsabbag6962
@taylorsabbag6962 Жыл бұрын
We've reduced two (or more, potentially many) arrays of objects into a single array. There might be a more efficient way of having tallied this information in the first place so that multiple arrays of objects were never created; however, if you run into this situation, this would indeed be an ideal way to solve the problem. It's like if your boss asked you to do something repetitive in Excel. As a programmer, you could write a script to do it for you. There are more efficient ways of tallying that information in the first place so that the script doesn't need to be created, but that won't stop your boss from wanting his information presented to him in Excel.
@thefrey9588
@thefrey9588 Жыл бұрын
nice one. 10:11 why not `x += y` instead of `x = x+y`?
@WesBos
@WesBos Жыл бұрын
because if I did that, you'd comment the opposite Just joking - thats a good improvement
@adrian333dev
@adrian333dev Жыл бұрын
@@WesBos 🤣🤣🤣🤣🤣🤣
@BobbyBundlez
@BobbyBundlez Жыл бұрын
@@WesBos LOL
@magicjuand
@magicjuand Жыл бұрын
there is no use case for reduce except to look smart. just use a loop, it's easier to read.
@magicjuand
@magicjuand Жыл бұрын
@@taylorsabbag6962 i don't find that reduce is particularly concise or parsable. the usage is so awkward, what with the inputs to the reduce function that everyone always forgets, and then the initial accumulator value comes at the end, after all the logic of the function? moreover, it just doesn't actually give you anything. here's my implementation of the above with two loops: ``` function addItUp(...arraysOfData){ const data = arraysOfData.flat(); const returnObj = {}; for(const {name, ...props} of data){ if(typeof name === 'undefined') continue; if(typeof returnObj[name] === 'undefined') returnObj[name] = {}; const returnItem = returnObj[name]; for(let [key, val] of Object.entries(props)){ if(typeof val !== 'number') continue; if(typeof returnItem[key] === 'undefined') returnItem[key] = 0; returnItem[key] += val; } } return returnObj; } ``` this is more concise, correct, performant and readable. if i'm interviewing someone, this is the kind of code i want to see. there's no tricks, you don't have to remember the order of arguments to the `reduce` callback and every block follows a simple format: set up pre-conditions and then do work. the cognitive load required is very low and i know this won't be a future headache for anyone.
@pointlessdeveloper
@pointlessdeveloper Жыл бұрын
/** * * @param {Number | undefined} augend * @param {Number | undefined} addend * @returns {Number} The sum of both parameters */ function safeSum(augend, addend) { return (augend ?? 0) + (addend ?? 0); } function addItUp(...arrays) { const data = arrays.flat(); console.log(data); return data.reduce(function (total, item) { const { name, ...stats } = item; if (total?.[name]) { return { ...total, [name]: Object.keys({ ...stats, ...total?.[name] }).reduce(function ( tally, key ) { return { ...tally, [key]: safeSum(total?.[name][key], tally[key]), }; }, stats), }; } return { ...total, [name]: stats }; }, {}); }
@cwnhaernjqw
@cwnhaernjqw Жыл бұрын
Mine: function addItUp(...arraysOfData) { const data = arraysOfData.flat() return data.reduce((prev, next) => { const matchIndex = prev.findIndex(i => i.name === next.name) if (matchIndex === -1) { prev.push(next) } else { const match = prev[matchIndex] Object.entries(next).forEach(([key, value]) => { if (match[key] && match !== 'name') { match[key] = match[key] + value } else { match[key] = value } }) } return prev }, []) }
@WesBos
@WesBos Жыл бұрын
impressively fast! your match !== 'name' should be key !== 'name', but a great solve :)
@cwnhaernjqw
@cwnhaernjqw Жыл бұрын
@@WesBos True, was a typo when pasting it over here. Have a nice rest of the week :)
Reduce: 10 Different Examples. JavaScript Fundamentals.
26:57
Leigh Halliday
Рет қаралды 28 М.
This Algorithm is 1,606,240% FASTER
13:31
ThePrimeagen
Рет қаралды 833 М.
The selfish The Joker was taught a lesson by Officer Rabbit. #funny #supersiblings
00:12
GIANT Gummy Worm Pt.6 #shorts
00:46
Mr DegrEE
Рет қаралды 101 МЛН
Why did OpenAI move from Next.js to Remix?
10:41
Wes Bos
Рет қаралды 142 М.
Reduce это просто. JavaScript
17:11
WebDev с нуля. Канал Алекса Лущенко
Рет қаралды 61 М.
JavaScript Array Reduce Method Practice in 5 Minutes
8:02
James Q Quick
Рет қаралды 48 М.
reduce Method | JavaScript Array Methods | Beginners tutorial
16:08
Code Explained
Рет қаралды 1,6 М.
5 JavaScript Concepts You HAVE TO KNOW
9:38
James Q Quick
Рет қаралды 1,4 МЛН
A subscriber was asked these interview questions for a junior role
44:35
Google Frontend Interview With A Frontend Expert
47:59
Clément Mihailescu
Рет қаралды 1 МЛН
Killing ReactJS - A Guide For Hopeful Frameworks
8:47
Theo - t3․gg
Рет қаралды 50 М.
Map, Filter & Reduce EXPLAINED in JavaScript - It's EASY!
11:23