Appropriate JS data structure for linking three arrays

I have three arrays that I need to link to each other in this way:

  1. arr1 = [‘A’, ‘A’, ‘B’, ‘B’, ‘C’, ‘C’ ‘A’, ‘C’]
  2. arr2 = [‘a’, ‘aa’, ‘b’, ‘bb’, ‘c’, ‘cc’, ‘aaa’, ‘ccc’]
  3. arr3 = [1, 2, 3, 4, 5, 6, 7, 8]

I want these arrays to be linked like this: [[‘A’, [‘a’, 1], [‘aa’,2], [‘aaa’,7]], [‘B’, [‘b’, 3], [‘bb’,4]], [‘C’, [‘c’, 5], [‘cc’,6], [‘ccc’, 8]]

I could create a 2d array like this but I feel it won’t be an efficient data structure. Moreover, I will be using HTML to display content from this data structure and the 2d array could get complicated. I was thinking of using a Map but not sure whether it supports arrays as values to the keys. Any ideas/suggestions how I can achieve this?

Answer

Update 2

With this version requiring only shared indices in the three arrays, plus some grouping of the results, it’s a bit easier:

const link = (a1, a2, a3) =>
  [... new Set (a1)] .map ((a) => [ 
    a,                       
    ...Object.keys(a1) .filter (k => a1[k] == a) .map (k => [a2 [k], a3 [k]])
  ])  

const arr1 = ['A', 'A', 'B', 'B', 'C', 'C', 'A', 'C']
const arr2 = ['a', 'aa', 'b', 'bb', 'c', 'cc', 'aaa', 'ccc']
const arr3 = [1, 2, 3, 4, 5, 6, 7, 8]

console .log (link (arr1, arr2, arr3))
.as-console-wrapper {max-height: 100% !important; top: 0}

I would still strongly recommend that you rethink your data structures. Shared array indices are a pretty nasty way to deal with data.

I would find this much, much cleaner:

[
  {foo: 'A', bars: [{baz: 'a', qux: 1}, {baz: 'aa', qux: 2}, {baz: 'aaa', qux: 7}]},
  {foo: 'B', bars: [{baz: 'b', qux: 3}, {baz: 'bb', qux: 4}]},
  {foo: 'C', bars: [{baz: 'c', qux: 5}, {baz: 'cc', qux: 6}, {baz: 'ccc', qux: 8}]}
]

Explanation

A comment asked for an explanation of this code. Here’s an attempt.

const link = (a1, a2, a3) =>
  [... new Set (a1)] .map ((a) => [ 
    a,                       
    ...Object.keys(a1) .filter (k => a1[k] == a) .map (k => [a2 [k], a3 [k]])
  ])  

We start by defining link as a function of three parameters, a1, a2, and a3. With realistic data you would likely give these more meaningful names. Our goal will be to take the three arrays and, using shared indices, pair up the values in each by indices, and then group the results based on the first value, each of which may be duplicated multiple times in the first array.

We could have chosen to use a crossproduct implementation to create an intermediate result like [['A', 'a', '1'], ['A', 'aa', 2], ['B', 'b', 3], ...] then do the grouping on those values.

Instead we choose to go a little more directly at this. Our arrow function contains only one expression, so there is no need for {} or a return statement. We first create an array of unique members of a1, using what’s probably the most common implementation of uniqueness: [... new Set (a1)]. Here new Set (a1) creates a set, an container without duplicates. When we prepend it with ..., we get an iterator for the set. And wrapping that in [] makes it into an array. That will give us ['A', 'B', 'C'].

We want an output array containing one output array for each of these, so we can use map, which by applying a function to each element turns one array into another of the same size. The function we pass to map takes a value from ['A', 'B', 'C'] and returns an array. The first element is that value, and the others are found by calling Object.keys on our array. (Logically, Array.keys makes more sense here, as it will give us numeric keys. But that would take one additional step as we’d have to turn its iterator result into an array; and in this case the string keys will work just as well.) We filter these keys/indices to find those where the values in our original array match the current value. For A, this would be ['0', '1', '6'].

We map again, converting these indices into a pair of same-index values from the other two arrays. This is .map (k => [a2 [k], a3 [k]]). Finally, we apply a ... to spread the results into our array.

I hope that helps.

Updated Version

We have more information now. If I understand correctly, we will have some way to match a given element from the second array with one from the first array. Let’s encode that in a function, which we can then pass to our generic solution.

Here I demonstrate with the sample data assuming a match is simply a letter match.

const link = (match) => (a1, a2, a3) =>
  a1 .map (x => [
    x, 
    ...Object.keys(a2) .filter (k => match (x, a2[k]))
      .map (k => [a2[k], a3[k]])
  ])  

const arr1 = ['A', 'B', 'C']
const arr2 = ['a', 'aa', 'b', 'bb', 'c', 'cc', 'aaa', 'ccc']
const arr3 = [1, 2, 3, 4, 5, 6, 7, 8]

const matchingLetter = (a1Val, a2Val) => 
  a2Val .startsWith (a1Val.toLowerCase())

console .log (link (matchingLetter) (arr1, arr2, arr3))
.as-console-wrapper {max-height: 100% !important; top: 0}

Original Version

As discussed in the comments, this strikes me as a bad idea, if your data sources don’t force it upon you.

But if all you need to do is to takes one element from the first array and pair it with a linked set of two pairs from the next two arrays, then you could write something like this:

const link = (a1, a2, a3) =>
  a1 .map ((a, i) => [a, [a2[2 * i], a3[2 * i]], [a2[2 * i + 1], a3[2 * i + 1]]])

const arr1 = ['A', 'B', 'C']
const arr2 = ['a', 'aa', 'b', 'bb', 'c', 'cc']
const arr3 = [1, 2, 3, 4, 5, 6]

console .log (link (arr1, arr2, arr3))
.as-console-wrapper {max-height: 100% !important; top: 0}

If there is some other relationship between the linked sets than this little two-for-one mapping, then I think we need more information.

Leave a Reply

Your email address will not be published. Required fields are marked *