Uzyskaj indeks elementu tablicy szybciej niż O (n)

104

Biorąc pod uwagę, że mam OGROMNĄ tablicę i wartość z niej. Chcę uzyskać indeks wartości w tablicy. Czy jest inny sposób, zamiast zadzwonić Array#indexpo to? Problem wynika z potrzeby utrzymywania naprawdę dużej tablicy i wywoływania Array#indexogromną liczbę razy.

Po kilku próbach odkryłem, że buforowanie indeksów wewnątrz elementów poprzez przechowywanie struktur z (value, index)polami zamiast samej wartości daje ogromny skok wydajności (20x wygrana).

Nadal zastanawiam się, czy istnieje wygodniejszy sposób na znalezienie indeksu elementu en bez buforowania (lub jest dobra technika buforowania, która zwiększy wydajność).

gmile
źródło

Odpowiedzi:

118

Przekonwertuj tablicę na skrót. Następnie poszukaj klucza.

array = ['a', 'b', 'c']
hash = Hash[array.map.with_index.to_a]    # => {"a"=>0, "b"=>1, "c"=>2}
hash['b'] # => 1
sawa
źródło
2
najszybszy, jeśli tablica jest bardzo długa
Kevin
17
W zależności od przypadku użycia może to być problematyczne, jeśli istnieją zduplikowane wartości. Metoda opisana powyżej zwróci ekwiwalent lub #rindex (ostatnie wystąpienie wartości) Aby uzyskać #index równoważne wyniki, co oznacza, że ​​hash zwracający pierwszy indeks wartości, musiałbyś zrobić coś zgodnie z odwróceniem tablicy przed utworzeniem hash następnie odejmuje zwróconą wartość indeksu od całkowitej długości początkowej tablicy - 1. # (array.length - 1) - hash ['b']
ashoda
2
Czy konwersja na hash nie zajmuje O (n) czasu? Przypuszczam, że jeśli będzie używany więcej niż raz, konwersja skrótu będzie wydajniejsza. ale dla pojedynczego użycia, czy nie różni się to od iteracji po tablicy?
ahnbizcad
Tak, i prawdopodobnie gorzej do jednorazowego użytku, jeśli naprawdę ma to znaczenie, ponieważ obliczanie skrótu nie spowoduje zwarcia tak szybko, jak porównanie.
Peter DeWeese
199

Dlaczego nie użyć indeksu lub rindeksu?

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')

indeks: 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

zrozumiałem
źródło
13
Właśnie tego OP powiedział, że NIE chce, ze względu na duży rozmiar ich macierzy. Indeks tablicy # to O (n) i wielokrotne robienie tego zabije wydajność. Wyszukiwanie skrótu to O (1).
Tim
4
@tim, cóż, nie pamiętam w momencie mojej odpowiedzi, że TO było to samo pytanie, być może OP poprawił to pytanie później, co unieważniłoby tę odpowiedź.
Roger
3
Czy nie powiedziałby, że był wtedy edytowany w określonym czasie?
Tim
Hehe, tak to prawda. Cóż, ja i kolejne 30 osób czytało to wtedy. Chyba: /
Roger
9

Inne odpowiedzi nie uwzględniają możliwości wielokrotnego wpisu w tablicy. To zwróci skrót, w którym każdy klucz jest unikalnym obiektem w tablicy, a każda wartość jest tablicą indeksów, która odpowiada miejscu, w którym obiekt żyje:

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] }

Pozwala to na szybkie wyszukiwanie zduplikowanych wpisów:

indices.select { |k, v| v.size > 1 }
=> { 1 => [0, 3], 2 => [1, 4], 3 => [2, 5] }
hololeap
źródło
6

Czy istnieje dobry powód, aby nie używać skrótu? Odnośniki są O(1)a O(n)dla tablicy.

Erik Peterson
źródło
Chodzi o to - wzywam #keyshash, który zwraca tablicę, której używam. Mimo to mógłbym przemyśleć również moją architekturę ...
gmile
3

Jeśli jest to posortowana tablica, możesz użyć algorytmu wyszukiwania binarnego ( O(log n)). Na przykład rozszerzenie klasy Array o tę funkcjonalność:

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
isakkarlsson
źródło
3
Właściwie nie jest to takie trudne do odczytania. Pierwsza część, zwróć, jeśli dolna granica jest większa niż górna (rekursja została złożona). druga część sprawdza, czy potrzebujemy lewej lub prawej strony, porównując środek m z wartością w tym punkcie do e. jeśli nie mamy odpowiedzi, której szukamy, powtarzamy.
ioquatix,
Myślę, że jest to lepsze dla ego ludzi, którzy głosują w dół niż edytują.
Andre Figueiredo
2

Biorąc pod uwagę odpowiedź @ sawy i komentarz tam wymieniony, można zaimplementować indeks „quick” i rindex w klasie tablicy.

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
ianstarz
źródło
2

Jeśli twoja tablica ma naturalną kolejność, użyj wyszukiwania binarnego.

Użyj wyszukiwania binarnego.

Wyszukiwanie binarne ma O(log n)czas dostępu.

Oto kroki, jak korzystać z wyszukiwania binarnego,

  • Jaka jest kolejność twojej tablicy? Na przykład, czy jest posortowane według nazwy?
  • Służy bsearchdo znajdowania elementów lub indeksów

Przykład kodu

# 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
akuhn
źródło
0

Nadal zastanawiam się, czy istnieje wygodniejszy sposób na znalezienie indeksu elementu en bez buforowania (lub jest dobra technika buforowania, która zwiększy wydajność).

Możesz użyć wyszukiwania binarnego (jeśli twoja tablica jest uporządkowana, a wartości, które przechowujesz w tablicy są w jakiś sposób porównywalne). Aby to zadziałało, musisz być w stanie powiedzieć wyszukiwarce binarnej, czy ma ona szukać „w lewo”, czy „w prawo” bieżącego elementu. Ale uważam, że nie ma nic złego w przechowywaniu indexczasu wstawiania, a następnie używaniu go, jeśli otrzymujesz element z tej samej tablicy.

Julik
źródło