Dapatkan indeks elemen array lebih cepat dari O (n)

104

Diberikan saya memiliki array BESAR, dan nilai darinya. Saya ingin mendapatkan indeks nilai dalam array. Apakah ada cara lain selain menelepon Array#indexuntuk mendapatkannya? Masalahnya berasal dari kebutuhan untuk menyimpan array yang sangat besar dan menelepon Array#indexberkali-kali.

Setelah beberapa percobaan, saya menemukan bahwa caching indeks di dalam elemen dengan menyimpan struct dengan (value, index)field alih-alih nilainya sendiri memberikan langkah besar dalam kinerja (20x kali menang).

Masih saya bertanya-tanya apakah ada cara yang lebih nyaman untuk menemukan indeks elemen en tanpa caching (atau ada teknik caching yang baik yang akan meningkatkan kinerja).

gmile
sumber

Jawaban:

118

Ubah array menjadi hash. Lalu cari kuncinya.

array = ['a', 'b', 'c']
hash = Hash[array.map.with_index.to_a]    # => {"a"=>0, "b"=>1, "c"=>2}
hash['b'] # => 1
sawa
sumber
2
tercepat jika susunannya sangat panjang
Kevin
17
Bergantung pada kasus penggunaan Anda, ini bisa menjadi masalah jika ada nilai duplikat. Metode yang dijelaskan di atas akan mengembalikan ekuivalen atau #rindex (kemunculan nilai terakhir) Untuk mendapatkan hasil ekuivalen #index, yang berarti hash mengembalikan indeks pertama dari nilai yang Anda perlukan untuk melakukan sesuatu di sepanjang baris membalikkan larik sebelum membuat hash kemudian mengurangi nilai indeks yang dikembalikan dari total panjang array awal - 1. # (array.length - 1) - hash ['b']
ashoda
2
Bukankah konversi menjadi hash membutuhkan waktu O (n)? Saya kira jika itu akan digunakan lebih dari sekali, maka konversi hash akan lebih berkinerja. tetapi untuk penggunaan tunggal, apakah tidak ada bedanya dengan melakukan iterasi melalui array?
ahnbizcad
Ya, dan mungkin lebih buruk untuk penggunaan tunggal jika itu benar-benar penting karena perhitungan hash tidak akan mengalami korsleting secepat perbandingan.
Peter DeWeese
199

Mengapa tidak menggunakan index atau 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')

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

Roger
sumber
13
Ini persis seperti yang dikatakan OP yang TIDAK mereka inginkan, karena ukuran array mereka yang besar. Array # index adalah O (n) dan melakukan itu beberapa kali akan mematikan kinerja. Pencarian hash adalah O (1).
Tim
4
@ Tim, saya tidak ingat pada saat jawaban saya bahwa INI adalah pertanyaan yang sama , mungkin OP merevisi pertanyaan nanti, yang akan membatalkan jawaban ini.
Roger
3
Bukankah akan dikatakan bahwa itu telah diedit pada waktu tertentu?
Tim
Hehe, ya itu benar. Saya dan 30 orang lainnya sedang membacanya. Saya kira: /
Roger
9

Jawaban lain tidak memperhitungkan kemungkinan entri terdaftar beberapa kali dalam sebuah larik. Ini akan mengembalikan hash di mana setiap kunci adalah objek unik dalam larik dan setiap nilai adalah larik indeks yang sesuai dengan tempat tinggal objek:

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

Ini memungkinkan pencarian cepat untuk entri duplikat:

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

Apakah ada alasan bagus untuk tidak menggunakan hash? Pencarian adalah O(1)vs. O(n)untuk larik.

Erik Peterson
sumber
Intinya adalah - Saya memanggil #keyshash, yang mengembalikan array yang saya gunakan. Namun, saya mungkin akan memikirkan arsitektur saya juga ...
gmile
3

Jika itu adalah array yang diurutkan, Anda dapat menggunakan algoritma pencarian Biner ( O(log n)). Misalnya, memperluas kelas Array dengan fungsionalitas ini:

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
sumber
3
Sebenarnya tidak terlalu sulit untuk dibaca. Bagian pertama, kembalikan jika batas bawah lebih besar dari batas atas (rekursi telah diajukan). bagian kedua memeriksa apakah kita membutuhkan sisi kiri atau kanan dengan membandingkan titik tengah m dengan nilai pada titik itu ke e. jika kita tidak mendapatkan jawaban yang kita inginkan, kita mengulang.
ioquatix
Saya pikir itu lebih baik untuk ego orang yang downvoting daripada mengedit.
Andre Figueiredo
2

Dengan menggabungkan jawaban @ sawa dan komentar yang terdaftar di sana, Anda dapat mengimplementasikan indeks "cepat" dan rindex pada kelas array.

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
sumber
2

Jika array Anda memiliki tatanan natural, gunakan pencarian biner.

Gunakan pencarian biner.

Pencarian biner memiliki O(log n)waktu akses.

Berikut langkah-langkah tentang cara menggunakan pencarian biner,

  • Apa urutan array Anda? Misalnya, apakah itu diurutkan berdasarkan nama?
  • Gunakan bsearchuntuk menemukan elemen atau indeks

Contoh kode

# 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
sumber
0

Masih saya bertanya-tanya apakah ada cara yang lebih nyaman untuk menemukan indeks elemen en tanpa caching (atau ada teknik caching yang baik yang akan meningkatkan kinerja).

Anda dapat menggunakan pencarian biner (jika array Anda diurutkan dan nilai yang Anda simpan dalam array sebanding dalam beberapa hal). Agar dapat bekerja, Anda harus dapat memberi tahu pencarian biner apakah itu harus melihat "ke kiri" atau "ke kanan" dari elemen saat ini. Tapi saya percaya tidak ada yang salah dengan menyimpan indexpada waktu penyisipan dan kemudian menggunakannya jika Anda mendapatkan elemen dari larik yang sama.

Julik
sumber