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 解决方案是什么以及(更重要的)导致该解决方案的想法。
更新
整数只是举例,它们也可以是关键字或字符串,不一定是有序的,尽管显然必须保留顺序。
通过嵌套,我的意思是它不会像 [1 [2 [3 [4 5] 6] 7] 8]
但更像 [1 [2 3] 4 [5] 6 [7 8 9]]
- shallow.
我敢肯定还有其他方法可以做到这一点,但这是一种方法。
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
,因为 filter
和 remove
return 列表,而不是向量。
许多 Clojure 核心库函数的一个重要特性是能够处理惰性(可能是无限的)序列;因此,我认为一个好的(惯用的)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)
尾序列实际上是同一个对象。
我有一个数据转换让我有点受阻。我无法在 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 解决方案是什么以及(更重要的)导致该解决方案的想法。
更新
整数只是举例,它们也可以是关键字或字符串,不一定是有序的,尽管显然必须保留顺序。
通过嵌套,我的意思是它不会像
[1 [2 [3 [4 5] 6] 7] 8]
但更像[1 [2 3] 4 [5] 6 [7 8 9]]
- shallow.
我敢肯定还有其他方法可以做到这一点,但这是一种方法。
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
,因为 filter
和 remove
return 列表,而不是向量。
许多 Clojure 核心库函数的一个重要特性是能够处理惰性(可能是无限的)序列;因此,我认为一个好的(惯用的)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)
尾序列实际上是同一个对象。