Skip to content

RU Работа с массивами

mingun edited this page May 26, 2014 · 5 revisions

ВикиСправка по APIЯдроРабота с массивами
English | Русский

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

JavaScript включает в себя методы-мутаторы, изменяющие массив:

  • array.pop — Удаляет последний элемент из массива.
  • array.push — Добавляет один или несколько элементов в конец массива.
  • array.reverse — Переворачивает порядок следования элементов в массиве.
  • array.shift — Удаляет первый элемент из массива.
  • array.sort — Сортирует элементы в массиве.
  • array.splice — Добавляет или удаляет элементы из массива.
  • array.unshift — Добавляет один или несколько элементов в начало массива.

Также он включает методы доступа, возвращающие некоторое представление массива:

  • array.concat — Соединяет массив с другими массивами или значениями.
  • array.join — Соединяет все элементы массива в одну строку.
  • array.slice — Извлекает кусочек массива.
  • array.indexOf — Ищет первое вхождение вначения в массив.
  • array.lastIndexOf — Ищет последнее вхождение значения в массив.

И наконец, он включает методы итерации по массиву, применяющие функцию к элементам массива:

  • array.filter — Создаёт новый массив с теми элементами, для которых предикат вернул true.
  • array.forEach — Вызывает функцию для каждого элемента в массиве.
  • array.every — Проверяет, каждый ли элемент массива удовлетворяет предикату.
  • array.map — Создаёт новый массив с результатами функции, применённой к каждому элементу массива.
  • array.some — Проверяет, что по крайней мере один элемент в массиве удовлятворяет предикату.
  • array.reduce — Применяет функцию для сворачивания массива в одно значение (слева направо).
  • array.reduceRight — Применяет функцию для сворачивания массива в одно значение (справа налево).

Сортировка

# d3.ascending(a, b)

Возвращает -1, если a меньше, чем b; 1, если a больше, чем b и 0, если они равны. Это функция сравнения для естественного порядка и она может использоваться с встроенным методом sort сортировки массива для упорядочивания элементов в порядке возрастания:

function(a, b) {
  return a < b ? -1 : a > b ? 1 : 0;
}

Обратите внимание, что если во встроенном методе sort не указана функция сравнения, порядок сортировки будет по умолчанию лексикографическим (в алфавитном порядке), а не естественным! Это может повлечь ошибки при сортировке массива чисел.

# d3.descending(a, b)

Возвращает -1, если a больше, чем b; 1, если a меньше, чем b и 0, если они равны. Это функция сравнения для обратного естественного порядка и она может использоваться с встроенным методом sort сортировки массива для упорядочивания элементов в порядке убывания:

function(a, b) {
  return b < a ? -1 : b > a ? 1 : 0;
}

Обратите внимание, что если во встроенном методе sort не указана функция сравнения, порядок сортировки будет по умолчанию лексикографическим (в алфавитном порядке), а не естественным! Это может повлечь ошибки при сортировке массива чисел.

# d3.min(array[, accessor])

Возвращает минимальное значение в указанном массиве array, используя естественное упорядочивание. Если массив пуст, возвращает undefined. Может быть указана необязательная функция доступа accessor, что будет эквивалентно вызову array.map(accessor) перед вычислением минимального значения. В отличии от встроенного метода Math.min, этот метод игнорирует неопределённые значения; это полезно для вычисления домена шкалы, рассматиривая только регион с определёнными данными. Кроме того, элементы сравниваются с использованием естественного упорядочивания, а не числового. Например, минимумом в массиве ["20", "3"] будет значение "20", в то время, как в массиве [20, 3] минимумом будет значение 3.

# d3.max(array[, accessor])

Возвращает максимальное значение в указанном массиве array, используя естественное упорядочивание. Если массив пуст, возвращает undefined. Может быть указана необязательная функция доступа accessor, что будет эквивалентно вызову array.map(accessor) перед вычислением максимального значения. В отличии от встроенного метода Math.max, этот метод игнорирует неопределённые значения; это полезно для вычисления домена шкалы, рассматиривая только регион с определёнными данными. Кроме того, элементы сравниваются с использованием естественного упорядочивания, а не числового. Например, максимумом в массиве ["20", "3"] будет значение "3", в то время, как в массиве [20, 3] максимумом будет значение 20.

# d3.extent(array[, accessor])

Возвращает минимальное и максимальное значения в указанном массиве array, используя естественное упорядочивание. Метод эквивалентен одновременному вызову d3.min и d3.max.

# d3.sum(array[, accessor])

