Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Ruby

Làm chủ Associative Arrays Ruby: Dùng Array#assoc & #rassoc cho Key-Value Lookup

Bạn có bao giờ có dữ liệu trong array, nhưng cần key/value lookup như hash? May mắn Ruby cung cấp cơ chế xử lý array như cấu trúc key-value. Xem ngay!

Giới thiệu Array#assoc và Array#rassoc

Giả sử bạn có máy chọn cổ phiếu thần thánh. Cứ vài phút nó phun recommendation buy/sell. Bạn hook nó với máy tính, nhận stream data dạng:


picks = [
  ["AAPL", "buy"],
  ["GOOG", "sell"],
  ["MSFT", "sell"]
]

Tìm guidance gần nhất cho Google, dùng `Array#assoc`:


# Trả về row đầu tiên với row[0] == "GOOG"
picks.assoc("GOOG") # => ["GOOG", "sell"]

Tìm recommendation "sell" gần nhất, dùng `Array#rassoc`:


# Trả về row đầu tiên với row[1] == "sell"
picks.rassoc("sell") # => ["GOOG", "sell"]

Không tìm thấy match, trả về nil:


picks.assoc("CSCO") # => nil
picks.rassoc("hold") # => nil

Dữ liệu lịch sử (Historical data)

Hash không thể có nhiều giá trị cho một key. Nhưng array có thể duplicate bao nhiêu tùy thích. `assoc` và `rassoc` làm điều hợp lý: trả về row match đầu tiên. Cho phép làm điều thú vị.

Máy chọn cổ phiếu stream data. Cuối cùng nó đổi ý về công ty, bảo buy cái trước bảo sell. Data trông như:


picks = [
  ["GOOG", "buy"],
  ["AAPL", "sell"],
  ["AAPL", "buy"],
  ["GOOG", "sell"],
  ["MSFT", "sell"]
]

Nếu đưa vào hash, update recommendation cho stock làm mất recommendation trước. Không với array. Prepend recommendation vào array, biết `Array#assoc` luôn cho recommendation gần nhất.


# Trả về row đầu tiên row[0] == "GOOG"
picks.assoc("GOOG") # => ["GOOG", "buy"]

Nhận được key-value goodness của hash, kèm audit trail miễn phí.

Nhiều hơn 2 cột

Điều neat khác về `assoc`: không giới hạn 2 cột/array. Có bao nhiêu cột tùy thích. Giả sử thêm timestamp cho recommendation buy/sell.


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

Dùng `assoc` hoặc `rassoc`, nhận timestamp theo:


# Toàn bộ row được trả về
picks.assoc("GOOG") # => ["GOOG", "sell", "2015-08-17 12:10:00 -0700"]

Rất hữu ích khi xử lý data từ CSV và file format nhiều cột.

Tốc độ (Speed)

Hash Ruby chắc chắn outperform `Array#assoc` trong hầu hết benchmark. Dataset càng lớn, chênh lệch càng rõ. Hash table search O(1), array search O(n). Tuy nhiên nhiều trường hợp chênh lệch không lớn đến mức lo lắng — tùy chi tiết.

Chỉ để vui, viết benchmark so sánh hash lookup vs assoc cho dataset 10 row và 100,000 row. Như kỳ vọng, hash và array perform tương đương với dataset nhỏ. Dataset lớn, hash dominates array.

...thoát fair, tôi search element cuối array, worst case cho array search.


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