将向量翻译成地图

时间:2018-09-20 08:58:59

标签: clojure

我有此字段列表(这是Facebook的图API字段列表)。

["a" "b" ["c" ["t"] "d"] "e" ["f"] "g"]

我想从中生成一张地图。遵循约定,如果跟随键向量,则其为键的内部对象。矢量示例可以表示为地图:

{"a" "value"
 "b" {"c" {"t" "value"} "d" "value"}
 "e" {"f" "value"}
 "g" "value"}

到目前为止,我有此解决方案

(defn traverse
  [data]
  (mapcat (fn [[left right]]
            (if (vector? right)
              (let [traversed (traverse right)]
                (mapv (partial into [left]) traversed))
              [[right]]))
          (partition 2 1 (into [nil] data))))

(defn facebook-fields->map
  [fields default-value]
  (->> fields
       (traverse)
       (reduce #(assoc-in %1 %2 nil) {})
       (clojure.walk/postwalk #(or % default-value))))

(let [data ["a" "b" ["c" ["t"] "d"] "e" ["f"] "g"]]
  (facebook-fields->map data "value"))
#=> {"a" "value", "b" {"c" {"t" "value"}, "d" "value"}, "e" {"f" "value"}, "g" "value"}

但是它很胖并且很难遵循。我想知道是否有更优雅的解决方案。

3 个答案:

答案 0 :(得分:3)

这是使用postwalk进行遍历的另一种方式,而不是仅将其用于default-value替换:

(defn facebook-fields->map
  [fields default-value]
  (clojure.walk/postwalk
    (fn [v] (if (coll? v)
              (->> (partition-all 2 1 v)
                   (remove (comp coll? first))
                   (map (fn [[l r]] [l (if (coll? r) r default-value)]))
                   (into {}))
              v))
    fields))

(facebook-fields->map ["a" "b" ["c" ["t"] "d"] "e" ["f"] "g"] "value")
=> {"a" "value",
    "b" {"c" {"t" "value"}, "d" "value"},
    "e" {"f" "value"},
    "g" "value"}

答案 1 :(得分:3)

尝试读取大量嵌套的代码会使我头受伤。更糟糕的是,答案是与postwalk进行“强制匹配”,以一种“由内而外”的方式进行操作。另外,使用partition-all有点浪费,因为我们需要丢弃带有两个非向量的任何对。

对我来说,最自然的解决方案是简单的自上而下的递归。唯一的问题是我们事先不知道是否需要从输入序列的开头删除一个或两个项目。因此,我们不能使用简单的for循环或map

因此,只需将其编写为简单的递归,然后使用if来确定我们是从列表的开头消费1项还是2项。

  1. 如果第二项是一个值,我们消耗一项并添加 :dummy-value进行地图输入。
  2. 如果第二项是向量,我们递归使用 作为地图条目中的值。

代码:

(ns tst.demo.core
  (:require [clojure.walk :as walk] ))

(def data ["a" "b" ["c" ["t"] "d"] "e" ["f"] "g"])

(defn parse [data]
  (loop [result {}
         data   data]
    (if (empty? data)
      (walk/keywordize-keys result)
      (let [a (first data)
            b (second data)]
        (if (sequential? b)
          (recur
            (into result {a (parse b)})
            (drop 2 data))
          (recur
            (into result {a :dummy-value})
            (drop 1 data)))))))

结果:

(parse data) => 
{:a :dummy-value,
 :b {:c {:t :dummy-value}, :d :dummy-value},
 :e {:f :dummy-value},
 :g :dummy-value}

我在结尾处添加了keywordize-keys,只是为了使结果更“ Clojurey”。

答案 2 :(得分:1)

由于您要的是一种更清洁的解决方案,而不是 a 解决方案,并且由于我认为这是一个小巧的小问题,因此这是另一个问题。

(defn facebook-fields->map [coll]
  (into {}
        (keep (fn [[x y]]
                (when-not (vector? x)
                  (if (vector? y)
                    [x (facebook-fields->map y)]
                    [x "value"]))))
        (partition-all 2 1 coll)))