我有一些看起来像这样的东西:
{:person-123 {:xxx [1 5]
:zzz [2 3 4]}
:person-456 {:yyy [6 7]}}
我想改造它,看起来像这样:
[{:person "123" :item "xxx"}
{:person "123" :item "zzz"}
{:person "456" :item "yyy"}]
这是类似flatten
的问题,我知道我可以通过调用name
将关键字转换为字符串,但我无法通过方便的方式来实现这一点。< / p>
我就是这样做的,但它似乎不太优雅(嵌套for
循环,我在看着你):
(require '[clojure.string :refer [split]])
(into []
(apply concat
(for [[person nested-data] input-data]
(for [[item _] nested-data]
{:person (last (split (name person) #"person-"))
:item (name item)}))))
答案 0 :(得分:7)
你的解决方案也不错,对于嵌套for循环,for
实际上支持嵌套循环,所以你可以把它写成:
(vec
(for [[person nested-data] input-data
[item _] nested-data]
{:person (last (clojure.string/split (name person) #"person-"))
:item (name item)}))
个人而言,我倾向于专门为此目的使用for
(嵌套循环),否则我通常会更熟悉map
等。但这只是个人偏好。
我也非常赞同@ amalloy对这个问题的评论,我会付出一些努力来开始寻找一个更好看的地图结构。
答案 1 :(得分:0)
(let [x {:person-123 {:xxx [1 5]
:zzz [2 3 4]}
:person-456 {:yyy [6 7]}}]
(clojure.pprint/pprint
(mapcat
(fn [[k v]]
(map (fn [[k1 v1]]
{:person (clojure.string/replace (name k) #"person-" "") :item (name k1)}) v))
x))
)
我不确定是否只有一个高阶函数,至少在核心中,能够一次性完成你想要的任务。
另一方面,GNU R重塑库中存在类似的方法,顺便说一下,它已经为clojure重新创建了: 你感兴趣的https://crossclj.info/ns/grafter/0.8.6/grafter.tabular.melt.html#_melt-column-groups。
这就是它在Gnu R中的作用:http://www.statmethods.net/management/reshape.html
答案 2 :(得分:0)
到目前为止,有很多好的解决方案。我要添加的是keys
:
(vec
(for [[person nested-data] input-data
item (map name (keys nested-data))]
{:person (clojure.string/replace-first
(name person)
#"person-" "")
:item item}))
注意btw近乎普遍的首选项替换为last / split。猜测转变的精神是“失去领先的人 - 前缀”,replace
说更好。如果OTOH的精神是“找到数字并使用它”,那么用一些正则表达式来隔离数字将更加真实。
答案 3 :(得分:0)
(reduce-kv (fn [ret k v]
(into ret (map (fn [v-k]
{:person (last (str/split (name k) #"-"))
:item (name v-k)})
(keys v))))
[]
{:person-123 {:xxx [1 5] :zzz [2 3 4]}
:person-456 {:yyy [6 7]}})
=> [{:person "123", :item "xxx"}
{:person "123", :item "zzz"}
{:person "456", :item "yyy"}]
答案 4 :(得分:-1)
以下是三种解决方案。
第一个解决方案通过lazy-gen
和yield
函数from the Tupelo library使用Python风格的延迟生成器函数。我认为这个方法最简单,因为内部循环产生了映射,外部循环产生了一个序列。此外,内部循环可以为每个外部循环运行零次,一次或多次。使用yield
,您无需考虑该部分。
(ns tst.clj.core
(:use clj.core clojure.test tupelo.test)
(:require
[clojure.string :as str]
[clojure.walk :as walk]
[clojure.pprint :refer [pprint]]
[tupelo.core :as t]
[tupelo.string :as ts]
))
(t/refer-tupelo)
(def data
{:person-123 {:xxx [1 5]
:zzz [2 3 4]}
:person-456 {:yyy [6 7]}})
(defn reformat-gen [data]
(t/lazy-gen
(doseq [[outer-key outer-val] data]
(let [int-str (str/replace (name outer-key) "person-" "")]
(doseq [[inner-key inner-val] outer-val]
(let [inner-key-str (name inner-key)]
(t/yield {:person int-str :item inner-key-str})))))))
如果你真的想要“纯粹”,以下是另一种解决方案。但是,使用此解决方案,我犯了一些错误,需要修复许多调试打印输出。此版本使用tupelo.core/glue
代替concat
,因为它“更安全”并验证集合是否都是地图,所有向量/列表等。
(defn reformat-glue [data]
(apply t/glue
(forv [[outer-key outer-val] data]
(let [int-str (str/replace (name outer-key) "person-" "")]
(forv [[inner-key inner-val] outer-val]
(let [inner-key-str (name inner-key)]
{:person int-str :item inner-key-str}))))))
两种方法都给出了相同的答案:
(newline) (println "reformat-gen:")
(pprint (reformat-gen data))
(newline) (println "reformat-glue:")
(pprint (reformat-glue data))
reformat-gen:
({:person "123", :item "xxx"}
{:person "123", :item "zzz"}
{:person "456", :item "yyy"})
reformat-glue:
[{:person "123", :item "xxx"}
{:person "123", :item "zzz"}
{:person "456", :item "yyy"}]
如果你想成为“超级纯粹”,这是第三个解决方案(虽然我认为这个尝试太难了!)。这里我们使用for
宏的功能在单个表达式中嵌套元素。 for
也可以在其中嵌入let
个表达式,但这会导致对int-str
的重复评估。
(defn reformat-overboard [data]
(for [[outer-key outer-val] data
[inner-key inner-val] outer-val
:let [int-str (str/replace (name outer-key) "person-" "") ; duplicate evaluation
inner-key-str (name inner-key)]]
{:person int-str :item inner-key-str}))
(newline)
(println "reformat-overboard:")
(pprint (reformat-overboard data))
reformat-overboard:
({:person "123", :item "xxx"}
{:person "123", :item "zzz"}
{:person "456", :item "yyy"})
我可能会坚持第一个,因为它(至少对我来说)更简单,更防弹。 YMMV。
请注意,即使发生了2次嵌套for
次迭代,第3种方法也会生成单个映射序列。这与两个嵌套的for
表达式不同,后者将生成一系列映射序列。