在Clojure重组地图。将当前地图中的相应值转换为新地图

时间:2016-03-07 23:47:45

标签: recursion clojure iteration

我想知道实现以下目标的最惯用方法是什么。我刚刚开始使用Clojure,我正在努力寻找操作不依赖于传统迭代结构的数据结构的策略(for,while等)。

如果我有如下地图结构:

(def test-map {:cat1-title "Title1", :cat1-val "Value1", :cat2-title "Title2", :cat2-val "Value2"})

我想将其转换为以下结构:

{"Title1" "Value1", "Title2" "Value2"}

基本上,我想创建一个新映射,其键是* title键的值,值是相应*值键的值。

这样做最好的clojure方法是什么?

我试过的是以下(我不知道它是否会一直有效)。它基本上提取*标题值,然后用提取的* val值

拉链它们
(let [titles
  (vals (filter #(re-matches #".*-title"
                             (str (key %)))
                test-map))
  values
  (vals (filter #(re-matches #".*-val"
                             (str (key %)))
                test-map))]
  (zipmap titles values))

这成功地提取了键和值,但我不确定是否将这些与zipmap一起压缩是最好的方法,或者是最常用的组合方式。

3 个答案:

答案 0 :(得分:2)

对于较大的地图,Zipmap将失败,因为键/值对在地图内没有严格的排序。对于小地图,它们通常遵循您创建它们的顺序,因为小地图被创建为PersistentArrayMaps。大型地图是PersistentHashMap。观察(apply hash-map (range 100)) vs (apply hash-map (range 10))的结果如果您有更大的地图,您的标题将不会与您的-vals对齐

不幸的是,这意味着您确实需要查找与您的标题明确匹配的val。这是一种方法:

(defn transform [m]
  (into {} (for [[k v] m
                 :let [title (name k)]
                 :when (.endsWith title "-title")
                 :let [val-name (clojure.string/replace title #"-title$" "-val")]]
             [v  (m (keyword val-name))])))

对于以标题结尾的每个键,使用相同的前缀查找val,并将其全部放入地图中。

答案 1 :(得分:2)

我会像Timothy一样回答这个问题,但对于生产代码,我认为最好将一些问题分散出来并更加明确:

R version 3.2.3 (2015-12-10)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Ubuntu 14.04.4 LTS

locale:
 [1] LC_CTYPE=en_GB.UTF-8       LC_NUMERIC=C               LC_TIME=en_GB.UTF-8        LC_COLLATE=en_GB.UTF-8     LC_MONETARY=en_GB.UTF-8    LC_MESSAGES=en_GB.UTF-8    LC_PAPER=en_GB.UTF-8      
 [8] LC_NAME=C                  LC_ADDRESS=C               LC_TELEPHONE=C             LC_MEASUREMENT=en_GB.UTF-8 LC_IDENTIFICATION=C       

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] Rserve_1.8-5         big.data.table_0.3.3 data.table_1.9.7    

loaded via a namespace (and not attached):
[1] RSclient_0.7-3 tools_3.2.3 

当然还有一些单元测试:

(ns clj.core
  (:require [clojure.string :as str] )
  (:use tupelo.core)) ; it->

(defn is-title-kw       [arg] (re-matches #".*-title" (name arg)))
(defn title-kw->val-kw  [arg] (it-> arg
                                    (name it)
                                    (str/replace it #"-title" "-val")
                                    (keyword it)))

(defn transform [map-arg]
  (let [title-kws (filter is-title-kw (keys map-arg)) ]
    (into {}
      (for [title-kw title-kws]
        (let [val-kw      (title-kw->val-kw title-kw)
              title-str   (title-kw map-arg)
              val-str     (val-kw   map-arg) ]
          {title-str val-str} )))))

运行测试:

(ns tst.clj.core
  (:use clj.core 
        clojure.test 
        tupelo.core))

(def test-map   { :cat1-title "Title1", :cat1-val "Value1", 
                  :cat2-title "Title2", :cat2-val "Value2" } )

(deftest t-is-title-kw
  (is      (is-title-kw :cat1-title))
  (is      (is-title-kw :cat2-title))
  (is (not (is-title-kw :cat1-val)))
  (is (not (is-title-kw :cat2-val))))

(deftest t-title-kw->val-kw
  (is (= :cat1-val (title-kw->val-kw :cat1-title)))
  (is (= :cat2-val (title-kw->val-kw :cat2-title))))

(deftest t-transform
  (is (=  (transform test-map)
          { "Title1" "Value1", 
            "Title2" "Value2" } )))

答案 2 :(得分:2)

我更愿意使用reduce-kv

(defn transform [items-map]
  (reduce-kv (fn [result k v]
               (if-let [[_ name] (re-find #"^:(.+)-title$" (str k))]
                 (assoc result v (items-map (keyword (str name "-val"))))
                 result))
             {} items-map))

在repl中:

user> (def test-map {:cat-1-title "Title1", :cat-1-val "Value1", 
                     :cat2-title "Title2", :cat2-val "Value2", 
                     :cat3-title "Title3", :cat3-val "Value3"})
#'user/test-map
user> (transform test-map)
{"Title1" "Value1", "Title2" "Value2", "Title3" "Value3"}