4

I am trying to combine two Javascript arrays into one array with straight Javascript.

I am trying to accomplish exactly what has been asked in the below 2 questions. However, my data has several points that need to be combine rather than a single item; and the arrays have a common element between them that is exactly the same.

Here are the other questions:

  1. Merge two arrays matching an id
  2. JavaScript merging objects by id

Here is my code (which is from the last answer provided by the first question listed above)(but is obviously wrong):

let arr1 = [
            { route: 'x1' },
            { route: 'x2' },
            { route: 'x3' },
            { route: 'x4' },
            { route: 'x5' }
        ]


        let arr2 = [
            { pattern: 'y1', route: 'x1' },
            { pattern: 'y2', route: 'x1' },
            { pattern: 'y3', route: 'x2' },
            { pattern: 'y4', route: 'x2' },
            { pattern: 'y5', route: 'x3' },
            { pattern: 'y6', route: 'x3' },
            { pattern: 'y7', route: 'x4' },
            { pattern: 'y8', route: 'x4' },
            { pattern: 'y9', route: 'x5' },
            { pattern: 'y10', route: 'x5' }
        ]

        let finalArray2 = [];
        arr2.forEach(member => {
            finalArray2.push(Object.assign({}, member,
                { route: arr1.find(m => m.route === member.route).route }
            ))
        });

        console.log(finalArray2);

I really need results to look like the following:

let arr3 = [
    { route: 'x1', pattern: ['y1','y2'] },
    { route: 'x2', pattern: ['y3','y4'] },
    { route: 'x3', pattern: ['y5','y6'] },
    { route: 'x4', pattern: ['y7','y8'] },
    { route: 'x5', pattern: ['y9','y10'] }
]

So that it can be presented in a table like the following:

<style type="text/css">
.tg  {border-collapse:collapse;border-spacing:0;}
.tg td{font-family:Arial, sans-serif;font-size:14px;padding:10px 5px;border-style:solid;border-width:1px;overflow:hidden;word-break:normal;}
.tg th{font-family:Arial, sans-serif;font-size:14px;font-weight:normal;padding:10px 5px;border-style:solid;border-width:1px;overflow:hidden;word-break:normal;}
.tg .tg-yw4l{vertical-align:top}
</style>
<table class="tg">
  <tr>
    <th class="tg-yw4l">ROUTE</th>
    <th class="tg-yw4l">PATTERN(s)</th>
  </tr>
  <tr>
    <td class="tg-yw4l">x1</td>
    <td class="tg-yw4l">y1, y2</td>
  </tr>
  <tr>
    <td class="tg-yw4l">x2</td>
    <td class="tg-yw4l">y3, y4</td>
  </tr>
  <tr>
    <td class="tg-yw4l">x3</td>
    <td class="tg-yw4l">y5, y6</td>
  </tr>
  <tr>
    <td class="tg-yw4l">x4</td>
    <td class="tg-yw4l">y7, y8</td>
  </tr>
  <tr>
    <td class="tg-yw4l">x5</td>
    <td class="tg-yw4l">y9, y10</td>
  </tr>
</table>
Andrew Petersen
  • 163
  • 1
  • 9

4 Answers4

1

You could take a Map for keeping a reference to the target object.

var array1 = [{ route: 'x1' }, { route: 'x2' }, { route: 'x3' }, { route: 'x4' }, { route: 'x5' }],
    array2 = [{ pattern: 'y1', route: 'x1' }, { pattern: 'y2', route: 'x1' }, { pattern: 'y3', route: 'x2' }, { pattern: 'y4', route: 'x2' }, { pattern: 'y5', route: 'x3' }, { pattern: 'y6', route: 'x3' }, { pattern: 'y7', route: 'x4' }, { pattern: 'y8', route: 'x4' }, { pattern: 'y9', route: 'x5' }, { pattern: 'y10', route: 'x5' }],
    routes = new Map,
    result = array1.map(o => (routes.set(o.route, {}), Object.assign(routes.get(o.route), o, { pattern: [] })));

array2.forEach(o => routes.get(o.route).pattern.push(o.pattern));

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
  • My answer fails to include items from array1 that aren't represented at all in array2. This answer handles that (+1) – Faust Dec 03 '17 at 21:58
0

You could try this -

