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功能,但只添加具有最大销售额的城市。我不认为这是最有效的方式。
答案 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是一种功能语言,它只是创造新东西。