我想根据一些函数的结果创建一个列表。在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
在满足一个条件后中断)添加到列表中。
我应该遵循哪种方式实现这一目标?
答案 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
的情况下才是有益的。如果是这种情况,则必须使用concat
或into
:
(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)))
没有一个连贯的示例,就很难更明确了。
一流的功能以及filter
和map
中的打包控制结构为我们提供了一种方法,可以简洁明了地表达算法,将内容隔离到数据结构中。