代码之家  ›  专栏  ›  技术社区  ›  fl00r

矢量地图化

  •  1
  • fl00r  · 技术社区  · 7 年前

    我有一个字段列表(这是Facebook的graph API字段列表)。

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

    我想从中生成一张地图。约定是following,如果在键向量之后,则它是键的内部对象。示例向量可以表示为地图:

    {"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 回复  |  直到 7 年前
        1
  •  1
  •   madstap    7 年前

    因为你要求的是更干净的解决方案而不是

    (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)))
    
        2
  •  3
  •   Taylor Wood    7 年前

    这里有另一种方法可以使用 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"}
    
        3
  •  3
  •   Alan Thompson    7 年前

    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

    推荐文章