const arr2 = [
    { pattern: 'y1', route: 'x1' },
    { pattern: 'y2', route: 'x1' },
    { pattern: 'y3', route: 'x2' },
    { pattern: 'y4', route: 'x2' },
    { pattern: 'y5', route: 'x3' },
    { pattern: 'y6', route: 'x3' },
    { pattern: 'y7', route: 'x4' },
    { pattern: 'y8', route: 'x4' },
    { pattern: 'y9', route: 'x5' },
    { pattern: 'y10', route: 'x5' }
]

const result = arr2.reduce((acc, val) => {
  const {pattern, route} = val;
  const o = acc.find(v => v.route === route);
  if (o) {
    o.pattern.push(pattern);
  } else {
    acc.push({route, pattern: [pattern]});
  }
  return acc;
}, []);

console.log(result);

If you only need arr1 routes, add a filter to the above result.

let arr1 = [
   { route: 'x1' },
   { route: 'x2' },
   { route: 'x3' },
   { route: 'x4' },
   { route: 'x5' }
]

const filteredResult = 
   result.filter(r => arr1.findIndex(o => o.route === r.route) > -1);
console.log(filteredResult);
Yasser Hussain
  • 854
  • 7
  • 21
  • Using `find` inside of each loop makes this O(n^2). An O(n) solution is pretty easily done with a map (see the other solutions). – Faust Dec 03 '17 at 21:50
0

Reduce the 2nd array to a new one. You need to build a map of the routes as you go to do this, but you can still do it in one pass:

    let map = {};
    let arr3 = arr2.reduce((newArray, item) => {
        if(!map[item.route]){
            map[item.route] = { route: item.route, pattern: [item.pattern] }
            newArray.push(map[item.route]);
        }
        else{
            map[item.route].pattern.push(item.pattern);
        }
        return newArray;
    }, []);
Faust
  • 15,130
  • 9
  • 54
  • 111
0

I would also suggest the use of a Map.

Use the Map constructor to create all route keys with the target object structure as values (but empty pattern arrays), and populate those arrays. Finally extract the map's values to get the result:

const arr1 = [{ route: 'x1' }, { route: 'x2' }, { route: 'x3' }, { route: 'x4' }, { route: 'x5' }],
      arr2 = [{ pattern: 'y1', route: 'x1' }, { pattern: 'y2', route: 'x1' }, { pattern: 'y3', route: 'x2' }, { pattern: 'y4', route: 'x2' }, { pattern: 'y5', route: 'x3' }, { pattern: 'y6', route: 'x3' }, { pattern: 'y7', route: 'x4' }, { pattern: 'y8', route: 'x4' }, { pattern: 'y9', route: 'x5' }, { pattern: 'y10', route: 'x5' }];

const map = new Map(arr2.map( ({route}) => [route, { route, pattern: [] }] ));
for (const o of arr2) map.get(o.route).pattern.push(o.pattern);
const result = [...map.values()];

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

If the only purpose is to populate the HTML table, then I would suggest this:

const arr1 = [{ route: 'x1' }, { route: 'x2' }, { route: 'x3' }, { route: 'x4' }, { route: 'x5' }],
      arr2 = [{ pattern: 'y1', route: 'x1' }, { pattern: 'y2', route: 'x1' }, { pattern: 'y3', route: 'x2' }, { pattern: 'y4', route: 'x2' }, { pattern: 'y5', route: 'x3' }, { pattern: 'y6', route: 'x3' }, { pattern: 'y7', route: 'x4' }, { pattern: 'y8', route: 'x4' }, { pattern: 'y9', route: 'x5' }, { pattern: 'y10', route: 'x5' }];

const map = new Map(arr2.map( o => [o.route, []] ));
for (const o of arr2) map.get(o.route).push(o.pattern);

const table = document.querySelector('.tg');
for (const [route, patterns] of map) {
    const row = table.insertRow();
    row.insertCell().textContent = route;
    row.insertCell().textContent = patterns.join(', ');
}
.tg  {border-collapse:collapse;border-spacing:0;}
.tg td{font-family:Arial, sans-serif;font-size:14px;padding:10px 5px;border-style:solid;border-width:1px;overflow:hidden;word-break:normal;}
.tg th{font-family:Arial, sans-serif;font-size:14px;font-weight:normal;padding:10px 5px;border-style:solid;border-width:1px;overflow:hidden;word-break:normal;}
.tg .tg-yw4l{vertical-align:top}
<table class="tg">
  <tr>
    <th class="tg-yw4l">ROUTE</th>
    <th class="tg-yw4l">PATTERN(s)</th>
  </tr>
</table>
trincot
  • 317,000
  • 35
  • 244
  • 286