Возвращает сумму указанного массива array. Если массив пуст, возвращает 0. Может быть указана необязательная функция доступа accessor, что будет эквивалентно вызову array.map(accessor) перед вычислением суммы. Этот метод игнорирует неверные значения вроде NaN и undefined; это полезно для вычисления суммы с учётом только определённых значений данных.

# d3.mean(array[, accessor])

Возвращает среднее указанного массива array. Если массив пуст, возвращает undefined. Может быть указана необязательная функция доступа accessor, что будет эквивалентно вызову array.map(accessor) перед вычислением среднего. Этот метод игнорирует неверные значения вроде NaN и undefined; это полезно для вычисления среднего с учётом только определённых значений данных.

# d3.median(array[, accessor])

Возвращает медиану указанного массива array, используя алгоритм R-7. Если массив пуст, возвращает undefined. Может быть указана необязательная функция доступа accessor, что будет эквивалентно вызову array.map(accessor) перед вычислением медианы. Этот метод игнорирует неверные значения вроде NaN и undefined; это полезно для вычисления медианы с учётом только определённых значений данных.

# d3.quantile(numbers, p)

Возвращает p-квантиль указанного отсортированного массива чисел numbers, где p — это число из диапазона [0, 1]. Например, медиана может быть вычислена с использованием p = 0.5, первый квартиль при p = 0.25 и третий квартиль при p = 0.75. Эта конкретная реализация использует алгоритм R-7, являющийся алгоритмом по умолчанию для языка программирования R и Excel. Этот метод требует, что бы массив numbers содерчал числа и был отсортирован в возрастающем порядке, например, с помощью предиката d3.ascending.

var a = [0, 1, 3];
d3.quantile(a, 0);      // возвращает 0
d3.quantile(a, 0.5);    // возвращает 1
d3.quantile(a, 1);      // возвращает 3
d3.quantile(a, 0.25);   // возвращает 0.5
d3.quantile(a, 0.75);   // возвращает 2
d3.quantile(a, 0.1);    // возвращает 0.19999999999999996 

# d3.bisectLeft(array, x[, lo[, hi]])

Ищет точку вставки значения x в массив array для поддержания упорядоченности массива. Агрументы lo и hi могут использоваться для определения рассматриваемого подмножества массива; по умолчанию рассматривается весь массив. Если x уже присутствует в массиве array, точка вставки будет находиться до (левее) любой существующей записи. Возвращаемое значение подходит для использования в качестве первого аргумента в методе splice, при условии, что массив array уже отсортирован. Возвращаемая точка вставки i делит массив array на две половины таким образом, что v < x для всех v в array.slice(lo, i) для левой половины и v >= x для всех v в array.slice(i, hi) для правой половины.

# d3.bisect(array, x[, lo[, hi]])
# d3.bisectRight(array, x[, lo[, hi]])

Работает, как и bisectLeft, но возвращает точку вставки, следующую (правее) за любым существующим элементом x в массиве array. Возвращаемая точка вставки i делит массив array на две половины таким образом, что v <= x для всех v в array.slice(lo, i) для левой половины и v > x для всех v в array.slice(i, hi) для правой половины.

# d3.bisector(accessor)
# d3.bisector(comparator)

Возвращает бисектор, использующий указанную функцию доступа accessor или функцию сравнения comparator. Возвращаемый объект имеет свойства left и right, ссылающиеся, соответственно, на bisectLeft и bisectRight. Этот метод может использоваться для разбиения пополам массива объектов вместо массива примитивов. Например, пусть мы имеем следующий массив объектов:

var data = [
  {date: new Date(2011,  1, 1), value: 0.5},
  {date: new Date(2011,  2, 1), value: 0.6},
  {date: new Date(2011,  3, 1), value: 0.7},
  {date: new Date(2011,  4, 1), value: 0.8}
];

Подходящая функция бисекции может быть сконструирована вот так:

var bisect = d3.bisector(function(d) { return d.date; }).right;

Это эквивалентно указанию функции сравнения:

var bisect = d3.bisector(function(a, b) { return a.date - b.date; }).right;

Последующее применение как bisect(data, new Date(2011, 1, 2)) вернёт индекс. Используйте функцию сравнения вместо функции доступа, если вы хотите, чтобы значения были отсортированы в порядке, отличном от естественного, например, по убыванию вместо возрастания.

# d3.shuffle(array)

Рандомизирует порядок элементов в указанном массиве array, используя алгоритм тасования Фишера–Йетса.

Ассоциативные массивы

