Отримати індекс елемента масиву швидше, ніж 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) - хеш ['b']
ashoda

2
Чи не перетворення в хеш займає O (n) час? Я припускаю, що якщо він буде використовуватися не один раз, то хеш-конверсія буде більш ефективною. але для одноразового використання це не відрізняється від ітерації через масив?
ahnbizcad

Так, і, ймовірно, гірше для одноразового використання, якщо це дійсно має значення, оскільки хеш-розрахунок не буде коротким замиканням так швидко, як порівняння.
Пітер ДеВіз

199

Чому б не використовувати індекс або 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
Це саме те, що в ОП заявили, що НЕ хочуть, завдяки великому розміру їх масиву. Індекс масиву # - це 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

Чи є вагомі причини не використовувати хеш? Lookups є O(1)vs. O(n)для масиву.


Справа в тому, що я закликаю #keysхеш, який повертає масив, який я використовую. І все-таки я міг би подумати над своєю архітектурою ...
gmile

3

Якщо це відсортований масив, ви можете використовувати алгоритм пошуку Binary ( 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

Я вважаю, що це краще для егоїзму людей, що зводяться, а не редагувати.
Andre Figueiredo

2

Взявши комбінацію відповіді @ sawa та перерахованого коментаря, ви могли реалізувати "швидкий" індекс та 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.