Solving a JavaScript Array Reduce Interview Question

Ғылым және технология

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

Пікірлер: 69

  • @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; }, {}); } ```

  • @LuisReyes-zs4uk
    @LuisReyes-zs4uk Жыл бұрын

    Nicely done. Learned a lot from this, thanks!

  • @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!

  • @IkraamDev
    @IkraamDev Жыл бұрын

    2 and half years ago I would be completely lost, now I find this very easy.

  • @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 😂

  • @bmehder
    @bmehder Жыл бұрын

    I love these JS challenges. Thank you. May I have another?

  • @WesBos

    @WesBos

    Жыл бұрын

    More to come!

  • @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!

  • @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

  • @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

  • @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; }

  • @omomer3506
    @omomer3506 Жыл бұрын

    Can you talk about how passing async to a map or filter function doesn't act how you would think?

  • @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);

  • @hardwired89
    @hardwired89 Жыл бұрын

    Thank you

  • @ThomazMartinez
    @ThomazMartinez Жыл бұрын

    What are you pressing on vscode to show all those types?

  • @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)

  • @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

  • @patcoston
    @patcoston Жыл бұрын

    10:20 could you have coded tallyArray[name][key] += val; // instead?

  • @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);

  • @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

  • @evgenyzhithere7956
    @evgenyzhithere7956 Жыл бұрын

    ArraysOfData auto takes two const arrays ?

  • @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.

  • @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.

  • @VidarrKerr
    @VidarrKerr9 ай бұрын

    6:24 LOL.

  • @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}))`

  • @user-kr6lp7rm5y
    @user-kr6lp7rm5y Жыл бұрын

    Using typescript this is a real challenge

  • @BobbyBundlez

    @BobbyBundlez

    Жыл бұрын

    (...arraysOfData: any) LOL

  • @heidialameda-mcneal603
    @heidialameda-mcneal603 Жыл бұрын

    AGT 2023

  • @phillfreitas4183
    @phillfreitas4183 Жыл бұрын

    My values are not adding to the aggregation, this just bringing a string ;/

  • @Alturic
    @Alturic Жыл бұрын

    I hate empty values, like the bones, so I’d like those to be 0.

  • @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)

  • @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!

  • @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

  • @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.

  • @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.

  • @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.

  • @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; } }, []); }

  • @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 :)

Келесі