Другим распространённым типом данных в JavaScript является ассоциативный массив, или по-простому, объект, имеющий множество именованных свойств. В Java он называется картой, а в Python — словарём. JavaScript предоставляет стандартный механизм для итерирования по ключам (или именам свойств) ассоциативного массива: цикл for…in. Однако, следует заметить, что порядок обхода в этом случае не определён. D3 предоставляет несколько операторов для преобразования ассоциативных массивов в стандартные индексированные массивы.

# d3.keys(object)

Возвращает массив, содержащий имена свойств указанного объекта (ассоциативного массива) object. Порядок элементов в возвращённом массиве не определён.

# d3.values(object)

Возвращает массив, содержащий значения свойств указанного объекта (ассоциативного массива) object. Порядок элементов в возвращённом массиве не определён.

# d3.entries(object)

Возвращает массив, содержащий как ключи, так и значения свойств указанного объекта (ассоциативного массива) object. Каждая запись — это объект, содержащий атрибуты key и value, например { key: "foo", value: 42 }. Порядок элементов в возвращённом массиве не определён.

var entry = d3.entries({ foo: 42 }); // returns [{key: "foo", value: 42}]

Карты

Хотя кажется заманчивым использовать обычные объекты в качестве карт в JavaScript, это может привести к неожиданному поведению при использовании встроенных нмён свойств в качестве ключей. Например, если вы попытаетесь установить object["__proto__"] = 42, вы скорее всего получите не то, что ожидали. Тоже самое верно, если вы попытаетесь определить, определён ли указанный ключ в карте; "hasOwnProperty" in object вернёт true, поскольку ваш объект наследует метод hasOwnProperty из прототипа Object. Для решения этих проблем ES6 предлагает ввести простые карты и множества; пока же современные браузеры не поддерживают эти коллекции, вы можете использовать d3.map вместо них.

Обратите внимание: в отличие от предложенной в ES6 карты, d3.map по-режнему использует приведение к строке для ключей вместо проверки на строгое равенство.

# d3.map([object])

Конструирует новую карту. Если параметр object указан, копирует все перечисляемые свойства из объекта object в новую карту.

# map.has(key)

Возвращает true только в том случае, если карта имеет запись под указанным строковым ключом key. Обратите внимание: значение может быть null или undefined.

# map.get(key)

Возвращает значение по указанному строковому ключу key. Если карта не содержит записи под указанным ключом, возвращает undefined.

# map.set(key, value)

Устанавливает значение value по указанному строковому ключу key, возвращает новое значение. Если карта ранее содержала запись под тем же самым ключом, старая запись будет заменена новым значением.

# map.remove(key)

Если карта содержит запись по указанному строковому ключу key, удаляет её и возвращает true. В противном случае ничего не делает и возвращает false.

# map.keys()

Возвращает массив строковых ключей для каждой записи в карте. Порядок возвращаемых ключей произволен.

# map.values()

Возвращает массив значений для каждой записи в карте. Порядок возвращаемых значений произволен.

# map.entries()

Возвращает массив объектов ключ-значение для каждой записи в карте. Порядок возвращаемых записей произволен. Ключ каждой записи является строкой, однако тип значения произволен.

# map.forEach(function)

Вызывает указанную функцию function для каждой записи в карте, двумя параметрами передаются ключ записи и его значение. Контекст this функции привязан к текущей карте. Возвращает undefined. Порядок обхода произвольный.

# map.empty()

Возвращает true только в том случае, если карта содержит ноль записей.

# map.size()

Возвращает количество записей в карте.

Множества

# d3.set([array])

Конструирует новое множество. Если указан параметр array, добавляет из указанного массива строковые значения в возвращаемое множество.

# set.has(value)

Возвращает true только в том случае, если множество содержит запись для указанной строки value.

# set.add(value)

Добавляет указанную строку value в множество.

# set.remove(value)

Если множество содержит указанную строку value, удаляет её и возвращает true. В противном случае ничего не делает и возвращает false.

# set.values()

Возвращает массив строковых значений в множестве. Порядок возвращаемых значений произволен. Может использоваться в качестве удобного способа вычисления уникальных значений в наборе строк. Напрмер:

d3.set(["foo", "bar", "foo", "baz"]).values(); // "foo", "bar", "baz"

# set.forEach(function)

Вызывает указанную функцию function для каждого значения в множестве, параметром передаётся значение. Контекст this функции привязан к текущему множеству. Возвращает undefined. Порядок обхода произвольный.

# set.empty()

Возвращает true только в том случае, если множество содержит ноль значений.

# set.size()

