2

I have two arrays:

const array = [ 
[1, 7, 'AAA'], 
[2, 5, 'BBB'], 
[3, 2, 'CCC'], 
[4, 4, 'DDD'], 
[4, 9, 'EEE'], 
[4, 2, 'FFF'], 
[5, 8, 'GGG'], 
[6, 2, 'HHH']];

const names = [
[1, 'Joe'],
[2, 'Dave'],
[3, 'Mike'],
[4, 'Sandra'],
[5, 'Sue'],
[6, 'Mary']];

Based on the value in the first column, I want to sum the values in the array[1] and list the three-character letters. The result I'm trying to get is:

const names = [
[1, 'Joe',7,'AAA'],
[2, 'Dave',5,'BBB'],
[3, 'Mike',2,'CCC'],
[4, 'Sandra',15,'DDD, EEE, FFF'],
[5, 'Sue',8,'GGG'],
[6, 'Mary',2,'HHH']]

I'm not sure of the best approach, I'm fairly new to Javascript. What I've managed to do is get the right result when a value in array[0] isn't repeated, but I can't get a sum or list to work.

const counter     = (array,value) => array.filter((v) => (v === value)).length;
const arrayCol    = (array,value) => array.map(v => v[value]);
const sum         = (prevVal, curVal) => prevVal + curVal;

names.forEach ((p,e) => {
array.forEach ((v,x) => (counter(arrayCol(array,0),v[0])===1) ?
(v[0]===p[0]) && names[e].push(v[1],v[2]) : 
(v[0]===p[0]) && names[e].push(array.reduce(sum,0)) );
});

console.log(names);

I'm sure the answer has to do with map or filter but not sure how... any pointers appreciated. Thank you

EDIT: All three answers below (from Michael Haddad, Nina Scholz, and testing_22) work and are interesting.

TopMarx
  • 77
  • 8

3 Answers3

1

You could collect all data for each group and then map the result in order of the names array.

const
    array = [[1, 7, 'AAA'], [2, 5, 'BBB'], [3, 2, 'CCC'], [4, 4, 'DDD'], [4, 9, 'EEE'], [4, 2, 'FFF'], [5, 8, 'GGG'], [6, 2, 'HHH']],
    names = [[1, 'Joe'], [2, 'Dave'], [3, 'Mike'], [4, 'Sandra'], [5, 'Sue'], [6, 'Mary']],
    groups = array.reduce((r, [id, value, code]) => {
        r[id] ??= [0, ''];
        r[id][0] += value;
        r[id][1] += (r[id][1] && ', ') + code;
        return r;
    }, {}),
    result = names.map(a => [...a, ...groups[a[0]]]);

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
  • Thank you @Nina-Scholz I was actually reading one of your other [answers](https://stackoverflow.com/questions/55258925/how-to-make-a-list-of-partial-sums-using-foreach/55259065#55259065) earlier when I was trying to figure this out. I hadn't considered that the currentValue in reduce could be an array, that's really nice. I'm having some trouble with this though, I'm using Google Apps Script, which I think is basically Javascript, but the first error I get when I copy/paste is that it doesn't like the `=` sign in `??= [0]`, it's an unexpected token. I'm sorry if I'm confusing things. – TopMarx Nov 11 '21 at 19:35
  • you could replace `r[id] ??= [0]` with `r[id] = r[id] || [0]` – Nina Scholz Nov 11 '21 at 19:36
  • Thank you, that works, and makes sense after reading up on the use of [logical OR](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_OR), very nice. One final alteration, if possible, to keep the dimensions of the array the same. I've tried `result = names.map(a => [...a, groups[a[0]].join(', ')]);` but I just want to join the codes (when there's more than one) and keep the values in their column. Should I create another `groups` but just for the values and then `map` afterwards? thank you – TopMarx Nov 11 '21 at 20:55
  • please see edit. – Nina Scholz Nov 11 '21 at 21:01
  • Thank you! It's interesting to see you work, changing to `= [0, '']` and `r[id][1] += (r[id][1] && ', ') + code;` for my own understanding (after reading [Logical AND](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_AND#description)) is it saying if there's more than one value then return `', '` if not just return `r[id][1]`? and after add the code. And finally, how you write your variables without the `;` at the end of each line, is that for a performance benefit? Thank you. – TopMarx Nov 11 '21 at 21:39
  • the comma is for listing variable in `const` statement. – Nina Scholz Nov 11 '21 at 21:44
1

You can use a combination of map and reduce, as in:

const array = [[1, 7, 'AAA'], [2, 5, 'BBB'], [3, 2, 'CCC'],[4, 4, 'DDD'], [4, 9, 'EEE'], [4, 2, 'FFF'], [5, 8, 'GGG'], [6, 2, 'HHH']];
const names = [[1, 'Joe'],[2, 'Dave'],[3, 'Mike'],[4, 'Sandra'],[5, 'Sue'],[6, 'Mary']];

