Skip to content

Algorithms

JessicaOPRD edited this page Nov 15, 2022 · 11 revisions

Comparing two arrays — primitive element values

Equality

This question often must be answered to prevent unnecessary state change (and possibly infinite loops in a template). Many use popular library functions such as lodash's isEqual. However if you are trying to avoid importing lodash or similar, you will likely need to write your own function.

Check some basics

You can reduce the work in the following kinds of cases:

  • If the arrays are different lengths, they are not equal
  • If one array is null or undefined, it is not equal to a non-null array

Option 1 — Convert to string

This is probably the laziest solution I've found, but it generally works in a lot of cases, and does not require nested loops.

const originalSorted = [...original].sort()
const changedSorted = [...changed].sort()

if (originalSorted.toString() !== changedSorted.toString()) {
  this.$emit('input', changedSorted)
}

Another option for primitive typed arrays is to serialize to JSON.

Retrieving diff — adds(+) and deletes(-)

This comes up a lot, especially in the case of bulk state changes where we need to know which IDs have been removed and/or added. Some fundamentals to consider:

Does a bulk change make sense?

List updates will often persist immediately in the case of additions and removals. This means an add or remove operation is limited to one ID at a time. However, sometimes it makes sense to defer persistence until a "Save" or "Apply" button is clicked. In this case, the operation is "bulk" in nature. Whether or not to do this may depend on:

  • Whether the interface still indicates an edit state (text input adds vs. string adds)
  • Whether performance/waste occurs as a result of one-at-a-time operations (user can't see the results of a search until they select "Apply")

Option 1 — Use a set

Adapted from Stack Overflow.

const incomingValues = new Set(v)
const currValues = [...currPersistedValues]

let i = currentValues.length

// Handle deletions between current and new
while (i--) {
  let existingValue = currentValues[i]

  // Can this existing value be deleted from the new value set? If not,
  // the existing value has been deleted in this change. This also takes
  // care of values that have not changed.
  if (!incomingValues.delete(existingValue)) {
    currentValues.splice(i, 1)
  }
}

// Handle additions: only added values remain at this point
this.internalValue.push(...incomingValues)

🟢 Use of set/map lookup is performant

🟢 Decently concise — naming can be improved possible to be more readable

🟡 Duplicates might need removal prior to set creation?

🟡 Provides merged output, but could be changed to provide adds and deletes

Option 2 — Loop over each array

public struct function arrayDiff(
  required array original,
  required array changed
) {

  // Does not verify same order or duplicates
  var removed = [];
  var added = [];

  // For each item in the original, does it still exist in the changed array?
  for (var value in arguments.original) {
    if (!ArrayContains(arguments.changed, value)) {
      ArrayAppend(removed, value);
    }
  }

  // For each item in the changed, does it still exist in the original?
  for (var value in arguments.changed) {
    if (!ArrayContains(arguments.original, value)) {
      ArrayAppend(added, value);
    }
  }

  return {
    added = added,
    removed = removed
  };
}

🟢 Decently concise and readable

🟢 Outcome of diff separated for insert/update operations

🟡 Use of loop is less performant — remember ArrayContains and includes functions are looping functions too, thus there are 4 search operations here

🟡 Could end up with duplicates