哈希映射不显示为哈希映射

时间:2017-11-12 01:48:03

标签: clojure

clojure新手。尝试使用java后台解决以下问题。我需要将表转换为哈希映射,将产品映射到销售产品的所有城市。所以输出应该是。

{"Pencil": ("Oshawa" "Toronto")
 "Bread": ("Ottawa" "Oshawa" "Toronto")}


(def table [
        {:product "Pencil"
        :city "Toronto"
        :year "2010"
        :sales "2653.00"}
        {:product "Pencil"
        :city "Oshawa"
        :year "2010"
        :sales "525.00"}
        {:product "Bread"
        :city "Toronto"
        :year "2010"
        :sales "136,264.00"}
        {:product "Bread"
        :city "Oshawa"
        :year "nil"
        :sales "242,634.00"}
        {:product "Bread"
        :city "Ottawa"
        :year "2011"
        :sales "426,164.00"}])

这是我到目前为止所拥有的。我将此代码写入repl。

(let [product-cities {}]
(for [row table]
  (if (= (contains? product-cities (keyword (row :product))) true)
    (println "YAMON") ;;To do after. Add city to product if statement is true
    (into product-cities {(keyword (row :product)) (str (row :city))}))))

然而,结果如下:

({:Pencil "Toronto"}
 {:Pencil "Oshawa"}
 {:Bread "Toronto"}
 {:Bread "Oshawa"}
 {:Bread "Ottawa"})

我的if语句一直返回false。我看到许多哈希映射周围有半圆括号。我无法弄清楚为什么它没有返回一个hashmap以及为什么有很多hashmap?感谢

编辑:

问题2: 将表转换为哈希映射,将产品映射到销售额最高的城市。例如,输出应如下所示:

{"Pencil": "Toronto"
 "Bread": "Ottawa"}

我认为需要采取不同的策略而不是建立价值,但这就是我所想的:

(reduce (fn [product-cities {:keys [product city sales]}]
      (update-in product-cities [product] (fnil conj []) {(keyword city) sales}))
    {}
    table)

这会产生以下输出:

{"Bread"
 [{:Toronto "136,264.00"}
  {:Oshawa "242,634.00"}
   {:Ottawa "426,164.00"}],
 "Pencil"
 [{:Toronto "2653.00"}
  {:Oshawa "525.00"}]}

然后我可以再次使用reduce功能,但只添加具有最大销售额的城市。我不认为这是最有效的方式。

2 个答案:

答案 0 :(得分:5)

你似乎对clojure如何运作有一些误解。来自java,很难知道如何做东西,只是因为它是如此不同。这个小问题非常适合作为如何建立价值的介绍,我将尝试解释解决方案的每个部分。

不变性

它是java中的常用模式,用于定义将保存最终结果的变量,然后在添加到该变量时循环访问某些变量。

这是您尝试对product-cities本地人做的事情。当您在clojure中定义let的本地时,它永远不会改变,因此要构建一个值,您需要另一种模式。

向地图添加内容

首先让我们来看看如何向地图添加内容"。在clojure中你实际做的是用添加的东西制作一张新地图。旧地图没有变化。我们有时候会将其称为添加到地图中,但这只是简单地为"创建添加了该事物的新地图"。

assoc获取地图,键和值,并返回一个新的地图,其中包含在键处添加的值。如果那里已有值,则会被覆盖。我们希望每个键都有多个东西,所以在这种情况下它不是正确的。

update类似,但它需要一个映射,一个键,一个函数和该函数的可选参数。它将使用已经在key处的值作为第一个参数和(如果存在)所提供的参数调用该函数。返回的映射将具有函数的返回值作为键的新值。一些例子可能会使这更清楚。

;; The function - is called with the old value of :foo and the argument supplied
;; (- 10 3)
(update {:foo 10} :foo - 3) ;=> {:foo 7}

如果键上没有任何内容,则将使用nil作为第一个参数调用该函数。那是nil的意思,没有。

(update {} :foo + 5) ;=> Null pointer exception. Same as (+ nil 5)

空指针并不好。有一个避免它们的技巧。 fnil是一个带有函数和参数的高阶函数。它返回一个新函数,它将nil参数替换为提供的参数。

;; The nil here is substituted with 0, so no NPE
((fnil + 0) nil 5) ;=> 5

;; If the arg is not nil, the zero is not used.
((fnil + 0) 5 5) ;=> 10

