2012-07-02 21 views
23

... Có thể lập trình bắt buộc với dữ liệu có thể thay đổi được khoan sâu vào bộ não của tôi, nhưng tôi tìm thấy mã để xây dựng vectơ dữ liệu trong Clojure để tiết, khó sử dụng và phức tạp . Phải có cách tốt hơn!Cách tốt nhất để tích lũy kết quả trong một vector trong Clojure là gì? (Mã chức năng thuần túy có vẻ xấu xí và tiết)

Trong Ruby tôi có thể viết mã như:

results = [] 
a_collection.each do |x| 
    x.nested_collection.each do |y| 
    next if some_condition_holds 
    results << y 
    end 
end 

Trong Clojure, tôi không biết một cách tốt hơn để làm điều đó hơn là sử dụng một hàm đệ quy, có lẽ như sau (khủng khiếp) mã:

; NEWBIE ALERT! NEWBIE ALERT! 
(loop [results [] 
     remaining a_collection] 
    (if (empty? remaining) 
     results 
     (recur 
     (loop [results results 
       nested (nested_collection (first remaining))] 
      (if (empty? nested) 
       results 
       (if (some_condition_holds) 
        (recur results (rest nested)) 
        (recur (conj results (first nested)) (rest nested))))) 
     (rest remaining)))) 

Nếu không có dữ liệu có thể thay đổi và lặp lặp lại, bạn cần sử dụng đệ quy để tạo bộ sưu tập. Mỗi hàm đệ quy như vậy cần một mệnh đề bảo vệ (empty?), v.v ... Toàn bộ điều này lặp đi lặp lại nó khiến tôi muốn hét lên.

Trong trường hợp đơn giản, map là đủ, nhưng tôi đang nghĩ đến trường hợp có nhiều cấp độ lồng nhau và ở mỗi cấp, có thể có các điều kiện yêu cầu bỏ qua một lần lặp.

Trong Lisp thông thường, tôi có thể sử dụng macro loop hoặc mapcan. Clojure không có bất cứ điều gì như mapcan?

+0

THANKS CHO TẤT CẢ NGƯỜI BÀI ĐĂNG! ĐÁP ỨNG CỦA BẠN TẤT CẢ TUYỆT VỜI! Thật khó để chọn câu trả lời để chấp nhận. –

+0

Nó đã được nói trước đây, nhưng cơ sở của lập trình chức năng là các chức năng soạn thảo và sử dụng các hàm bậc cao hơn, chứ không phải sử dụng đệ quy. Theo quy định, đệ quy chỉ nên được sử dụng nếu một giải pháp với HOFs sẽ phức tạp hơn so với một đệ quy sử dụng đệ quy. – NielsK

+5

Để xây dựng: bạn sẽ sử dụng tấn đệ quy, nhưng hầu như tất cả nó phải được đệ quy mà người khác đã viết cho bạn, ví dụ như trong 'map' - nó khá hiếm khi viết đệ quy bằng tay. – amalloy

Trả lời

25

Trong thứ tự giảm dần về cách thoải mái Tôi nghĩ tùy chọn giao diện:

(for [x coll, 
     y (nested-collection x) 
     :when (not (some-condition-holds y))] 
    y) 

Ngoài ra, nếu bạn muốn thay vì xây dựng nó ra khỏi các chức năng như mapmapcat thay vì sử dụng for cú pháp:

(mapcat (fn [x] 
      (remove some-condition-holds 
        (nested-collection x))) 
     coll) 

Nếu bạn đang thực sự quan tâm vào nó, bạn cũng có thể xây dựng nó với các ứng dụng chức năng một phần và thành phần:

(mapcat (comp (partial remove some-condition-holds) 
       nested-collection) 
     coll) 

Kiểu thứ ba này không đọc rất tốt ở Clojure, mặc dù mã tương đương ở một số ngôn ngữ khác rất hay. Trong Haskell, ví dụ:

coll >>= (filter (not . someConditionHolds) . nestedCollection) 
4

chức năng đặt hàng cao hơn thực sự có thể giúp làm cho nó đẹp hơn nhiều mặc dù phải mất một thời gian để làm quen với suy nghĩ theo trình tự và chuyển đổi chuỗi.

có rất nhiều cách để viết những dòng này:

user> (into [] a_colletion) 
[0 1 2 3 4 5 6 7 8 9] 

user> (vec a_colletion) 
[0 1 2 3 4 5 6 7 8 9] 

user> (for [x a_colletion :when (even? x)] x) 
(0 2 4 6 8) 

một ví dụ phức tạp hơn có thể trông giống như thế này:

(flatten (for [x (map extract-from-nested-collection a_collection) 
       :when (test-conditions? x)] 
      x)) 

làm cho một bộ sưu tập lồng nhau