const result = names.map(([id, name]) => {
    let vals = [];
    let sum = array.reduce((acc, [idx, number, XXX]) => 
      (idx === id ? (vals.push(XXX), number) : 0) + acc, 0);
    return [
      id, 
      name,
      sum, 
      vals.join(", ")
    ]
})

console.log(result)
testing_22
  • 2,340
  • 1
  • 12
  • 28
  • Thank you @testing_22, this is nearly the right result, only that when there is more than one id, to join the codes together so that the dimensions of the array remain the same. I've been playing around with [join()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/join) but can't quite get it to work. And I need to learn about the `...` – TopMarx Nov 11 '21 at 19:25
  • Oh, sure! I didn't realize it. Since you want to join the array of codes together then you can use `join()` instead of the [spread operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax#spread_in_array_literals) – testing_22 Nov 11 '21 at 19:57
  • 1
    Ah perfect! I was close with my testing with join() but not quite, thank you for fixing. And thank you for the link, I need to read up on the spread operator. All the best – TopMarx Nov 11 '21 at 20:09
1

A basic approach could be:

const array = [[1, 7, 'AAA'], [2, 5, 'BBB'], [3, 2, 'CCC'], [4, 4, 'DDD'], [4, 9, 'EEE'], [4, 2, 'FFF'], [5, 8, 'GGG'], [6, 2, 'HHH']];
const names = [[1, 'Joe'], [2, 'Dave'], [3, 'Mike'], [4, 'Sandra'], [5, 'Sue'], [6, 'Mary']];

let result = [];
for (let name of names) {
    let newValue = [...name, 0];
    let matchingItems = array.filter(i => i[0] === name[0]);
    let strings = []; // for lack of a better name...

    for (let item of matchingItems) {
        newValue[2] += item[1];
        strings.push(item[2]);
    }
    newValue.push(strings.join(", "));

    result.push(newValue);
}

console.log(result);

You could also implement the joining logic yourself (I actually prefer this version for readability reasons):

   const array = [[1, 7, 'AAA'], [2, 5, 'BBB'], [3, 2, 'CCC'], [4, 4, 'DDD'], [4, 9, 'EEE'], [4, 2, 'FFF'], [5, 8, 'GGG'], [6, 2, 'HHH']];
const names = [[1, 'Joe'], [2, 'Dave'], [3, 'Mike'], [4, 'Sandra'], [5, 'Sue'], [6, 'Mary']];

let result = [];
for (let name of names) {
    let newValue = [...name, 0, ""];
    let matchingItems = array.filter(i => i[0] === name[0]);

    for (let item of matchingItems) {
        newValue[2] += item[1];
        newValue[3] += newValue[3] === "" ? item[2] : `, ${item[2]}`;
    }

    result.push(newValue);
}

console.log(result);
Michael Haddad
  • 4,085
  • 7
  • 42
  • 82
  • Thank you @Michael-Haddad really interesting, I'd not seen `item of` used before but it makes sense, and summing the values with `newValue[2] += item[1]` is clear. The only trouble I'm still having is the dimensions of the array increasing from 4 columns to 6 when there is more than one code `newValue = newValue.concat(item.slice(2));` I'm not trying to join `'DDD', 'EEE', 'FFF'` together into single column – TopMarx Nov 11 '21 at 20:05
  • @TopMarx - Please consider the edits to the answer. I believe it answers the questions better now, and is also more readable. – Michael Haddad Nov 11 '21 at 20:34
  • 1
    Thank you again @Michael-Haddad yes that makes sense: create an array ('strings') to push values to within the loop and then join() after. It works perfectly. And just to help me understand the spread operator, I changed `[...name, 0]` to `name.slice().concat(0)` essentially the spread operator just makes the script easier to read otherwise it's the same? thank you – TopMarx Nov 11 '21 at 21:11
  • Calling `slice` with no arguments will just create a copy of `name`, so you do not need it. `[...name, 0]`, `name.concat(0)` and `name.slice().concat(0)` will have the same effect. For me, the former is the more readable version but it's for you to make the choice. – Michael Haddad Nov 11 '21 at 21:23
  • @TopMarx - I have added another version, in which you don't need the `strings` array and the `join` function. – Michael Haddad Nov 11 '21 at 21:30
  • Really interesting, thank you. Is the `${}` used to put a variable inside of a string? I tried with `', '+item[2]` instead and it also worked. I guess it's again just preference? – TopMarx Nov 11 '21 at 22:06
  • [Yes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals), but you could also do it the simple way: `", " + item[2]`. It's a matter of preference. – Michael Haddad Nov 11 '21 at 22:09