2012-03-21 18 views
11

Tôi có một tập hợp các cặp tiền tố/giá trị và muốn tìm bất kỳ giá trị nào trong kết nối này được liên kết với tiền tố mà chuỗi đích hiện tại của tôi bắt đầu. (Nó không quan trọng là hành vi được định nghĩa trong trường hợp có nhiều hơn một tiền tố khớp với nhau, vì bản chất của ca sử dụng của tôi là như vậy mà điều này sẽ không bao giờ xảy ra).clojure: Xác định hiệu quả nếu một chuỗi bắt đầu với bất kỳ tiền tố nào trong bộ sưu tập

Một ngây thơ (làm việc) thực hiện sau:

(defn prefix-match [target-str pairs] 
    (some 
    (fn [[k v]] 
     (if (.startsWith target-str k) 
      v 
      false)) 
    pairs)) 

như vậy mà:

user=> (prefix-match "foobar" {"meh" :qux, "foo" :baz}) 
:baz 

này hoạt động như dự định, nhưng là O (n) với chiều dài của chuỗi pairs. (Nhanh chóng chèn vào pairs cũng là mong muốn, nhưng không quan trọng như tra cứu nhanh). Điều đầu tiên xuất hiện trong đầu là chia một bộ sưu tập được sắp xếp với truy cập ngẫu nhiên hiệu quả, nhưng tôi không chắc cấu trúc dữ liệu nào trong Clojure phù hợp nhất với nhiệm vụ. Gợi ý?

+0

Mã ví dụ của bạn không hoạt động như được quảng cáo. Đó là tiền tố, mục tiêu-str hoặc khóa bản đồ? –

+0

@JustinKramer Rất tiếc. Khóa bản đồ là tiền tố; cuộc gọi mẫu không chính xác. Đã sửa. (Hàm tiền tố khớp được đưa ra là những gì tôi đang thực sự sử dụng trong mã sản xuất). –

Trả lời

4

Cách tiếp cận hiệu quả, ngắn gọn là tận dụng lợi thế của rsubseq, hoạt động trên mọi loại triển khai clojure.lang.Sorted - bao gồm sorted-map.

(defn prefix-match [sorted-map target] 
    (let [[closest-match value] (first (rsubseq sorted-map <= target))] 
    (if closest-match 
     (if (.startsWith target closest-match) 
     value 
     nil) 
     nil))) 

này vượt qua các bài kiểm tra có liên quan trong bộ tôi:

(deftest prefix-match-success 
    (testing "prefix-match returns a successful match" 
    (is (prefix-match (sorted-map "foo" :one "bar" :two) "foobar") :one) 
    (is (prefix-match (sorted-map "foo" :one "bar" :two) "foo") :one))) 

(deftest prefix-match-fail 
    (testing "prefix-match returns nil on no match" 
    (is (= nil (prefix-match (sorted-map "foo" :one, "bar" :two) "bazqux"))) 
    (is (= nil (prefix-match (sorted-map "foo" :one, "bar" :two) "zzz"))) 
    (is (= nil (prefix-match (sorted-map "foo" :one, "bar" :two) "aaa"))))) 
20

Làm thế nào về một trie?

(defn build-trie [seed & kvs] 
    (reduce 
    (fn [trie [k v]] 
    (assoc-in trie (concat k [:val]) v)) 
    seed 
    (partition 2 kvs))) 

(defn prefix-match [target trie] 
    (when (seq target) 
    (when-let [node (trie (first target))] 
     (or (:val node) 
      (recur (rest target) node))))) 

Cách sử dụng:

user> (def trie (build-trie {} "foo" :baz "meh" :qux)) 
#'user/trie 
user> trie 
{\m {\e {\h {:val :qux}}}, \f {\o {\o {:val :baz}}}} 
user> (prefix-match "foobar" trie) 
:baz 
user> (prefix-match "foo" trie) 
:baz 
user> (prefix-match "f" trie) 
nil 
user> (prefix-match "abcd" trie) 
nil 
+0

Đẹp - dễ dàng và hiệu quả để cập nhật khi xây dựng. Cảm ơn! –

+0

FYI - Tôi không chấp nhận điều này vì xây dựng cấu trúc của riêng mình khi thư viện chuẩn có sẵn một cái gì đó với hiệu suất bộ nhớ tốt hơn (và hiệu suất tương đương tốt hơn) hơi ngớ ngẩn một chút. Nó vẫn còn rất nhiều giá trị một +1, mặc dù. –

2

Có vẻ như đơn giản nhất để chỉ cần bật danh sách các tiền tố vào một biểu thức chính quy, và thức ăn những thành một khớp regex, được tối ưu hóa cho chính xác loại này nhiệm vụ. Một cái gì đó như

(java.util.regex.Pattern/compile (str "^" 
             "(?:" 
             (clojure.string/join "|" 
                  (map #(java.util.regex.Pattern/quote %) 
                   prefixes)) 
             ")")) 

Bạn nên dùng regex phù hợp để thử nghiệm với chuỗi (nhưng tôi có thể có một số tên phương thức sai hoặc thứ gì đó).

+0

Cách tiếp cận tốt đẹp - đối với các tiền tố dài, tôi có thể thấy điều này hiệu quả hơn đáng kể so với phương pháp tiếp cận trie trên tìm kiếm (mặc dù nó sẽ rất thú vị đối với điểm chuẩn).Mặt khác, cách tiếp cận trie dường như sẽ có hiệu suất tốt hơn khi thêm ánh xạ tiền tố/kết quả mới vào danh sách, đặc biệt khi bản đồ đã phát triển lớn. –

2

Các giải pháp sau thấy tiền tố phù hợp nhất và các công trình cũng đáng ngạc nhiên khi bản đồ là rất lớn và chuỗi là tương đối ngắn. Nó cố gắng khớp với v.d. "foobar", "fooba", "foob", "foo", "fo", "f" theo thứ tự và trả về kết quả trùng khớp đầu tiên.

(defn prefix-match 
    [s m] 
    (->> (for [end (range (count s) 0 -1)] (.subSequence s 0 end)) ; "foo", "fo", "f" 
     (map m)   ; match "foo", match "fo", ... 
     (remove nil?)  ; ignore unmatched 
     (first)))   ; Take first and longest match 
+0

Rất đẹp. Tôi tò mò để chuẩn nó chống lại cách tiếp cận sắp xếp bản đồ; tò mò khi một trong hai người đó tốt hơn. (Trực giác của tôi là điều này nhanh hơn khi bạn tạo nhanh bản đồ được sắp xếp cho hoạt động và cách sắp xếp bản đồ được sắp xếp nhanh hơn khi cấu trúc dữ liệu được tái sử dụng, nhưng đó thực sự chỉ là phỏng đoán). –

Các vấn đề liên quan