user> (def a_collection (map #(reductions + (range %)) (range 1 5))) 
#'user/a_collection 
user> a_collection 
((0) (0 1) (0 1 3) (0 1 3 6)) 

lấy một lồng nhau bộ sưu tập từ mỗi phần tử của a_collection và bỏ qua một số phần tử:

user> (map #(filter pos? %) a_collection) 
(() (1) (1 3) (1 3 6)) 

thêm các bộ sưu tập lồng nhau cùng

user> (flatten (map #(filter pos? %) a_collection)) 
(1 1 3 1 3 6) 

lọc một số bất cứ điều gì lớn hơn 3 từ bộ sưu tập san phẳng và sau đó vuông mỗi trong số họ

user> (for [x (flatten (map #(filter pos? %) a_collection)) 
       :when (<= x 3)] 
      (* x x)) 
(1 1 9 1 9) 
user> 
+0

'vào' sẽ là tốt nếu tôi chỉ muốn chắp thêm' a_collection' vào một vectơ; nhưng trong ví dụ này, tôi cần truy xuất bộ sưu tập lồng nhau từ mỗi phần tử của 'a_collection' và nối các bộ sưu tập lồng nhau lại với nhau. Ngoài ra, tôi cần phải lọc các phần tử được tích lũy trong vectơ. –

+0

bạn có thể tách riêng từng hành động này thành hành động của riêng nó. Tôi sẽ thêm một ví dụ sớm –

+0

Cảm ơn các ví dụ ... Tôi thích chúng. Nhưng tôi nghĩ rằng '(mapcat ...)' như được chứng minh bởi @ Hendekagon là tốt hơn so với '(flatten (map ...))'. –

7

Những người khác đã cung cấp câu trả lời về cách giải quyết vấn đề quy định sử dụng khái niệm FP như sử dụng chức năng tự cao. Nếu bạn phân tích quá trình suy nghĩ dẫn đến mã hiện tại của mình và so sánh với các giải pháp FP mà người khác đã cung cấp, bạn sẽ thấy rằng bất cứ khi nào bạn nghĩ - "có một biến để lưu trữ kết quả đã xử lý" - nó sẽ dẫn đến OR bắt buộc từng bước giải pháp và do đó mã Clojure của bạn chủ yếu là bắt buộc khi bạn nghĩ về lưu trữ kết quả là một "biến vector". Loại tư duy này sẽ không cho phép bạn áp dụng các khái niệm FP dựa trên "đánh giá biểu thức" và "giải quyết vấn đề theo thành phần"

+1

để chơi chủ trương của ma quỷ, và ngoài sự ngây thơ, thành phần chức năng khác với "từng bước" như thế nào, ngoài các đối số của mỗi hàm là các biến thể bất biến? – Hendekagon

+0

Cuối cùng tất cả đều thực hiện như "từng bước", nhưng sự khác biệt là cách bạn suy nghĩ về giải pháp. Ví dụ: nhân là kết quả của thành phần thêm và tất cả mọi thứ tương tự trong thế giới này được xây dựng từ việc sáng tác những thứ nguyên thủy và keo để tạo 2 thứ cũng là một thứ nguyên thủy hoặc hợp chất khác. – Ankur

2

amalloy's answer có lẽ là tốt nhất nếu bạn muốn theo một phong cách chức năng thành ngữ và tạo ra một sự lười biếng trình tự.

Nếu bạn đang thực sự quan tâm đến việc phải nhất thiết xây dựng một vector (chứ không phải là một chuỗi lười biếng), tôi có lẽ sẽ làm điều đó bằng một nguyên tử và doseq như sau:

(let [v (atom [])] 
    (doseq [x (range 5) 
      y (range 5)] 
    (if (> y x) 
     (swap! v conj (str x y)))) 
    @v) 

=> ["01" "02" "03" "04" "12" "13" "14" "23" "24" "34"] 

Như bạn có thể thấy, đây kết thúc cấu trúc rất giống với mã Ruby.

Cũng có thể thực hiện việc này bằng cách giảm, tuy nhiên điều này phù hợp nhất khi chỉ có một chuỗi đầu vào, ví dụ::

(reduce 
    (fn [v x] 
    (if (even? x) 
     (conj v x) 
     v)) 
    [] 
    (range 20)) 

=> [0 2 4 6 8 10 12 14 16 18] 
+0

Tôi nghĩ rằng việc sử dụng 'reduce' với một vectơ trống như bộ tích lũy sẽ có ý nghĩa hơn. – ponzao

+0

@ponzao - Tôi vừa thêm vào một ví dụ rút gọn khi nó xảy ra :-) nhưng giảm không thích hợp cho việc lặp lại hai bộ sưu tập cùng một lúc vì nó buộc bạn phải xây dựng một chuỗi trung gian [x y]. Bạn cũng có thể sử dụng giải pháp amalloy tại thời điểm đó. – mikera

+0

Vâng, tệ của tôi, tôi đã không đọc đúng câu hỏi gốc :) – ponzao

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