Best Way to Search for Repeated Values in a Multidimensional Array in JavaScript

In JavaScript Technology

What is the best approach to solve the following problem in JavaScript?

There is a multidimensional array ‘Teams’ with multiple arrays that have a TeamName (string), WinRecord (number), LossRecord (number), and a Sort_Key (number) in this format: [TeamName, W, L, sort_key]

The data looks like this:

Teams[
   ['Team A', 25, 2, 88],
   ['Team B', 20, 7, 84],
   ['Team C', 20, 7, 76],
   ['Team D', 20, 7, 54],
   ['Team E', 20, 7, 65],
   ['Team F', 20, 6, 34],
   ['Team G', 19, 8, 67],
   ['Team H', 20, 7, 11],
   ...
   ['Team N', 3-24, 33]
]

The previous data is in an arbitrary order for a reason.

The task here is to look inside the Multidimensional Array “Teams” where the W-L record is the same in the adjacent arrays. From the previous data, teams B, C, D and E have the same W-L record and they are all adjacent to each other. Team F does NOT have the same because although it matches the W but it does not match the L. Notice that team H has the same 20-7 record but since it is NOT adjacent, we do not consider it for our comparison!

Now need to re-organise the adjacent teams B, C, D and E , in ASC order by sort_key. The arrays that do not have a matching W-L with another array must stay in the same position. Therefore the solution would look like this:

Teams[
   ['Team A', 25, 2, 88], //stays in the same place because it has no W-L match adjacent
   ['Team D', 20, 7, 54],    //sort_key is the lowest of this "chunk" so this goes first
   ['Team E', 20, 7, 65],    //sort_key second in ASC order  
   ['Team C', 20, 7, 76],    //then this one
   ['Team B', 20, 7, 84],    //finally this is the highest sort_key of this "chunk"
   ['Team F', 20, 6, 34], //stays in the same place because it has no W-L match adjacent
   ['Team G', 19, 8, 67], //stays in the same place because it has no W-L match adjacent
   ['Team H', 20, 7, 11], //stays in the same place because its NOT ADJACENT to the last 20-7 team
   ...
   ['Team N', 3-24, 33] //stays in the same place because it has no W-L match adjacent
]

Note: The previous example has adjacent arrays with the same W-L of 20-7 , but there might be more “chunks” with matching W-L arrays that are adjacent to each other, this may happen multiple times until we reach array at position ‘n’.

Some solutions that could work are ass follows:

  1. To take the first array and compare with the next array, if there is a W-L match to check the next one, n times until there is no further match. If there is no match we move on to the next array and perform this step again.
  2. If there is an adjacent W-L we need to keep checking the next array until there is no exact match and we stop. All the adjacent matching arrays can be copied to a temporary array. We need to save “from” the original_position because if we take data from Teams[2] all the way to Teams[5] we need to re-insert the sorted values later starting at position 2. Maybe save this original_position into a variable?
  3. We now sort the temporary array by sort_key ASC. We now copy the temp array into the original Teams array “from” the original_position.
  4. We keep going looking for more matches like this until we reach position ‘n’ and we can exit and return the finalized properly sorted Teams array.

SOLUTION

Write a generic groupAdjacent function. It has two parameters:

  1. t – the input array
  2. equal – a function to test if elements are considered adjacent
function groupAdjacent(t, equal)
{ function* run (r, q, i)
  { if (i >= t.length)
      return yield [...r, q]
    if (equal(q, t[i]))
      return yield* run([...r, q], t[i], i + 1)
    yield [...r, q]
    yield* run([], t[i], i + 1)
  }
  return t.length
    ? Array.from(run([], t[0], 1))
    : [] 
}

After that define two functions specific to the particular teams data:

  1. teamAdjacent – determines what makes a team adjacency
  2. teamSort – compares two teams for sorting
const teamAdjacent = (a, b) =>
  a[1] == b[1] && a[2] == b[2] // w/l is equal

const teamSort = (a, b) =>
  a[3] - b[3]                  // sort by sortKey, ascending

Finally tie everything together.

groupAdjacent creates an array of groups, then .sort each group, and lastly call .flat to return an un-grouped result :

const result =
  groupAdjacent(teams, teamAdjacent)
    .map(_ => _.sort(teamSort))
    .flat()

console.log(result)
['Team A', 25, 2, 88]
['Team D', 20, 7, 54]
['Team E', 20, 7, 65]
['Team C', 20, 7, 76]
['Team B', 20, 7, 84]
['Team F', 20, 6, 34]
['Team G', 19, 8, 67]
['Team H', 20, 7, 11]
['Team N', 3, 24, 33]
function groupAdjacent(t, equal)
{ function* run (r, q, i)
  { if (i >= t.length)
      return yield [...r, q]
    if (equal(q, t[i]))
      return yield* run([...r, q], t[i], i + 1)
    yield [...r, q]
    yield* run([], t[i], i + 1)
  }
  return t.length
    ? Array.from(run([], t[0], 1))
    : [] 
}

const teamAdjacent = (a, b) =>
  a[1] == b[1] && a[2] == b[2] // w/l is equal

const teamSort = (a, b) =>
  a[3] - b[3]                  // sort by sortKey, ascending

const teams =
  [ ['Team A', 25, 2, 88]
  , ['Team B', 20, 7, 84]
  , ['Team C', 20, 7, 76]
  , ['Team D', 20, 7, 54]
  , ['Team E', 20, 7, 65]
  , ['Team F', 20, 6, 34]
  , ['Team G', 19, 8, 67]
  , ['Team H', 20, 7, 11]
  , ['Team N', 3, 24, 33]
  ]

const result =
  groupAdjacent(teams, teamAdjacent)
    .map(_ => _.sort(teamSort))
    .flat()

console.log(result)

groupAdjacentcan be written without recursion too:

function* groupAdjacent(t, equal)
{ if (t.length === 0) return
  let g = [t[0]]
  for (const _ of t.slice(1))
    if (equal(_, g[0]))
      g.push(_)
    else
      (yield g, g = [_])
  yield g
}

The only difference with this version is that you must wrap the groupAdjacent call in Array.from:

const result =
  Array.from(groupAdjacent(teams, teamAdjacent))
    .map(_ => _.sort(teamSort))
    .flat()
// => (same output)

Click here to view original web page at stackoverflow.com