将值累积到列表

时间:2019-02-27 03:29:51

标签: clojure

我想根据一些函数的结果创建一个列表。在Java(我的背景)中,我会做类似的事情:

List<String> messages = ...

if(condition 1)
   messages.add(message 1);

if(condition 2)
   messages.add(message 2);

...

if(condition N)
   messages.add(message N);

在Clojure中,我认为我需要使用let创建一个列表,如下所示(只是一个虚拟示例):

(let [result
  (vec
    (if (= 1 1) "message1" "message2")
    (if (= 1 0) "message3" "message4"))]
result)

我还检查了cond,但我需要考虑所有验证(并将cond在满足一个条件后中断)添加到列表中。

我应该遵循哪种方式实现这一目标?

6 个答案:

答案 0 :(得分:5)

如果您希望像Java示例中那样有条件地添加它们,可以使用cond->,它不会短路:

(let [messages []]
  (cond-> messages ; Conditionally thread through conj
    (= 1 1) (conj "Message1")
    (= 0 1) (conj "Message2")
    (= 0 0) (conj "Message3")))

=> ["Message1" "Message3"]

但是,如果您想像第二个示例所建议的那样有条件地添加一个或另一个,则可以只使用带有一些conj表达式的普通if

(let [messages []]
  (conj messages
    (if (= 1 1) "Message1" "Message2")
    (if (= 0 1) "Message3" "Message4")))

=> ["Message1" "Message4"]

我会注意到您最初的尝试几乎奏效。可以使用vec代替vector,也可以只使用向量常量:

(let [messages [(if (= 1 1) "Message1" "Message2")
                (if (= 1 0) "Message3" "Message4")]]
  messages)

=> ["Message1" "Message4"]

尽管如此,这只有在您尚未添加要添加的messages的情况下才是有益的。如果是这种情况,则必须使用concatinto

(let [old-messages ["old stuff"]
      messages [(if (= 1 1) "Message1" "Message2")
                (if (= 1 0) "Message3" "Message4")]]
  (into old-messages messages))

=> ["old stuff" "Message1" "Message4"]

答案 1 :(得分:3)

看看cond->

例如,您的Java示例可以写为:

(cond-> (some-fn-returning-messages)
  (= 1 1) (conj "message1")
  (= 1 2) (conj "message2")
  ...
  (= 1 n) (conj "messagen"))

答案 2 :(得分:2)

我看到一些指向cond->宏的答案,该宏似乎与您的请求最接近,因为它与问题中概述的样式最接近。

根据您所拥有的条件数量,您的问题似乎是仅使用filter的一个很好的候选人。

(def nums (range 10))
(filter #(or (even? %) (= 7 %)) nums)

如果您有一堆条件(函数),并且将它们“或”在一起很麻烦,则可以使用some-fn

0-19之间的数字可以是偶数,可以被7整除,大于17,或者正好等于1。我知道这个愚蠢的例子,只是想显示一个简单的用例。

(filter (some-fn
         even?
         #(zero? (mod % 7))
         #(> % 17)
         #(= 1 %))
        (range 20))

答案 3 :(得分:1)

看起来每个人都有相同的想法!我确实使用了关键字:

(ns tst.demo.core
  (:use tupelo.core demo.core tupelo.test))

(defn accum
  [conds]
  (cond-> []   ; append to the vector in order 1,2,3
    (contains? conds :cond-1) (conj :msg-1)
    (contains? conds :cond-2) (conj :msg-2)
    (contains? conds :cond-3) (conj :msg-3)))

(dotest
  (is= [:msg-1]                 (accum #{:cond-1}))
  (is= [:msg-1 :msg-3]          (accum #{:cond-1 :cond-3}))
  (is= [:msg-1 :msg-2]          (accum #{:cond-2 :cond-1}))
  (is= [:msg-2 :msg-3]          (accum #{:cond-2 :cond-3}))
  (is= [:msg-1 :msg-2 :msg-3]   (accum #{:cond-3 :cond-2 :cond-1 })) ; note sets are unsorted
)

如果需要更多电源,可以使用cond-it-> from the Tupelo library。它通过 两者 条件 动作形式插入目标值,并使用特殊符号{{1 }}显示放置线程值的位置。此修改后的示例显示了第四个条件,其中“ msg-3嫉妒msg-1” 并始终将其引导出结果:

it

答案 4 :(得分:1)

不一定与您的用例相关,也不一定与主流解决方案相关,但我偶尔会喜欢cl-format的条件表达式:

(require '[clojure.pprint :refer [cl-format]])
(require '[clojure.data.generators :as g])
(cl-format nil 
           "~:[He~;She~] ~:[did~;did not~] ~:[thought about it~;care~]"
           (g/boolean) (g/boolean) (g/boolean))

答案 5 :(得分:0)

典型情况是验证一条数据以产生错误列表。

我将构造一个将条件映射到消息的表:

 (def error->message-table
  {condition1 message1
   condition2 message2
   ...})

请注意,条件是函数。由于我们永远无法正确地通过值识别函数,因此您可以使该表成为一对序列。

无论如何实现表,我们要做的就是收集适用于谓词的消息:

(defn messages [stuff]
  (->> error->message-table
       (filter (fn [pair] ((first pair) stuff)))
       (map second)))

没有一个连贯的示例,就很难更明确了。

一流的功能以及filtermap中的打包控制结构为我们提供了一种方法,可以简洁明了地表达算法,将内容隔离到数据结构中。