Clojure:如何在列表中进行复杂迭代?

4

我想使用一个数字20和一个列表'(1 2 3 4 5 6 7 8 9 10),返回一个集合,其中包含原始列表中每个值的两个值:原始值与20除以该值时的余数。如果能将原始值与余数相关联,那就太好了,这样我就可以轻松地检索产生特定余数的每个数字。基本上,我需要一个名为func的函数:

user=> (func 20 '(1 2 3 4 5 6 7 8 9 10))
'(:0 1, :0 2, :2 3,... :20 0)

我很难弄清如何遍历这个列表,尤其是独立使用列表中的元素以及如何返回20被哪个元素除以后的余数。请问有谁能帮帮我吗?

我的想法是在一个计算平方根的程序中使用类似这样的内容。如果数字按余数键入,那么我可以查询集合以获取所有将输入分为0余数的数字。


这是我最初的处理方式。

;; My idea on the best way to find a square root is simple.
;; If I want to find the square root of n, divide n in half
;; Then divide our initial number (n) by all numbers in the range 0...n/2 
;; Separate out a list of results that only only return a remainder of 0.
;; Then test the results in a comparison to see if the elements of our returned 
;; list when squared are equal with the number we want to find a square root of.
;; First I'll develop a function that works with evens and then odds

(defn sqroot-range-high-end [input] (/ input 2))
(sqroot-range-high-end 36) ; 18

(defn make-sqrt-range [input] (range (sqroot-range-high-end (+ 1 input))))
(make-sqrt-range 36) ; '(0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18)

(defn zero-culler [input] (lazy-seq (remove zero? (make-sqrt-range input))))
(zero-culler 100) ; '(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18)

(defn odd-culler [input] (lazy-seq (remove odd? (zero-culler input))))
(odd-culler 100) ; '(2 4 6 8 10 12 14 16 18)

;;the following is where I got stuck
;;I'm new to clojure and programming,
;;and am just trying to learn in a way that I understand

(defn remainder-culler [input]
  (if
    (/ input (first odd-culler (input)))
  input)
  (recur (lazy-seq (input)))
)

(remainder-culler 100)

2
你不需要在lazy-seq中包装remove,它已经是lazy的了。 - Diego Basch
2个回答

7

欢迎来到Clojure!

一个快速的提示: [1 2 3 4 5 6 7 8 9 10] 是一个向量,而不是列表。

当你说“keyed to”时,这让我想到你正在寻找返回映射的东西。

映射

这就是 Clojure 的备忘单 非常有用的地方。你正在尝试从函数创建一个映射。映射是一种集合,因此如果你转到备忘单上的“集合”部分,并向下滚动到映射,你将看到许多类别。你想要创建一个,所以在那个列表中查找,并探索到Clojure文档的链接。

这将带您到非常方便的 group-by 函数。你给它一个函数和一个集合,它返回一个映射,其中包含该集合中的所有项,由应用f到每个值的结果键入。

> (group-by #(rem 20 %) [1 2 3 4 5 6 7 8 9 10])
{0 [1 2 4 5 10], 2 [3 6 9], 6 [7], 4 [8]}

如果您想让键成为实际的关键字,您需要修改匿名函数以返回关键字:
> (group-by #(keyword (str (rem 20 %))) [1 2 3 4 5 6 7 8 9 10])
{:0 [1 2 4 5 10], :2 [3 6 9], :6 [7], :4 [8]}

请注意,返回值是向量。这是因为您不能从单个键映射到两个项(即,映射编码函数)。
迭代/列表推导
现在,所有这些都说了,我不确定这是否是您要寻找的内容。您问道:“有人能帮助我理解如何独立使用列表元素,然后如何返回20被除以的元素以及它是否返回余数?”对于您的目的,您可以将其视为迭代,但实际上它正在进行列表推导。
(for [i [1 2 3 4 5 6 7 8 9 10]]
    (list (rem 20 i) i))

再次强调,如果您想使用关键字而不是值,可以这样做:

(for [i [1 2 3 4 5 6 7 8 9 10]]
    (list (keyword (str (rem 20 i))) i))

在这种情况下,正如Kyle所指出的那样,你可以使用map:
(map #(list (keyword (str (rem 20 %)))
            %)
     [1 2 3 4 5 6 7 8 9 10])

如果您不喜欢这些嵌套结构,可以在它们上面使用flatten筛选器 但我仍然不确定您是否希望解决此问题。在您的评论中,您有“分离仅返回余数为0的结果列表”的情况。对我来说,这听起来像是filter的一个案例,它还具有惰性的附加好处。
> (filter #(zero? (rem 20 %)) [1 2 3 4 5 6 7 8 9 10])
(1 2 4 5 10)

瞧,它只会输出满足你需求的原始集合中的元素。

希望这能有所帮助。虽然它不能让你完全达到目标,但我希望它能为你提供一些不错的工具,你可以用它们来实现目标。你有选择!在学习过程中,请尝试多个选项。如果你在某处读到一个比另一个更好,看看是否能弄清楚为什么。


谢谢你,这非常有帮助。 - dmbennett

2
(map #(vector (rem 20 %) %) (range 1 21))
;; => ([0 1] [0 2] [2 3] ... [1 19] [0 20])

我现在非常困难,不知道如何遍历列表

要进行迭代,请使用高阶函数,例如clojure.core/map

返回20被除以的元素,以及如果存在余数则返回该余数

您想要返回两个内容。提供给clojure.core/mapfn可以通过返回一个包含两个元素的向量来实现。

要确定余数,请使用rem

以其返回的余数为键

因为可能有多个操作返回相同的余数,所以我们不能创建一个映射({})并使用余数作为键 - 这将导致冲突。

clojure.core/range用于创建分母的集合。

为了更简洁并使用clojure.core/juxt,以下代码可以达到同样的效果:

(map (juxt (partial rem 20) identity) (range 1 21))

最后,为了参数化整个过程并返回一个惰性序列:
(defn rem-denominator
  [n]
  (map (juxt (partial rem n) identity)
       (iterate inc 1)))

(take 5 (rem-denominator 20))
;; => ([0 1] [0 2] [2 3] [0 4] [0 5])
(take 20 (rem-denominator 20))
;; => ([0 1] [0 2] [2 3] ... [1 19] [0 20])

非常好的代码,但您可能需要为其编写一份说明。 - TheBat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接