如何防止此clojure函数返回列表

时间:2013-03-10 08:49:40

标签: clojure

我想在clojure中编写一个函数,它会返回一个地图,给出一个形式为“key1 $ value1,key2 $ value2”的字符串。我想出了这个。

(defn get-map
  "Returns a map of key value pairs from a string formatted in the form 'key1$value1,key2$value2'"
  [line]
  (let [result {}]
    (for [item (split line #",")]
      (let [pair (split item #"\$")]
        (assoc result (nth pair 0)
          (if (= (.size pair) 2) (nth pair 1) ""))))))

虽然它有效但这段代码的唯一问题是它会在列表中返回地图。

=>(get-map "key1$value1,key2,value2")
({"key1" "value1"} {"key2" "value2"})

我试图将结果作为第一个let表单的最后一个表达式返回,但结果是一个空映射。

(defn get-map
  "Returns a map of key value pairs from a string formatted in the form 'key1$value1,key2$value2'"
  [line]
  (let [result {}]
    (for [item (split line #",")]
      (let [pair (split item #"\$")]
        (assoc result (nth pair 0)
          (if (= (.size pair) 2) (nth pair 1) ""))))
    result))

=>(get-map "key1$value1,key2,value2")
{}

两个问题 -

  1. 我应该如何修改函数以仅返回地图,而不使用列表包装?
  2. 为什么返回结果作为第一个let表单的最后一个表达式返回一个空映射?
  3. 此外,如果您有建议以更好,更惯用的方式编写相同的功能,那将是值得赞赏的。

3 个答案:

答案 0 :(得分:2)

(defn get-map
  "Returns a map of key value pairs from a string formatted in the form 'key1$value1,key2$value2'"
  [line]
  (->> (clojure.string/split line #",")
       (mapcat #(clojure.string/split % #"\$"))
       (apply hash-map)))

user> (get-map "key1$value1,key2$value2")
{"key1" "value1", "key2" "value2"}

对问题1的回答/提示: 总是返回一个序列。这是Clojure中的列表理解。

对问题2的回答/提示: Clojure的数据结构是不可变的。这意味着在现有地图上关联不会改变现有地图,但会返回一个新地图。

答案 1 :(得分:2)

for表单执行惰性列表解析,因此不适合顺序更新结构。您可以使用for生成配对列表,并使用reduce将它们打包到地图中。或者,使用into进行缩减:

(defn get-map [line]
  (into {}
    (for [item (split line #",")]
      (split item #"\$"))))

使用re-seq的另一种可能的实现方式:

(defn get-map [s]
  (into {} (map #(subvec % 1) (re-seq #"([^\$]+)\$([^,]+),*" s))))

答案 2 :(得分:2)

在Clojure中,for不是一个循环,它是一个列表理解。基本上它需要你的每个项目 收集并将其绑定到您指定的任何名称(在本例中为item),然后进行评估 表达式(在这种情况下为let [pair...)。每个评估的结果都以a。形式返回 序列

例如:

(for [item (split "key1$value1,key2$value2" #",")] 
  item)
;returns: ("key1$value1" "key2$value2")

在你的函数中,for实际创建了列表。由let绑定的名称是不可变的 所以每次for评估它的表达式result仍然是一张空地图。这就是你得到的原因 每个地图中都有一个条目的地图列表。

这是一种生成使用nth的未找到功能的键值对列表的方法 如果缺少$,则提供默认值:

(for [item (split "key1$value1,key2$value2,key3" #",")]
  (let [pair (split item #"\$")] 
    [(nth pair 0) (nth pair 1 "")]))
;returns: (["key1" "value1"] ["key2" "value2"] ["key3" ""])

然后您可以使用into将其转换为地图。这是完整的功能:

(defn get-map   
  "Returns a map of key value pairs from a string formatted in the form 'key1$value1,key2$value2'"
  [line]
  (into {}
    (for [item (split line #",")]
      (let [pair (split item #"\$")] 
        [(nth pair 0) (nth pair 1 "")]))))