Получить индекс элемента массива быстрее, чем O (n)


104

Учитывая, что у меня есть ОГРОМНЫЙ массив и значение из него. Я хочу получить индекс значения в массиве. Есть ли другой способ вместо звонка, Array#indexчтобы получить его? Проблема возникает из-за необходимости хранить действительно огромный массив и вызывать Array#indexогромное количество раз.

После пары попыток я обнаружил, что кеширование индексов внутри элементов путем сохранения структур с (value, index)полями вместо самого значения дает огромный скачок в производительности (выигрыш в 20 раз).

Тем не менее, мне интересно, есть ли более удобный способ найти индекс элемента en без кеширования (или есть хороший метод кеширования, который повысит производительность).

Ответы:


118

Преобразуйте массив в хеш. Тогда ищите ключ.

array = ['a', 'b', 'c']
hash = Hash[array.map.with_index.to_a]    # => {"a"=>0, "b"=>1, "c"=>2}
hash['b'] # => 1

2
самый быстрый, если массив очень длинный
Кевин

17
В зависимости от вашего варианта использования это может быть проблематично, если есть повторяющиеся значения. Описанный выше метод вернет эквивалент или #rindex (последнее вхождение значения). Чтобы получить результаты, эквивалентные #index, то есть хеш, возвращающий первый индекс значения, вам нужно будет сделать что-то в строках реверсирования массива перед созданием затем хеш вычитает возвращенное значение индекса из общей длины исходного массива - 1. # (array.length - 1) - hash ['b']
ashoda

2
Разве преобразование в хэш не занимает O (n) времени? Я полагаю, что если он будет использоваться более одного раза, то преобразование хэша будет более эффективным. но для одноразового использования это не отличается от итерации по массиву?
ahnbizcad

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

199

Почему бы не использовать index или rindex?

array = %w( a b c d e)
# get FIRST index of element searched
puts array.index('a')
# get LAST index of element searched
puts array.rindex('a')

индекс: http://www.ruby-doc.org/core-1.9.3/Array.html#method-i-index

rindex: http://www.ruby-doc.org/core-1.9.3/Array.html#method-i-rindex


13
Это именно то, что OP сказал, что они НЕ хотят из-за большого размера их массива. Индекс массива # - O (n), и выполнение этого несколько раз приведет к снижению производительности. Поиск по хэшу - O (1).
Тим

4
@tim, ну, я не могу вспомнить во время своего ответа, что ЭТО был тот же вопрос, возможно, ОП изменил вопрос позже, что сделало бы этот ответ недействительным.
Роджер

3
Разве тогда не было бы сказано, что он был отредактирован в определенное время?
Тим

Хе-хе, да, это правда. Я и еще 30 человек читали это тогда. Я думаю: /
Роджер

9

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

a = [1, 2, 3, 1, 2, 3, 4]
=> [1, 2, 3, 1, 2, 3, 4]

indices = a.each_with_index.inject(Hash.new { Array.new }) do |hash, (obj, i)| 
    hash[obj] += [i]
    hash
end
=> { 1 => [0, 3], 2 => [1, 4], 3 => [2, 5], 4 => [6] }

Это позволяет быстро искать повторяющиеся записи:

indices.select { |k, v| v.size > 1 }
=> { 1 => [0, 3], 2 => [1, 4], 3 => [2, 5] }

6

Есть ли веская причина не использовать хеш? Поиск по O(1)сравнению O(n)с массивом.


Дело в том, что я вызываю #keysхэш, который возвращает массив, который я использую. Тем не менее, я мог бы подумать и над своей архитектурой ...
gmile

3

Если это отсортированный массив, вы можете использовать алгоритм двоичного поиска ( O(log n)). Например, расширение класса Array с помощью этой функции:

class Array
  def b_search(e, l = 0, u = length - 1)
    return if lower_index > upper_index

    midpoint_index = (lower_index + upper_index) / 2
    return midpoint_index if self[midpoint_index] == value

    if value < self[midpoint_index]
      b_search(value, lower_index, upper_index - 1)
    else
      b_search(value, lower_index + 1, upper_index)
    end
  end
end

3
На самом деле это не так уж и сложно читать. Первая часть, вернуть, если нижняя граница больше верхней (рекурсия подана). Вторая часть проверяет, нужна ли нам левая или правая сторона, сравнивая среднюю точку m со значением в этой точке с e. если у нас нет нужного ответа, мы рекурсивно используем его.
ioquatix

Я думаю, что это лучше для эго людей, которые голосуют против, а не редактируют.
Андре Фигейредо

2

Взяв комбинацию ответа @awa и указанного там комментария, вы можете реализовать «быстрый» индекс и rindex для класса массива.

class Array
  def quick_index el
    hash = Hash[self.map.with_index.to_a]
    hash[el]
  end

  def quick_rindex el
    hash = Hash[self.reverse.map.with_index.to_a]
    array.length - 1 - hash[el]
  end
end

2

Если ваш массив имеет естественный порядок, используйте двоичный поиск.

Используйте бинарный поиск.

У двоичного поиска есть O(log n)время доступа.

Вот шаги по использованию бинарного поиска,

  • Каков порядок вашего массива? Например, по названию?
  • Используйте bsearchдля поиска элементов или индексов

Пример кода

# assume array is sorted by name!

array.bsearch { |each| "Jamie" <=> each.name } # returns element
(0..array.size).bsearch { |n| "Jamie" <=> array[n].name } # returns index

0

Тем не менее, мне интересно, есть ли более удобный способ найти индекс элемента en без кеширования (или есть хороший метод кеширования, который повысит производительность).

Вы можете использовать двоичный поиск (если ваш массив упорядочен и значения, которые вы храните в массиве, каким-то образом сопоставимы). Чтобы это работало, вам необходимо указать двоичному поиску, должен ли он смотреть «влево» или «вправо» от текущего элемента. Но я считаю, что нет ничего плохого в том, чтобы сохранить время indexвставки и затем использовать его, если вы получаете элемент из того же массива.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.