【问题标题】:Converting a list of lists into a map of lists of lists in Clojure在 Clojure 中将列表列表转换为列表列表的映射
【发布时间】:2010-10-15 17:02:36
【问题描述】:

我有一个列表列表,并希望将其放入映射中,其中键是列表中的常见值之一(本例中为动物名称)。我知道如何使用 into {} 和 for 从列表中创建地图,但这并不是我想要的。我希望地图中的键(动物名称)引用这些值的列表。

我创建了一个列表列表

(def animals (list '("tiger" "fur" "yellow & black stripes") '("tiger" "weight" "150") '("tiger" "home" "India") '("elephant" "skin" "gray") '("elephant" "weight" "1500") '("elephant" "home" "Africa") '("frog" "skin" "green") '("frog" "diet" "insects")))

动物

(("tiger" "fur" "yellow & black stripes") ("tiger" "weight" "150") ("tiger" "home" "India") ("elephant" "skin" "gray") ("elephant" "weight" "1500") ("elephant" "home" "Africa") ("frog" "skin" "green") ("frog" "diet" "insects"))

这并不完全符合我的要求(但几乎)

(def animal-map (into {} (for [[name attribute value] animals] [name (list attribute value)])))

当我希望每个键有多个列表时,这会导致每个键只有一个列表

动物地图(列表地图)

{"tiger" ("home" "India"), "elephant" ("home" "Africa"), "frog" ("diet" "insects")}

这是我想要结束的。

动物地图(列表列表的地图)

{"tiger" ("fur" "yellow & black stripes") ("weight" "150") ("home" "India"),   
"elephant" ("skin" "gray") ("weight" "1500") ("home" "Africa"),   
"frog" ("skin" "green") ("diet" "insects")}  

【问题讨论】:

    标签: list clojure maps


    【解决方案1】:

    关于您想要的结果,您发布的最终地图并没有按照您的想法进行。在这张地图中:

    {"tiger" ("fur" "yellow & black stripes") ("weight" "150") ("home" "India")}
    

    这不是一个有一个键和三个值的映射,这是一个有两个键("tiger"("weight" "150"))和两个值的映射。

    以下内容并没有直接回答您的问题,而是关于惯用的 Clojure 的一句话。如果您为手头的工作选择合适的数据结构,您将在 Clojure 中度过更轻松的时光。

    对于您的初始数据,您可能应该使用向量而不是列表,除非您有充分的理由使用列表。向量在许多操作(例如随机访问)中表现更好;你不需要像做列表那样引用向量;和向量表明您正在处理数据而不是代码。

    如果您的数据必须从前面而不是后面增长,或者如果您将源代码生成为数据(例如在宏中),则列表是合适的。

    [["tiger" "fur" "yellow & black stripes"]
     ["tiger" "weight" "150"]
     ["tiger" "home" "India"]
     ...]
    

    对于您的最终数据,请考虑使用字符串到映射的映射,而不是字符串到列表的映射。使用assoc-in 创建地图地图很容易,因为它会为您创建中间地图。然后您可以使用get-in 访问数据的子键。

    要在字符串映射到列表中访问动物的属性,您必须对属性列表进行线性搜索。这将难以编码并且性能不佳。

    user> (def animal-map (reduce (fn [m [animal k v]]
                                    (assoc-in m [animal k] v))
                                  {} animals))
    #'user/animal-map
    user> animal-map
    {"frog" {"diet" "insects", "skin" "green"}, 
     "elephant" {"home" "Africa", "weight" "1500", "skin" "gray"}, 
     "tiger" {"home" "India", "weight" "150", "fur" "yellow & black stripes"}}
    
    user> (get-in animal-map ["frog" "diet"])
    "insects"
    

    最后,我不确定这些数据是从哪里来的,但可以考虑为其中一些键使用关键字而不是字符串。它可能适合也可能不适合动物的名字,但它几乎肯定适合动物的属性(体重、家、饮食)。

    如果你的key是关键字,你甚至不需要get-in,你可以直接将关键字用作函数,或者使用->。你不能用字符串来做到这一点。

    user> (def animal-keyword-map (reduce (fn [m [animal k v]]
                                            (assoc-in m [(keyword animal) (keyword k)] v))
                                          {} animals))
    #'user/animal-keyword-map
    user> animal-keyword-map
    {:frog {:diet "insects", :skin "green"}, 
     :elephant {:home "Africa", :weight "1500", :skin "gray"}, 
     :tiger {:home "India", :weight "150", :fur "yellow & black stripes"}}
    user> (:weight (:tiger animal-keyword-map))
    "150"
    user> (-> animal-keyword-map :tiger :weight)
    "150"
    

    【讨论】:

      【解决方案2】:

      通过将您的列表合并到 {} 中,您可以替换重复键。

      (reduce (fn [m [k & vals]] (assoc m k (conj (m k) vals))) {} animals)
      

      【讨论】:

      • 我想你明白了。看起来它有效。谢谢你。我现在将尝试了解它为什么会起作用。
      【解决方案3】:

      为了避免后面的条目替换以前的条目,您需要创建一堆地图,然后以受控方式将它们合并在一起。万岁!我们有 merge-with 来做这份工作!

      首先,我们将每个列表中的第一个字符串作为键,将其余字符串作为值,将列表转换为一系列映射。

      (map #(hash-map (first %) (rest %)) animals)
      
      ({"tiger" ("fur" "yellow & black stripes")} 
       {"tiger" ("weight" "150")} 
       {"tiger" ("home" "India")} 
       {"elephant" ("skin" "gray")} 
       {"elephant" ("weight" "1500")} 
       {"elephant" ("home" "Africa")} 
       {"frog" ("skin" "green")} 
       {"frog" ("diet" "insects")})
      

      然后让我们尝试将它们与 list 合并,因为这是首先想到的。

      (apply merge-with list (map #(hash-map (first %) (rest %)) animals))
      
      {"frog" (("skin" "green") ("diet" "insects")), "elephant" ((("skin" "gray") 
      ("weight"    "1500")) ("home" "Africa")), "tiger" ((("fur" "yellow & black stripes") 
      ("weight" "150")) ("home" "India"))}
      

      虽然嵌套太深,但看起来更像它。不用担心,我们可以通过将列表创建移动到内部映射来防止嵌套:

      (apply merge-with concat (map #(hash-map (first %) (list (rest %))) animals))
      
      {"frog" (("skin" "green") ("diet" "insects")), "elephant" (("skin" "gray") ("weight" 
      "1500") ("home" "Africa")), "tiger" (("fur" "yellow & black stripes") ("weight" "150")
       ("home" "India"))}
      

      我无法完全匹配所需的输出,因为映射只能有一个键值。到“老虎”需要映射到对列表。而不是一对以上。

      【讨论】:

      • 这是一个很好的尝试,但结果与我想要的结果不完全匹配。我希望所有列表都在同一级别,而不是子列表。
      • 需要弄清楚合并函数使用什么。 concat 也产生相同的输出。
      • 已修复 :) 并添加了提出它的过程。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-02-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-10-23
      • 1970-01-01
      • 2017-08-24
      相关资源
      最近更新 更多