Functional/idiomatic 在 Clojure 中表达这个 Python 的方式

Functional/idiomatic way to express this Python in Clojure

我有一个数据转换让我有点受阻。我无法在 Clojure 中表达自己,甚至我的 Python,我很流利,仍然感觉很恶心。

我需要这样的数据结构:

[1, 2, [3, 4], 5, 6]

生成这样的结构:

[[1, 2, 3, 5, 6]
 [1, 2, 4, 5, 6]]

其中每个子集合使用迄今为止积累的项目创建一个新集合。我只期望单层嵌套。

我的 python 尝试是这样的:

def foo(path, acc_list=[[]]):
    for val in path:
        if not isinstance(val, list):
            for acc in acc_list:
                acc.append(val)
        else:
            for subval in val:
                new = acc_list[0][:]
                new.append(subval)
                acc_list.append(new)
            del acc_list[0]
    return acc_list

foo([1, 2, [3, 4], 5, 6])
# => [[1, 2, 3, 5, 6], [1, 2, 4, 5, 6]]

我想知道 Clojure 解决方案是什么以及(更重要的)导致该解决方案的想法。

更新

我敢肯定还有其他方法可以做到这一点,但这是一种方法。

user.core=> (def x [1, 2, [3, 4], 5, 6])
#'user.core/x
user.core=> (defn create-vecs [x]
         =>   (let [sub (first (filter vector? x))
         =>         all (remove vector? x)]
         =>   (vec (map #(vec (sort (conj all %))) sub))))
#'user.core/create-vecs
user.core=> (create-vecs x)
[[1 2 3 5 6] [1 2 4 5 6]]

基本上,您是获取向量元素和减去向量元素的向量的其余部分,然后在它们之上映射以使用 conj 创建两个新向量。你需要额外的 vec,因为 filterremove return 列表,而不是向量。

许多 Clo​​jure 核心库函数的一个重要特性是能够处理惰性(可能是无限的)序列;因此,我认为一个好的(惯用的)Clojure 解决方案将能够正确扩展包含无限惰性序列子序列的输入。例如:

[:a :b (range) :c]

应该扩展到

((:a :b 0 :c) (:a :b 1 :c) (:a :b 2 :c) (:a :b 3 :c) ...)

如果顶层序列也可以是无限的并且可以延迟处理,那就太好了,但是,我认为这对于这个问题来说是不可能的。 (但如果其他人能想出一种方法来实际处理这个问题,我会感到非常惊喜!)

这是我的解决方案:

(defn expand-subseqs [[x & xs]]
  (when-not (nil? x)
    (let [heads (if (sequential? x) x [x])
          tails (expand-subseqs xs)]
      (if-not tails (map vector heads)
        (for [z tails, y heads] (cons y z))))))

这里的直觉是,您首先递归地处理输入序列的尾部,然后将当前头部的每个可能值添加到每个可能的尾部。

一些示例输出:

user=> (expand-subseqs [1, 2, [3, 4], 5, 6])
((1 2 3 5 6) (1 2 4 5 6))
user=> (take 5 (expand-subseqs [:a :b (range) :c [true false]]))
((:a :b 0 :c true) (:a :b 1 :c true) (:a :b 2 :c true) (:a :b 3 :c true) (:a :b 4 :c true))

这个解决方案的一个好处是,通过使用 cons,我们实际上为每个结果重用表示尾部序列的对象,而不是为每个排列复制整个序列。例如,在上面的最后一个示例中,五个输出中的每个输出中的 (:c true) 尾序列实际上是同一个对象。