Введение

В JavaScript карты и наборы — это структуры данных, которые позволяют хранить любое количество данных любого типа. Каждый из них является итерируемым, что позволяет легко получать доступ к данным и управлять ими, и каждый поставляется со своим собственным набором предопределенных методов. Однако по большей части на этом сходство между ними заканчивается.

Вы можете думать о карте как о сверхмощной версии обычного объекта. Это:

  1. Хранит пары "ключ-значение".
  2. Полуупорядоченный. Итерация выполняется в том же порядке, в котором были вставлены значения. Все ключи на карте перечислимы (что не всегда верно для объекта).
  3. Не может содержать повторяющиеся ключи, но может содержать повторяющиеся значения.
  4. Имеет свойство размера, поэтому вернуть количество пар "ключ-значение" в карте проще, чем в объекте.

Набор, с другой стороны, похож на массив по своей конструкции, где каждый элемент существует как одно значение, а не как пара ключ-значение. Однако это:

  1. Не может содержать повторяющиеся значения.
  2. Не имеет гарантированного порядка, поскольку элементы не могут быть доступны по индексу. Но, как и в случае с картой, итерация будет происходить в том порядке, в котором были вставлены значения.
  3. Обычно имеет лучшую временную сложность по сравнению с массивом, особенно когда речь идет о больших наборах данных.

Как видите, карты и наборы предлагают несколько уникальных преимуществ по сравнению с объектами и массивами, что делает их ценными инструментами для разработчиков. В этой статье мы рассмотрим некоторые менее известные способы использования этих структур данных.

Связывание сложных типов данных со значениями

Карты можно использовать для хранения сложных типов данных, таких как объекты и функции, в качестве ключей. Это полезно в тех случаях, когда вам нужно связать сложный тип данных с определенным значением:

const coolNewMap = new Map();
const averageObj = { name: "John", age: 30 };
const middlingFunc = function() { console.log("This is mediocre."); }

coolNewMap.set(averageObj, "some value");
coolNewMap.set(middlingFunc, "another value");

console.log(coolNewMap.get(averageObj)); // "some value"
console.log(coolNewMap.get(middlingFunc)); // "another value"

Здесь мы создаем новую карту и добавляем объект и функцию в качестве ключей со связанными значениями. Затем мы получаем значения с помощью метода get, который возвращает значение, связанное с указанным ключом.

Это полезная функция, когда вам нужно закешировать результат трудоемкого вычисления — например, запрос API или сложную математическую операцию:

const evenCoolerNewMap = new Map();

function expensiveComputation(value) {
  if (evenCoolerNewMap.has(value)) {
    return evenCoolerNewMap.get(value);
  } else {
    // perform expensive computation
    const result = evenCoolerNewMap.set(value, result);
    return result;
  }
}

Выше мы определяем еще одну новую карту для хранения результатов наших вычислений. Функция expensiveComputation сначала проверяет, содержит ли карта уже значение для данного ввода. Если это так, функция возвращает кэшированный результат. В противном случае он выполняет операцию и сохраняет результат в нашей карте, прежде чем вернуть его.

Удаление повторяющихся значений из массива

Удаление повторяющихся значений из массива может быть довольно трудоемким, так как оно должно включать некоторую комбинацию циклов, использования indexOf, сокращения и/или фильтрации данных. Процесс может стать настолько болезненным, что Underscore даже создал целый метод, чтобы справиться с ним. К счастью, используя множества, мы можем относительно легко выполнить ту же операцию:

const wordsThatShouldExist = ['memoreek', 'hoag', 'furty', 'hoag'];
const arrayToSet = new Set(wordsThatShouldExist);
const uniqueArray = Array.from(arrayToSet);

console.log(uniqueArray); // ['memoreek', 'hoag', 'furty']

В этом примере мы создаем новый массив, содержащий несколько повторяющихся значений. Затем мы преобразуем его в набор, удаляя все неуникальные элементы. Затем мы конвертируем набор обратно в массив, оставляя нам наш результат: массив уникальных значений.

Внедрение эффективных алгоритмов

Поиск в ширину — это алгоритм, используемый для поиска заданного значения в структуре данных, такой как дерево или граф. Процесс должен начинаться с корневого узла и исследовать каждый узел на текущем уровне глубины, прежде чем переходить глубже.

const oakTree = {
  value: 'Branch',
  children: [
    {
      value: 'Twig 1',
      children: [
        { value: 'small acorn', children: [] },
        { value: 'leaf', children: [] }
      ]
    },
    {
      value: 'Twig 2',
      children: [
        { value: 'eensy-weensy acorn', children: [] },
        { value: 'a second, weirder leaf', children: [] }
      ]
    }
  ]
};

function breadthFirstSearch(tree, target) {
  const nodes = [tree]; // create array with root node

  const visited = new Set(); // create set to keep track of visited nodes

  for (let node of nodes) {
    if (node.value === target) { // check value of current node
      return node;
    }
    if (!visited.has(node)) { // check if set contains current node
      visited.add(node); // if not, add node to set
      nodes.push(...node.children); // add children of node to array
    }
  }
  return null;
}

// Searching for the node with value 'leaf':
const result = breadthFirstSearch(oakTree, 'leaf');
console.log(result); // { value: 'leaf', children: [] }

Здесь мы определяем простую древовидную структуру данных. Затем мы используем нашу функцию breadthFirstSearch, которая принимает дерево и целевое значение и возвращает либо узел с совпадающим значением, либо null, если значение не найдено. Функция использует набор для отслеживания посещенных узлов, поэтому мы можем избежать повторного поиска одного и того же узла.

Одним из преимуществ использования набора таким образом является более быстрое время поиска. Метод массива .includes имеет линейную, или O(n), временную сложность, потому что для выполнения своей работы он должен перебирать каждое значение массива. К счастью, для эквивалентного метода множества это не так! Для того же типа поиска .has имеет среднюю временную сложность O(1). При работе с небольшими наборами данных разница здесь может быть незначительной, но для больших объемов влияние огромно.

Наконец, использование набора позволяет нам писать более чистый и простой код. Если бы мы использовали массив для отслеживания уже найденных узлов, нам пришлось бы перебирать и проверять каждое значение. Вместо этого мы смогли сжать нашу логику в две простые строки кода. Комбинированный эффект? Более быстрый и эффективный код с меньшим количеством ошибок.

В итоге…

Карты и наборы — это очень крутые структуры данных в JavaScript, которые предлагают несколько уникальных преимуществ по сравнению с объектами и массивами. С помощью карт и наборов мы можем хранить сложные типы данных и управлять ими, удалять дубликаты из массивов, кэшировать дорогостоящие вычисления, с легкостью реализовывать более эффективные алгоритмы и многое другое. В конце концов, возможности использования этих структур данных гораздо шире, чем я мог бы описать в одной статье. Я бы посоветовал вам поэкспериментировать с ними в свободное время, поскольку лучшее понимание их преимуществ и ограничений может только помочь вам стать более эффективным разработчиком!

Рекомендации