Clojure:map函数没有返回我可以评估的东西

时间:2015-12-31 15:14:14

标签: dictionary clojure

我正在写一个小小的“秘密圣诞老人”程序让我的手弄脏了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)))

3 个答案:

答案 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。而不是从一个空集合开始,然后尝试改变该集合以积累我们需要的关联,我做了以下:

  1. 确定需要多少次santas集合重复,因此我们至少拥有与孩子一样多的圣诞老人(等等 - 我们会得到它...... :-)。这只是

    TRUNC(#_of_kids / #_of_santas) + 1

  2. 或者,在Clojure-speak中

    `(inc (int (/ (count recipients) (count santas))))`
    
    1. 创建一个集合,santas集合根据需要重复多次(从步骤1开始)。这是通过

      完成的

      (flatten (repeat santa-reps santas))

    2. 这个重复(repeatsantas集合santa-reps次(santa-reps由步骤1计算)然后flatten就是它 - 即需要来自所有子集合的元素(尝试执行(repeat 3 santas)并查看你得到的内容),并且只对所有子集合的元素进行大集合。

      1. 然后我们

        (map #(hash-map :santa %1 :kid %2) many-santas recipients)

      2. 这表示“从每个many-santasrecipients集合中获取第一个元素,将它们传递给给定的匿名函数,然后将函数返回的结果累积到新集合中” 。 (新系列,再次 - 我们在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上比我更专业的人(不是很大的挑战:-)可以想出一个更快/更好/更酷的方法,但这至少可以给你一个关于如何处理这个问题的不同观点。

        祝你好运。