我正在写一个小小的“秘密圣诞老人”程序让我的手弄脏了Clojure,而且我对我的输出感到磕磕绊。
该程序获取一组(圣诞老人),将他们的电子邮件提取到另一个列表,然后随机将收件人分配给圣诞老人。我想我已经掌握了它,但是当我尝试输出map
的结果时,我得到的是#<Fn@dc32d15 clojure.core/map$fn__4549>
,
(ns secret-santas-helper.core
(:require [clojure.pprint :as pprint])
(:gen-class))
(def santas [{:name "Foo" :email "foo@gmail.com"}
{:name "Bar" :email "bar@gmail.com"}
{:name "Baz" :email "baz@gmail.com"}])
(defn pluck
"Pull out the value of a given key from a seq"
[arr k]
(map #(get % k) arr))
(defn find-first
"Find the first matching value"
[f coll]
(first (filter f coll)))
(defn assign-santas
"Iterate over a list of santas and assign a recipient"
[recipients santas]
(let [r (atom recipients)])
(map (fn [santa]
(let [recipient (find-first #(= % (get santa :email)) @recipients)]
(assoc santa :recipient recipient)
(swap! recipients (remove #(= % recipient) recipients))))))
(defn -main []
(let [recipients (shuffle (pluck santas :email))
pairs (assign-santas recipients santas)]
(pprint/pprint pairs)))
答案 0 :(得分:5)
还要注意使用map
的方式。您将返回swap!
的结果,我不相信您的目标。
继续努力让您的版本正确编译和运行。我想为你的问题提供一个替代解决方案,这个解决方案可以减少突变,而是专注于组合集合。
(def rand-santas
"Randomize the current santa list"
(shuffle santas))
(def paired-santas
"Use partition with overlap to pair up all random santas"
(partition 2 1 rand-santas))
(def final-pairs
"Add the first in the list as santa to the last to ensure everyone is paired"
(conj paired-santas (list (last rand-santas) (first rand-santas))))
(defn inject-santas
"Loop through all pairs and assoc the second pair into first as the secret santa"
[pairs]
(map
(fn [[recipent santa]]
(assoc recipent :santa santa))
pairs))
(defn -main []
(pprint/pprint (inject-santas final-pairs)))
答案 1 :(得分:4)
您的assign-santas
函数正在返回地图传感器。当您将map
应用于单个参数时,它将返回一个将在转换上下文中执行该转换的转换器。您很可能打算提供第三个arg santas
来映射。
在assign-santas
函数中,您使用@
来解析不是原子的值。也许你的意思是@r
而不是@recipients
,但是你的let块太快停止了,并且还没有提供r
绑定到函数体的其余部分。
答案 2 :(得分:3)
Lisp(一般而言)和Clojure(具体案例)不同,需要采用不同的方法来解决问题。学习如何使用Clojure解决问题的部分原因似乎是 unlearning 我们在进行命令式编程时已经习得的很多习惯。特别是,当用命令式语言做某事时,我们经常会想到“我如何从一个空集合开始,然后在迭代我的数据时添加元素,以便最终得到我想要的结果?”。这不是好思维,Clojure-wise。在Clojure中,思考过程需要更多地说:“我有一个或多个包含我的数据的集合。如何将函数应用于这些集合,很可能在此过程中创建中间(也可能是丢弃)集合,最终得到我想要的结果集合?“。
好的,让我们切入追逐,然后我们会回去看看为什么我们做了我们做的事情。以下是我修改原始代码的方法:
(def santas [{:name "Foo" :email "foo@gmail.com"}
{:name "Bar" :email "bar@gmail.com"}
{:name "Baz" :email "baz@gmail.com"}])
(def kids [{:name "Tommy" :email "tommy@gmail.com"}
{:name "Jimmy" :email "jimmy@gmail.com"}
{:name "Jerry" :email "jerry@gmail.com"}
{:name "Johny" :email "johny@gmail.com"}
{:name "Juney" :email "juney@gmail.com"}])
(defn pluck
"Pull out the value of a given key from a seq"
[arr k]
(map #(get % k) arr))
(defn assign-santas [recipients santas]
; Assign kids to santas randomly
; recipients is a shuffled/randomized vector of kids
; santas is a vector of santas
(let [santa-reps (inc (int (/ (count recipients) (count santas)))) ; counts how many repetitions of the santas collection we need to cover the kids
many-santas (flatten (repeat santa-reps santas))] ; repeats the santas collection 'santa-reps' times
(map #(hash-map :santa %1 :kid %2) many-santas recipients)
)
)
(defn assign-santas-main []
(let [recipients (shuffle (pluck kids :email))
pairs (assign-santas recipients (map #(%1 :name) santas))]
; (pprint/pprint pairs)
pairs))
我创建了一个单独的孩子集合,这些孩子应该被随机分配给圣诞老人。我也更改了它,因此它创建了assign-santas-main
函数而不是-main
,仅用于测试目的。
唯一更改的功能是assign-santas
。而不是从一个空集合开始,然后尝试改变该集合以积累我们需要的关联,我做了以下:
确定需要多少次santas
集合重复,因此我们至少拥有与孩子一样多的圣诞老人(等等 - 我们会得到它...... :-)。这只是
TRUNC(#_of_kids / #_of_santas) + 1
或者,在Clojure-speak中
`(inc (int (/ (count recipients) (count santas))))`
创建一个集合,santas
集合根据需要重复多次(从步骤1开始)。这是通过
(flatten (repeat santa-reps santas))
这个重复(repeat
)santas
集合santa-reps
次(santa-reps
由步骤1计算)然后flatten
就是它 - 即需要来自所有子集合的元素(尝试执行(repeat 3 santas)
并查看你得到的内容),并且只对所有子集合的元素进行大集合。
然后我们
(map #(hash-map :santa %1 :kid %2) many-santas recipients)
这表示“从每个many-santas
和recipients
集合中获取第一个元素,将它们传递给给定的匿名函数,然后将函数返回的结果累积到新集合中” 。 (新系列,再次 - 我们在Clojure中做了很多)。我们的小匿名函数说“创建一个关联(hash-map
函数),将:santa
的密钥分配给我给出的第一个参数,并将:kid
的密钥分配给第二个参数” 。然后map
函数返回该关联集合。
如果您运行assign-santas-main
功能,则会得到类似
({:kid "jimmy@gmail.com", :santa "Foo"}
{:kid "tommy@gmail.com", :santa "Bar"}
{:kid "jerry@gmail.com", :santa "Baz"}
{:kid "johny@gmail.com", :santa "Foo"}
{:kid "juney@gmail.com", :santa "Bar"})
(我将每个协会放在一个单独的行上 - 当它打印出来时,Clojure并不是那么亲切 - 但你明白了)。如果你再次运行它会得到不同的东西:
({:kid "juney@gmail.com", :santa "Foo"}
{:kid "tommy@gmail.com", :santa "Bar"}
{:kid "jimmy@gmail.com", :santa "Baz"}
{:kid "johny@gmail.com", :santa "Foo"}
{:kid "jerry@gmail.com", :santa "Bar"})
每次不同的跑步等等。
请注意,在assign-santas
的重写版本中,整个函数可能已写在一行上。我在这里只使用了let
来打破santa-reps
的计算和many-santas
的创建,因此很容易看到和解释。
对我来说,我发现Clojure很困难的事情之一(这是因为我仍然在努力攀登学习曲线 - 对我来说,拥有40多年的命令式编程经验和习惯,这是我一条相当陡峭的曲线)只是学习基本功能以及如何使用它们。我定期找到的一些方法是:
map
apply
reduce
I have great difficulty remembering the difference between apply and
reduce. In practice, if one doesn't do what I want I use the other.
repeat
flatten
interleave
partition
hash-map
mapcat
当然还有+
,-
等所有“常见”内容。
我很确定在Clojure上比我更专业的人(不是很大的挑战:-)可以想出一个更快/更好/更酷的方法,但这至少可以给你一个关于如何处理这个问题的不同观点。
祝你好运。