Solving a JavaScript Array Reduce Interview Question

แชร์
ฝัง

ความคิดเห็น • 69

  • @marcusaureo
    @marcusaureo ปีที่แล้ว +32

    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 ปีที่แล้ว +13

    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 ปีที่แล้ว +2

    Nicely done. Learned a lot from this, thanks!

  • @noctemcat_
    @noctemcat_ ปีที่แล้ว +7

    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

  • @bradb0t
    @bradb0t ปีที่แล้ว +4

    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  ปีที่แล้ว +4

      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 ปีที่แล้ว +1

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

  • @bmehder
    @bmehder ปีที่แล้ว +5

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

    • @WesBos
      @WesBos  ปีที่แล้ว +1

      More to come!

  • @omomer3506
    @omomer3506 ปีที่แล้ว

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

  • @carlosjimenez7197
    @carlosjimenez7197 ปีที่แล้ว +2

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

  • @QuangLe-hd2oc
    @QuangLe-hd2oc ปีที่แล้ว +4

    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 ปีที่แล้ว +1

      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

  • @ThomazMartinez
    @ThomazMartinez ปีที่แล้ว

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

  • @rtorcato
    @rtorcato ปีที่แล้ว +12

    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  ปีที่แล้ว +6

      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 ปีที่แล้ว +1

      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!

  • @evgenyzhithere7956
    @evgenyzhithere7956 ปีที่แล้ว

    ArraysOfData auto takes two const arrays ?

  • @patcoston
    @patcoston ปีที่แล้ว

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

  • @harshpatel7338
    @harshpatel7338 ปีที่แล้ว +1

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

  • @markhuot824
    @markhuot824 ปีที่แล้ว +2

    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 ปีที่แล้ว +2

    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 ปีที่แล้ว +1

      @@taylorsabbag6962 put your {id, ...rest} as the 2nd argument instead of obj

    • @taylorsabbag6962
      @taylorsabbag6962 ปีที่แล้ว

      @@kristun216 Sick, thanks

  • @hardwired89
    @hardwired89 ปีที่แล้ว

    Thank you

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

  • @phillfreitas4183
    @phillfreitas4183 ปีที่แล้ว

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

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

  • @plusquare
    @plusquare ปีที่แล้ว +5

    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 ปีที่แล้ว +1

      Ya I would also take the same approach... Initially creating an object and then through map manipulating the same object...

    • @YaohanChen
      @YaohanChen ปีที่แล้ว +2

      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 ปีที่แล้ว +4

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

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

  • @Alturic
    @Alturic ปีที่แล้ว

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

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

  • @user-kr6lp7rm5y
    @user-kr6lp7rm5y ปีที่แล้ว

    Using typescript this is a real challenge

    • @BobbyBundlez
      @BobbyBundlez ปีที่แล้ว

      (...arraysOfData: any) 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}))`

  • @VidarrKerr
    @VidarrKerr 10 หลายเดือนก่อน

    6:24 LOL.

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

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

  • @heidialameda-mcneal603
    @heidialameda-mcneal603 ปีที่แล้ว

    AGT 2023

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

  • @thefrey9588
    @thefrey9588 ปีที่แล้ว +4

    nice one.
    10:11
    why not `x += y` instead of `x = x+y`?

    • @WesBos
      @WesBos  ปีที่แล้ว +15

      because if I did that, you'd comment the opposite
      Just joking - thats a good improvement

    • @adrian333dev
      @adrian333dev ปีที่แล้ว +1

      @@WesBos 🤣🤣🤣🤣🤣🤣

    • @BobbyBundlez
      @BobbyBundlez ปีที่แล้ว

      @@WesBos LOL

  • @elmotareal
    @elmotareal ปีที่แล้ว +1

    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 ปีที่แล้ว +2

    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  ปีที่แล้ว +2

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