Bạn đã bao giờ có một loạt dữ liệu trong một mảng, nhưng cần phải thực hiện tra cứu khóa / giá trị như bạn làm với hàm băm chưa? May mắn thay, Ruby cung cấp một cơ chế để coi các mảng là cấu trúc khóa-giá trị. Hãy cùng khám phá!
Giới thiệu Array#assoc
và Array#rassoc
Hãy tưởng tượng rằng bạn đã được trao cho một chiếc máy chọn cổ phiếu kỳ diệu. Cứ sau vài phút, nó lại đưa ra một khuyến nghị mua hoặc bán một cổ phiếu. Bạn đã quản lý để kết nối nó với máy tính của mình và nhận một luồng dữ liệu giống như sau:
picks = [
["AAPL", "buy"],
["GOOG", "sell"],
["MSFT", "sell"]
]
Để tìm hướng dẫn mới nhất cho Google, bạn có thể sử dụng Array#assoc
phương pháp. Đây là những gì trông giống như:
# Returns the first row of data where row[0] == "GOOG"
picks.assoc("GOOG") # => ["GOOG", "sell"]
Để tìm đề xuất "bán" gần đây nhất, bạn có thể sử dụng Array#rassoc
phương pháp.
# Returns the first row of data where row[1] == "sell"
picks.rassoc("sell") # => ["GOOG", "sell"]
Nếu không tìm thấy kết quả phù hợp nào, các phương thức trả về nil:
picks.assoc("CSCO") # => nil
picks.rassoc("hold") # => nil
Dữ liệu lịch sử
Hàm băm không được có nhiều hơn một giá trị cho một khóa. Nhưng các mảng có thể có bao nhiêu bản sao tùy thích. Các phương thức assoc và rassoc thực hiện điều hợp lý trong trường hợp này và trả về hàng phù hợp đầu tiên mà chúng tìm thấy. Điều này cho phép chúng tôi làm một số điều khá thú vị.
Máy chọn cổ phiếu tưởng tượng của chúng tôi cung cấp một luồng dữ liệu. Cuối cùng, nó sẽ thay đổi ý định về một công ty cụ thể và yêu cầu tôi mua những gì mà trước đây nó đã bảo tôi bán. Trong trường hợp đó, dữ liệu của chúng tôi trông giống như sau:
picks = [
["GOOG", "buy"],
["AAPL", "sell"],
["AAPL", "buy"],
["GOOG", "sell"],
["MSFT", "sell"]
]
Nếu tôi đặt tất cả dữ liệu này vào một hàm băm, thì việc cập nhật khuyến nghị cho một cổ phiếu cụ thể sẽ khiến tôi mất mọi khuyến nghị trước đó cho cổ phiếu đó. Không phải như vậy với mảng. Tôi có thể giữ các đề xuất trước cho mảng, biết rằng Array # assoc sẽ luôn đưa ra đề xuất mới nhất cho tôi.
# Returns the first row of data where row[0] == "GOOG"
picks.assoc("GOOG") # => ["GOOG", "buy"]
Vì vậy, chúng tôi nhận được độ tốt của giá trị khóa của một hàm băm, cùng với một dấu vết kiểm tra miễn phí.
Nhiều hơn hai cột
Một điều thú vị khác về assoc là bạn không bị giới hạn chỉ ở hai cột trên mỗi mảng. Bạn có thể có bao nhiêu cột tùy thích. Giả sử bạn đã thêm dấu thời gian cho mỗi đề xuất mua / bán.
picks = [
["AAPL", "buy", "2015-08-17 12:11:55 -0700"],
["GOOG", "sell", "2015-08-17 12:10:00 -0700"],
["MSFT", "sell", "2015-08-17 12:09:00 -0700"]
]
Bây giờ khi chúng ta sử dụng assoc
hoặc rassoc
, chúng tôi cũng sẽ nhận được dấu thời gian:
# The entire row is returned
picks.assoc("GOOG") # => ["GOOG", "sell", "2015-08-17 12:10:00 -0700"]
Tôi hy vọng bạn có thể thấy điều này hữu ích như thế nào khi xử lý dữ liệu từ CSV và các định dạng tệp khác có thể có nhiều cột.
Tốc độ
Hàm băm của Ruby chắc chắn sẽ tốt hơn Array#assoc
trong hầu hết các điểm chuẩn. Khi tập dữ liệu lớn hơn, sự khác biệt trở nên rõ ràng hơn. Rốt cuộc, tìm kiếm bảng băm là O (1), trong khi tìm kiếm mảng là O (n). Tuy nhiên, trong những trường hợp có thể, sự khác biệt sẽ không đủ lớn để bạn lo lắng - điều đó phụ thuộc vào chi tiết.
Chỉ cho vui thôi, tôi đã viết điểm chuẩn đơn giản so sánh tra cứu băm với assoc cho tập dữ liệu 10 hàng và cho tập dữ liệu 100.000 hàng. Như mong đợi, hàm băm và mảng hoạt động tương tự với tập dữ liệu nhỏ. Với tập dữ liệu lớn, hàm băm thống trị mảng.
... mặc dù công bằng mà nói, tôi đang tìm kiếm phần tử cuối cùng trong mảng, đây là trường hợp xấu nhất cho các tìm kiếm mảng.
require 'benchmark/ips'
require 'securerandom'
Benchmark.ips do |x|
x.time = 5
x.warmup = 2
short_array = (0..10).map { |i| [SecureRandom.hex(), i] }
short_hash = Hash[short_array]
short_key = short_array.last.first
long_array = (0..100_000).map { |i| [SecureRandom.hex(), i] }
long_hash = Hash[long_array]
long_key = short_array.last.first
x.report("short_array") { short_array.assoc(short_key) }
x.report("short_hash") { short_hash[short_key] }
x.report("long_array") { long_array.assoc(long_key) }
x.report("long_hash") { long_hash[long_key] }
x.compare!
end
# Calculating -------------------------------------
# short_array 91.882k i/100ms
# short_hash 149.430k i/100ms
# long_array 19.000 i/100ms
# long_hash 152.086k i/100ms
# -------------------------------------------------
# short_array 1.828M (± 3.4%) i/s - 9.188M
# short_hash 6.500M (± 4.8%) i/s - 32.426M
# long_array 205.416 (± 3.9%) i/s - 1.026k
# long_hash 6.974M (± 4.2%) i/s - 34.828M
# Comparison:
# long_hash: 6974073.6 i/s
# short_hash: 6500207.2 i/s - 1.07x slower
# short_array: 1827628.6 i/s - 3.82x slower
# long_array: 205.4 i/s - 33950.98x slower