Nhận chỉ mục của phần tử mảng nhanh hơn O (n)


104

Cho trước tôi có một mảng HUGE và một giá trị từ nó. Tôi muốn lấy chỉ mục của giá trị trong mảng. Có cách nào khác, thay vì sau đó gọi Array#indexđể lấy nó? Vấn đề xuất phát từ nhu cầu giữ một mảng thực sự lớn và gọi Array#indexsố lần khổng lồ.

Sau một vài lần thử, tôi nhận thấy rằng bộ nhớ đệm lập chỉ mục bên trong các phần tử bằng cách lưu trữ cấu trúc với (value, index)các trường thay vì giá trị của chính nó mang lại một bước tiến lớn về hiệu suất (chiến thắng gấp 20 lần).

Tôi vẫn tự hỏi liệu có cách nào thuận tiện hơn để tìm chỉ mục của phần tử en mà không cần bộ nhớ đệm (hoặc có một kỹ thuật bộ nhớ đệm tốt sẽ tăng hiệu suất).

Câu trả lời:


118

Chuyển mảng thành hàm băm. Sau đó, hãy tìm chìa khóa.

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

2
nhanh nhất nếu mảng là rất dài
Kevin

17
Tùy thuộc vào trường hợp sử dụng của bạn, điều này có thể có vấn đề nếu có các giá trị trùng lặp. Phương thức được mô tả ở trên sẽ trả về giá trị tương đương hoặc #rindex (lần xuất hiện cuối cùng của giá trị) Để nhận được kết quả tương đương # chỉ số, nghĩa là hàm băm trả về chỉ mục đầu tiên của giá trị mà bạn cần thực hiện điều gì đó dọc theo dòng đảo ngược mảng trước khi tạo sau đó hàm băm trừ đi giá trị chỉ mục trả về từ tổng độ dài của mảng ban đầu - 1. # (array.length - 1) - hash ['b']
ashoda

2
Việc chuyển đổi thành hàm băm có mất O (n) thời gian không? Tôi cho rằng nếu nó được sử dụng nhiều lần, thì chuyển đổi băm sẽ hiệu quả hơn. nhưng đối với cách sử dụng đơn lẻ, nó không khác gì sau đó lặp qua mảng?
ahnbizcad

Có, và có thể tồi tệ hơn khi sử dụng một lần nếu nó thực sự quan trọng vì tính toán băm sẽ không ngắn mạch nhanh như so sánh.
Peter DeWeese

199

Tại sao không sử dụng index hoặc 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')

chỉ mục: 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
Đây chính xác là những gì OP nói rằng họ KHÔNG muốn, do kích thước mảng của họ lớn. Chỉ số mảng # là O (n) và làm điều đó nhiều lần sẽ ảnh hưởng đến hiệu suất. Tra cứu băm là O (1).
Tim

4
@tim, tôi không thể nhớ tại thời điểm câu trả lời của tôi rằng ĐÂY là cùng một câu hỏi, có thể OP đã sửa lại câu hỏi sau đó, điều này sẽ làm mất hiệu lực câu trả lời này.
Roger

3
Nó sẽ không nói rằng nó đã được chỉnh sửa vào một thời điểm cụ thể?
Tim

Hehe, đúng là như vậy. Tôi và 30 người khác đã đọc nó sau đó. Tôi đoán: /
Roger

9

Các câu trả lời khác không tính đến khả năng một mục nhập được liệt kê nhiều lần trong một mảng. Thao tác này sẽ trả về một hàm băm trong đó mỗi khóa là một đối tượng duy nhất trong mảng và mỗi giá trị là một mảng chỉ số tương ứng với vị trí của đối tượng:

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

Điều này cho phép tìm kiếm nhanh các mục nhập trùng lặp:

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

6

Có lý do chính đáng để không sử dụng hàm băm không? Tra cứu O(1)so O(n)với mảng.


Vấn đề là - tôi đang gọi #keyshàm băm, nó trả về một mảng tôi đang sử dụng. Tuy nhiên, tôi có thể suy nghĩ về kiến trúc của tôi cũng ...
gmile

3

Nếu đó là một mảng được sắp xếp, bạn có thể sử dụng thuật toán tìm kiếm Binary ( O(log n)). Ví dụ: mở rộng lớp Mảng với chức năng này:

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
Nó thực sự không khó đọc. Phần đầu tiên, trả về nếu giới hạn dưới lớn hơn giới hạn trên (đệ quy đã nộp). Phần thứ hai kiểm tra xem chúng ta cần vế trái hay vế phải bằng cách so sánh trung điểm m với giá trị tại điểm đó là e. nếu chúng tôi không có câu trả lời chúng tôi muốn, chúng tôi lặp lại.
ioquatix

Tôi nghĩ nó tốt hơn cho cái tôi của những người phản đối hơn là chỉnh sửa.
Andre Figueedlyo

2

Kết hợp câu trả lời của @ sawa và nhận xét được liệt kê ở đó, bạn có thể triển khai chỉ mục và rindex "nhanh" trên lớp mảng.

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

Nếu mảng của bạn có thứ tự tự nhiên, hãy sử dụng tìm kiếm nhị phân.

Sử dụng tìm kiếm nhị phân.

Tìm kiếm nhị phân có O(log n)thời gian truy cập.

Đây là các bước về cách sử dụng tìm kiếm nhị phân,

  • Thứ tự của bạn mảng là gì? Ví dụ, nó có được sắp xếp theo tên không?
  • Sử dụng bsearchđể tìm các phần tử hoặc chỉ số

Mã ví dụ

# 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

Tôi vẫn tự hỏi liệu có cách nào thuận tiện hơn để tìm chỉ mục của phần tử en mà không cần bộ nhớ đệm (hoặc có một kỹ thuật bộ nhớ đệm tốt sẽ tăng hiệu suất).

Bạn có thể sử dụng tìm kiếm nhị phân (nếu mảng của bạn có thứ tự các giá trị bạn lưu trữ trong mảng có thể so sánh được theo một cách nào đó). Để điều đó hoạt động, bạn cần có khả năng cho tìm kiếm nhị phân biết liệu nó nên tìm kiếm "bên trái" hay "bên phải" của phần tử hiện tại. Nhưng tôi tin rằng không có gì sai khi lưu trữ indextại thời điểm chèn và sau đó sử dụng nó nếu bạn đang lấy phần tử từ cùng một mảng.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.