【问题标题】:Better function for collections更好的集合功能
【发布时间】:2021-03-15 10:15:51
【问题描述】:

在SO中回答一个问题,我偶然发现了这个问题:

(def x [7 4 8 9 10 54 55 2 23 30 12 5])

(defn insert-x 
  ([sorted-coll x] 
   (insert-x sorted-coll x 
     (if (= (type sorted-coll) clojure.lang.PersistentVector) [] '())))

  ([sorted-coll x acc]
  (let [is-vector  (= (type sorted-coll) clojure.lang.PersistentVector)
        format-it  #(into (if is-vector [] '()) %)
        compare   (if is-vector < >)]
    (cond 
      (empty? sorted-coll) (format-it (cons x acc))

      (compare (peek sorted-coll) x) 
      (format-it (concat 
                   ((if is-vector identity reverse) sorted-coll) 
                   (conj acc x)))

      :else (recur (pop sorted-coll) x (cons (peek sorted-coll) acc))))))

(defn bubble-sort [coll]
  "Insert x into a sorted collection"
  (reduce insert-x [] coll))

(bubble-sort x)
;; => [2 4 5 7 8 9 10 12 23 30 54 55]

代码做它应该做的。

但是,insert-x 并不那么优雅。 如何以对所有集合都有效的方式编写insert-x? 所以它更简单/更优雅? 向量应该返回向量,列表应该返回列表等。

【问题讨论】:

    标签: collections clojure bubble-sort


    【解决方案1】:

    我猜你想多了。

    你有两个任务:

    1. 在已排序集合内的适当位置插入项目
    2. 输入向量的返回向量和输入列表的列表

    首先,我会像这样重写insert-x

    (defn insert-x [sorted-coll x]
      (let [[l r] (split-with #(<= % x) sorted-coll)]
        `(~@l ~x ~@r)))
    

    注意,它的作用与您的变体大致相同:取值直到所需位置,然后将左右部分与x 连接起来。另请注意,它总是生成正确排序的列表,与输入类型无关。

    user> (insert-x [1 3 5 7 9] 10)
    ;;=> (1 3 5 7 9 10)
    
    user> (insert-x [1 3 5 7 9] 0)
    ;;=> (0 1 3 5 7 9)
    
    user> (insert-x [1 3 5 7 9] 4)
    ;;=> (1 3 4 5 7 9)
    

    所以,接下来您需要做的就是减少输入并返回正确键入的结果:

    (defn my-sort [coll]
      (let [sorted (reduce insert-x () coll)]
        (if (vector? coll)
          (vec sorted)
          sorted)))
    
    user> (my-sort '(0 3 1 4 2 5 10 7))
    ;;=> (0 1 2 3 4 5 7 10)
    
    user> (my-sort [0 3 1 4 2 5 10 7])
    ;;=> [0 1 2 3 4 5 7 10]
    
    user> (my-sort ())
    ;;=> ()
    
    user> (my-sort [])
    ;;=> []
    

    【讨论】:

    • 这可能会导致中等大小的集合 (my-sort (shuffle (range 1000))) 的堆栈溢出,因为惰性 concat 和惰性 split-with 的堆叠。
    • @A.Webb 。当然可以,但是因为这似乎是一项仅限教育的任务(谁会在生产中真正使用它?)。否则(doall `(~@l ~x ~@r)) 会解决这个问题。
    • @leetwinski 您将如何在生产中使用它? (内置排序功能?)
    • @Gwang-JinKim 这基本上取决于您的任务和输入大小。一般来说,内置的sort 就足够了,所以如果有必要,你应该考虑优化它。您还可以查看 sorted-sets,以防万一您需要对更改的数据进行排序,这比每次更改时都使用它要好。但同样,除非我们有任何特定于任务的信息,否则我们无法对此进行推理
    【解决方案2】:

    您需要让insert-x 处理常规列表(即'() 或nil)并重写bubble-sort 以使用empty 函数创建与输入相同的类型。

    empty 返回相同类型的空集合:

    (class (empty [1 2]))
    ;; => clojure.lang.PersistentVector
    (class (empty #{1 2}))
    ;; => clojure.lang.PersistentHashSet
    (class (empty '(1 2)))
    ;; => clojure.lang.PersistentList$EmptyList
    

    你的bubble-sort 可以这样看:

    (defn bubble-sort [coll]
      "Insert x into a sorted collection"
      (into (empty coll) (reduce insert-x nil coll)))
    

    这样你就可以摆脱insert-x中的所有类型检查

    【讨论】:

    • @generateme - 但是这个(into (emtpy coll) ...) 构造必须发生在insert-x 的级别,而不是以后。因为否则reduce 会抱怨(例如,当向量输入的中间结果是一个列表时)。
    • 啊,不,我意识到 - 它不能解决向量和列表及其顺序的conj/cons 问题......有没有很好的解决方案?
    • 因为在列表中我必须reverse 但没有向量等等。这就是insert-x 变得如此复杂的原因。我在问自己,是否有更统一的解决方案。
    【解决方案3】:

    谢谢你们!

    我接受了你的两个答案并生成了这个解决方案:

    (defn insert-x [sq x]
      (apply concat (interleave (split-with #(<= % x) sq) [[x] []])))
    
    (defn bubble-sort [sq]
      ((if (vector? sq) vec identity)
       (reduce insert-x () sq)))
    

    测试:

    user=> (bubble-sort '(0 2 8 3 9 7))
    (0 2 3 7 8 9)
    user=> (bubble-sort [0 2 8 3 9 7])
    [0 2 3 7 8 9]
    

    或者可能更好的可读性:

    (defn insert [q x]
      (let [[l r] (split-with #(< % x) sq)] 
        (concat l [x] r)))
    
    (defn bubble-sort [sq]
      ((if (vector? sq) vec identity) 
       (reduce insert () sq)))
    

    或者:

    (defn insert [sq x]
      (let [[l r] (split-with #(< % x) sq)] 
        `(~@l ~x ~@r)))
    
    (defn intoo [x sq] ;; direction neutral `into`
      (into x (into x sq))) 
    
    (defn bubble-sort [sq]
      (intoo (empty sq) (reduce insert () sq)))
    

    需要intoo,因为

    (into () '(1 2 3)) ;;=> '(3 2 1)
    (into () [1 2 3])  ;;=> '(3 2 1)
    (into [] '(1 2 3)) ;;=> [1 2 3]
    (into [] [1 2 3])  ;;=> [1 2 3]
    # `into` uses `conj` underneath!
    

    连续申请两次into修复这个问题:

    (into () (into () '(1 2 3))) ;; '(1 2 3)
    (into () (into () [1 2 3]))  ;; '(1 2 3)
    (into [] (into [] '(1 2 3))) ;; [1 2 3]
    (into [] (into [] [1 2 3]))  ;; [1 2 3]
    

    因此intoointo 的方向中性版本。

    实际上,intoo 最高效的版本可能是:

    (defmacro intoo [x sq]
      (if (vector? x)
          `(into ~x ~sq)
          `(reverse (into ~x ~sq))))
    
    ;; user=> (intoo () '(1 2 3))
    ;; (1 2 3)
    ;; user=> (intoo () [1 2 3])
    ;; (1 2 3)
    ;; user=> (intoo [] [1 2 3])
    ;; [1 2 3]
    ;; user=> (intoo [] '(1 2 3))
    ;; [1 2 3]
    
    ;; or as a function:
    (defn intoo [x & args]
      (if (vector? x)
          (apply into x args)
          (reverse (apply into x args))))
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-07-07
      • 2020-07-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多