;; So now we can update the value at :foo regardless of whether it's already there.
(update {} :foo (fnil + 0) 5) ;=> {:foo 5}

conj为集合添加了一些东西。如果该集合是一个向量,它会在最后添加它。

(conj [] :foo) ;=> :foo

(conj [:foo] :bar) ;=> [:foo :bar]

要向地图添加内容,我们可以将它们组合在一起:

(update {} "product" (fnil conj []) "city") ;=> {"product ["city"]"}

(update {"product" ["city"]} "product" (fnil conj []) "another city")
;;=> {"product" ["city" "another city"]}

建立值

我们需要以某种方式做一些循环。然而,clojure中的for是列表解析,而不是for循环。它将返回一系列事物,因此当您想要建立一个值时,它不适合使用。

一种方法是使用loop

使用循环定义绑定名称与其初始值配对。循环中的绑定可以被认为是"在循环期间会发生变化的事情"。

loop和传统循环之间的一个区别是,要突破循环,您根本不做任何事情,并且您需要专门使用recur来保持循环。

;; the bindings are pairs of names and initial values
(loop [product-cities {} ; This is the accumulator that we're gonna build up.
       rows table] ; and these are the rows, we're gonna go through them one by one.

  ;; Here's the base case, if there are no rows left we return the accumulator.
  (if (empty? rows)
    product-cities

    ;; If there are rows left, we need to add to the accumulator.
    (let [row (first rows)
          city (:city row)
          product (:product row)
          new-accumulator (update product-cities product (fnil conj []) city)]

      ;; recur takes as many arguments as the pairs we defined
      ;;  and "jumps" back to the loop
      (recur new-accumulator (rest rows)))))
;;=> {"Pencil" ["Toronto" "Oshawa"], "Bread" ["Toronto" "Oshawa" "Ottawa"]}

对于某些destructuring,这可以更好。

(loop [product-cities {}
       [{:keys [city product] :as row} & rows] table]
  (if (nil? row)
    product-cities
    (recur (update product-cities product (fnil conj []) city) rows)))
;;=> {"Pencil" ["Toronto" "Oshawa"], "Bread" ["Toronto" "Oshawa" "Ottawa"]}

loop在正常的clojure中使用不多。它过于笼统,通常你想要更具体的东西。就像在这种情况下一样,你想要在循环遍历一系列事物中的每一件事物时建立一个值。这是reduce的作用。

Reduce有三个参数,一个函数,一个初始值和一个集合。该功能称为"减少功能"有两个论点;到目前为止的累计值和一个项目。对于集合中的每个项目都会调用一次。

所以最终的实现变为:

(reduce (fn [product-cities {:keys [product city]}]
          (update product-cities product (fnil conj []) city))
        {}
        table)

编辑:

关于您对其他答案的评论。在clojure 1.7.0中添加了update,因此您可以在旧版本上进行推理。您可以使用update-in代替(尽管您应该考虑升级)。除了键在向量中之外,它的调用方式完全相同。

(reduce (fn [product-cities {:keys [product city]}]
          (update-in product-cities [product] (fnil conj []) city))
        {}
        table)

答案 1 :(得分:0)

您需要逐个浏览表格并累积(conj)产品下的城市:

(reduce
    (fn [acc {:keys [product city]}]
      (update acc product conj city))
    {}
    table)
;; => {"Pencil" ("Oshawa" "Toronto"), "Bread" ("Ottawa" "Oshawa" "Toronto")}

使用更新函数(在这种情况下为conj)可能有点棘手,所以这里是update的另一种形式:

(update acc product (fn [cities] 
                      (conj cities city)))

而不是{}我开始使用:

{"Pencil" []
 "Bread" []}

这可能会让人们更容易看到表格中的每个条目update正在更新产品密钥(“铅笔”或“面包”),方法是将最新的城市放在最后(这就是conj所做的)序列。当它正常工作时,我只是替换为{},使用update将插入新密钥的事实,如果不存在的话。

我认为for是生成性的。它需要一个序列作为输入,并在每一步产生一个新的东西 - 因此你最终得到一系列新的东西。 无法更新product-cities

reduce对于您想要做的事情更有用,因为您获得的“累加器”可以在每一步稍微修改一下。实际上你每次都会创建一个新的'累加器',通过修改传入的那个,所以实际上你根本就没有修改任何东西:Clojure是一种功能语言,它只是创造新东西。

相关问题