Возвращает количество значений в множестве.

Операторы над массивами

# d3.merge(arrays)

Сливает указанные массивы arrays в один массив. Этот метод похож на встроенный метод concat массива; отличие заключается в том, что данный метод удобнее, когда мы имеем дело с массивом массивов.

d3.merge([ [1], [2, 3] ]); // возвращает [1, 2, 3]

# d3.range([start, ]stop[, step])

Генерирует массив, содержащий арифметическую прогрессию, подобно встроенной в Python функции range. Этот метод часто используется для прохода по последовательности чисел, например, индексов в массиве. В отличии от аналога на Python, аргументы не обязательно должны быть целыми числами, хотя результаты более предсказуемы, если они являются числами с плавающей запятой. Если параметр step опущен, по умолчанию он равен 1. Если параметр start опущен, по умолчанию он равен 0. Значение stop не включается в результат. Полная форма возвращаемого массива чисел может быть представлена как [start, start + step, start + 2 * step, …]. Если значение step положительно, последний элемент будет наибольшим start + i * step, который меньше stop; если же значение step отрицательно, последний элемент будет наименьшим start + i * step, большим чем stop. Если возвращаемый массив будет содержать бесконечное число значений, выбросится ошибка вместо сваливания в бесконечный цикл.

# d3.permute(array, indexes)

Возвращает перестановку указанного массива array, используя массив индексов indexes. Возвращаемый массив содержит элементы из массива array на позициях из массива indexes. Например, вызов permute(["a", "b", "c"], [1, 2, 0]) вернёт массив ["b", "c", "a"]. Допустимо использовать массив indexes, отличающийся по длине от массива array, индексы в этом случае будут продублированы или опущены.

Этот метод так же может использоваться для извлечения значений из объекта в массив с устойчивым порядком (индексы массива в JavaScript — это просто свойства, имеющие специальную договорённость со свойством length). Извлечение значений по ключам с сохранением порядка может быть полезно для генерирования массивов данных во вложенных выборках. Например, мы хотим отобразить некоторые данные по урожайности ячменя в штате Миннесота в виде таблицы:

var cols = ["site", "variety", "yield"];
thead.selectAll('th').data(cols)
  .enter().append('th').text(function (d) { return d.toUpperCase(); });
tbody.selectAll('tr').data(yields)
  .enter().append('tr').selectAll('td').data(function (row) { return d3.permute(row, cols); })
    .enter().append('td').text(function (d) { return d; });

# d3.zip(arrays…)

Возвращает массив массивов, где каждый i-тый массив содержит i-тый элемент из каждого из массивов arrays. Возвращаемый массив обрезается по длине по самому короткому массиву в arrays. Если arrays содержит только один массив, возвращаемый массив будет содержать одноэлементные массивы. При вызове без аргументов возвращаемый массив будет пустым.

d3.zip([1, 2], [3, 4]); // вернёт [[1, 3], [2, 4]]

# d3.transpose(matrix)

Эквивалентен вызову d3.zip.apply(null, matrix); использует оператор zip для транспонирования двумерной матрицы.

# d3.pairs(array)

Для каждой пары соседних элементов в указанном массиве array возвращает новый массив с кортежом из элемента i и элемента i - 1. Например:

d3.pairs([1, 2, 3, 4]); // вернёт [[1, 2], [2, 3], [3, 4]]

Если указанный массив содержит менее двух элементов, вернёт пустой массив.

Вкладывание

Вкладывание позволяет элементам в массиве группироваться в иерархическую древовидную структуру; думайте о нём как об операторе GROUP BY в SQL, за исключением того, что вы можете иметь несколько уровней группировки и результирующий вывод — это дерево, а не плоская таблица. Уровни в дереве определяются функциями ключа. Листья дерева могут быть отсортированы по значению, в то время, как внутренние узлы могут быть отсортированы по ключу. Необязательная накопительная функция rollup может схлопывать элементы в каждом листе с использованием итоговой функции. Оператор вкладывания (объект, возвращаемый методом d3.nest) может использоваться повторно и не удерживает никаких ссылок на вложеные данные.

Например, рассмотрим следующую табличную структуру данных урожайности ячменя, с различных участков в штате Миннесота с 1931 по 1932 год:

var yields = [{yield: 27.00, variety: "Manchuria", year: 1931, site: "University Farm"},
              {yield: 48.87, variety: "Manchuria", year: 1931, site: "Waseca"},
              {yield: 27.43, variety: "Manchuria", year: 1931, site: "Morris"}, ...]

