Dizi elemanının dizinini O (n) 'den daha hızlı alın


104

BÜYÜK bir dizim ve ondan bir değerim var. Dizideki değerin indeksini almak istiyorum. Array#indexOnu almak için aramak yerine başka bir yolu var mı ? Sorun, gerçekten büyük bir dizi tutma ve Array#indexçok sayıda kez arama ihtiyacından kaynaklanıyor .

Birkaç denemeden sonra , değerin kendisi yerine alanlarla yapıları depolayarak elemanların içindeki dizinleri önbelleğe almanın(value, index) performansta büyük bir adım olduğunu (20x kez kazan) buldum .

Yine de, önbelleğe almadan en öğesinin dizinini bulmanın daha uygun bir yolu olup olmadığını merak ediyorum (veya performansı artıracak iyi bir önbelleğe alma tekniği var mı).

Yanıtlar:


118

Diziyi bir karmaya dönüştürün. Ardından anahtarı arayın.

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

2
dizi çok uzunsa en hızlı
Kevin

17
Kullanım durumunuza bağlı olarak, yinelenen değerler varsa bu sorunlu olabilir. Yukarıda açıklanan yöntem, eşdeğer veya #rindex'i (değerin son oluşumunu) döndürecektir. karma daha sonra döndürülen endeks değerini ilk dizinin toplam uzunluğundan çıkarır - 1. # (array.length - 1) - hash ['b']
ashoda

2
Hash'e dönüştürme O (n) süresini almaz mı? Sanırım birden fazla kullanılacaksa, hash dönüştürme daha başarılı olacaktır. ancak tek kullanım için, dizi boyunca yinelemekten farklı değil mi?
ahnbizcad

Evet, ve hash hesaplaması bir karşılaştırma kadar hızlı kısa devre yapmayacağından, gerçekten önemliyse muhtemelen tek kullanım için daha kötüdür.
Peter DeWeese

199

Neden indeks veya rindex kullanmıyorsunuz?

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

dizin: 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
Bu, dizilerinin büyüklüğünden dolayı OP'nin istemediklerini söylediği şeydi. Dizi # indeksi O (n) ve bunu birden çok kez yapmak performansı öldürecektir. Karma arama O (1) 'dir.
Tim

4
@tim, cevabım sırasında BU'nun aynı soru olduğunu hatırlayamıyorum , belki OP soruyu daha sonra revize etti, bu da bu cevabı geçersiz kılar.
Roger 13

3
O zaman belirli bir zamanda düzenlendiğini söylemez miydi?
Tim

Hehe, evet bu doğru. O zamanlar ben ve 30 kişi daha okuyorduk. Sanırım: /
Roger

9

Diğer yanıtlar, bir dizide birden çok kez listelenen bir giriş olasılığını hesaba katmaz. Bu, her anahtarın dizideki benzersiz bir nesne olduğu ve her değerin nesnenin yaşadığı yere karşılık gelen bir dizin dizisi olduğu bir karma döndürür:

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

Bu, yinelenen girişler için hızlı bir arama sağlar:

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

6

Hash kullanmamak için iyi bir sebep var mı? Aramaları olan O(1)vs O(n)dizisi için.


Önemli olan - #keyshash'i çağırıyorum , bu da kullandığım diziyi döndürüyor. Yine de mimarimi de düşünebilirim ...
gmile

3

Bir buysa sıralı dizi bir ikili arama algoritması kullanabilirsiniz ( O(log n)). Örneğin, Array sınıfını şu işlevsellikle genişletmek:

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
Aslında okumak o kadar da zor değil. Birinci kısım, alt sınır üst sınırdan büyükse dönün (özyineleme dosyalanmış). ikinci kısım, orta nokta m ile o noktadaki değer e'yi karşılaştırarak sol tarafa mı yoksa sağ tarafa mı ihtiyacımız olduğunu kontrol eder. İstediğimiz cevaba sahip değilsek, tekrar ederiz.
ioquatix

Düzenlemeden ziyade olumsuz oy kullanan insanların egosuna daha iyi geldiğini düşünüyorum.
Andre Figueiredo

2

@ Sawa'nın cevabı ve orada listelenen yorumun bir kombinasyonunu alarak, dizi sınıfına bir "hızlı" indeks ve rindex uygulayabilirsiniz.

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

Dizinizin doğal bir sırası varsa ikili aramayı kullanın.

İkili aramayı kullanın.

İkili arama vardır O(log n) erişim süresi vardır.

İkili aramanın nasıl kullanılacağına ilişkin adımlar,

  • Dizinizin sıralaması nedir? Örneğin, isme göre mi sıralanıyor?
  • bsearchÖğeleri veya dizinleri bulmak için kullanın

Kod örneği

# 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

Yine de, önbelleğe almadan en öğesinin dizinini bulmanın daha uygun bir yolu olup olmadığını merak ediyorum (veya performansı artıracak iyi bir önbelleğe alma tekniği var mı).

İkili aramayı kullanabilirsiniz (diziniz sıralıysa ve dizide sakladığınız değerler bir şekilde karşılaştırılabilirse). Bunun çalışması için, ikili aramaya mevcut elemanın "soluna" mı yoksa "sağına" mı bakması gerektiğini söyleyebilmeniz gerekir. Ama indexaynı diziden öğeyi alıyorsanız, ekleme zamanında depolamanın ve sonra onu kullanmanın yanlış olmadığına inanıyorum .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.