Для удобства визуализации может быть полезно разложить элементы сначала по годам, а потом по сортам, как показано ниже:

var nest = d3.nest()
  .key(function(d) { return d.year; })
  .key(function(d) { return d.variety; })
  .entries(yields);

Этот вызов вернёт вложенный массив. Каждый элемент внешнего массива — это пара ключ-значение, перечисляющая значения для каждого отдельного ключа:

[{key: 1931, values: [
   {key: "Manchuria", values: [
     {yield: 27.00, variety: "Manchuria", year: 1931, site: "University Farm"},
     {yield: 48.87, variety: "Manchuria", year: 1931, site: "Waseca"},
     {yield: 27.43, variety: "Manchuria", year: 1931, site: "Morris"}, ...]},
   {key: "Glabron", values: [
     {yield: 43.07, variety: "Glabron", year: 1931, site: "University Farm"},
     {yield: 55.20, variety: "Glabron", year: 1931, site: "Waseca"}, ...]}, ...]},
 {key: 1932, values: ...}]

Вложенная форма позволяет легко обходить и строить поколения иерархических структур в SVG или HTML.

# d3.nest()

Создаёт новый оператор вкладывания. Первоначально массив ключей пуст. Если оператор map или оператор entries будут вызваны до регистрации функции ключа, оператор вкладывания просто вернёт входной массив. Пример вкладывания: http://bl.ocks.org/phoebebright/raw/3176159/

# nest.key(function)

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

# nest.sortKeys(comparator)

Сортирует значения ключей для текущего ключа с использование функции сравнения comparator, в роли которой может выступать, например, d3.descending. Если для текущего ключа не указана функция сравнения, порядок, в котором будут возвращены ключи, неизвестен. Обратите внимание, что этот оператор влияет только на результат оператора entries; порядок ключей, возвращённых оператором map неопределён всегда, независимо от функции сравнения.

var nest = d3.nest()
    .key(function(d) { return d.year; })
    .sortKeys(d3.ascending)
    .entries(yields);

# nest.sortValues(comparator)

Сортирует листья, используя функцию сравнения comparator, в роли которой может выступать, например, d3.descending. Это примерно эквивалентно сортировке входного массива перед применением оператора вкладывания; однако обычно более эффективно, поскольку размер каждой группы меньше. Если функция сравнения значений не указана, элементы будут возвращены в том же порядке, в котором они присутствуют во входном массиве. Это относится как к оператору map, так и к оператору entries.

# nest.rollup(function)

Определяет накопительную функцию function, применяемую к каждой группе листьев. Возвращаемое значение накопительной функции заменит массив листьев либо ассоциативным массивом, возвращённым оператором map, либо значениями атрибутов каждой записи, возвращённой оператором entries.

# nest.map(array[, mapType])

Применяет оператор вкладывания к указанному массиву array, возвращая ассоциативный массив. Каждая запись в возвращённом ассоциативном массиве соответствует отдельному значению ключа, возвращённому первой функцией ключа. Значение записи зависит от количества зарегистрированных функций ключа: если есть дополнительный ключ, значением будет ещё один вложенный ассоциативный массив; в противном случае значением будет массив элементов, отфильтрованный из входного массива array по данному ключу.

Если указан параметр mapType, указанная функция будет использоваться для конструирования карты вместо простого JavaScript-объекта. Рекомендуется использовать функцию d3.map для этих целей. Пример:

var yieldsByYearAndVariety = d3.nest()
  .key(function(d) { return d.year; })
  .key(function(d) { return d.variety; })
  .map(yields, d3.map);

Использование d3.map вместо объекта предоставляет больше удобств (например, возвращённая карта имеет методы keys для получения всех ключей карты и values для получения всех значений) и защищена от конфликтов необычных названий ключей, таких как «proto» с именами встроенных свойств JavaScript.

# nest.entries(array)

Применяет оператор вкладывания к указанному массиву array, возвращая массив записей ключ-значение. Концептуально, это просто применение оператора d3.entries к ассоциативному массиву, возвращённому оператором map, но он применяется к каждому уровню иерархии, а не только к самому верхнему. Каждая запись в возвращённом массиве соответствует отдельному значению ключа, возвращённому первой функцией ключа. Значение записи зависит от количества зарегистрированных функций ключа: если есть дополнительный ключ, значением будет ещё один вложенный массив записей; в противном случае значением будет массив элементов, отфильтрованный из входного массива array по данному ключу.

Примеры оператора вкладывания: http://bl.ocks.org/phoebebright/raw/3176159/.

Clone this wiki locally