我正在尝试修改数据结构中的特定字段,如下所述(可以找到填充示例here:
[{:fields "There are a few other fields here"
:incidents [{:fields "There are a few other fields here"
:updates [{:fields "There are a few other fields here"
:content "THIS is the field I want to replace"
:translations [{:based_on "Based on the VALUE of this"
:content "Replace with this value"}]}]}]}]
我已经在许多函数中实现了它,如下所示:
(defn- translation-content
[arr]
(:content (nth arr (.indexOf (map :locale arr) (env/get-locale)))))
(defn- translate
[k coll fn & [k2]]
(let [k2 (if (nil? k2) k k2)
c ((keyword k2) coll)]
(assoc-in coll [(keyword k)] (fn c))))
(defn- format-update-translation
[update]
(dissoc update :translations))
(defn translate-update
[update]
(format-update-translation (translate :content update translation-content :translations)))
(defn translate-updates
[updates]
(vec (map translate-update updates)))
(defn translate-incident
[incident]
(translate :updates incident translate-updates))
(defn translate-incidents
[incidents]
(vec (map translate-incident incidents)))
(defn translate-service
[service]
(assoc-in service [:incidents] (translate-incidents (:incidents service))))
(defn translate-services
[services]
(vec (map translate-service services)))
每个数组可以包含任意数量的条目(尽管数量可能小于10)。
基本前提是根据提供的值替换每个:content
中的:update
和相关的:translation
。
我的Clojure知识是有限的,所以我很好奇是否有更优化的方法来实现这一目标?
修改 解决方案到目前为止:
(defn- translation-content
[arr]
(:content (nth arr (.indexOf (map :locale arr) (env/get-locale)))))
(defn- translate
[k coll fn & [k2]]
(let [k2 (if (nil? k2) k k2)
c ((keyword k2) coll)]
(assoc-in coll [(keyword k)] (fn c))))
(defn- format-update-translation
[update]
(dissoc update :translations))
(defn translate-update
[update]
(format-update-translation (translate :content update translation-content :translations)))
(defn translate-updates
[updates]
(mapv translate-update updates))
(defn translate-incident
[incident]
(translate :updates incident translate-updates))
(defn translate-incidents
[incidents]
(mapv translate-incident incidents))
(defn translate-service
[service]
(assoc-in service [:incidents] (translate-incidents (:incidents service))))
(defn translate-services
[services]
(mapv translate-service services))
答案 0 :(得分:3)
我会或多或少地开始,自下而上,通过定义一些看起来有用的功能:如何从翻译列表中选择翻译,以及如何将该选择应用于更新。但是我不会让你的功能变得如此微小:逻辑分散在很多地方,并且要弄清楚发生了什么并不容易。以下是我开始使用的两个函数:
(letfn [(choose-translation [translations]
(let [applicable (filter #(= (:locale %) (get-locale))
translations)]
(when (= 1 (count applicable))
(:content (first applicable)))))
(translate-update [update]
(-> update
(assoc :content (or (choose-translation (:translations update))
(:content update)))
(dissoc :translations)))]
...)
当然,如果您愿意,可以defn
代替他们,我怀疑很多人会这样做,但他们只会在一个地方使用,而且他们密切关注它们被使用了,所以我喜欢letfn
。这两个函数真的都是有趣的逻辑;其余的只是一些无聊的树遍历代码,可以在正确的位置应用这个逻辑。
现在构建letfn
的主体很简单,如果你使你的代码与它操作的数据形状相同,则易于阅读。我们想要遍历一系列嵌套列表,在路上更新对象,因此我们只编写一系列嵌套的for
推理,调用update
进入正确的键空间:
(for [user users]
(update user :incidents
(fn [incidents]
(for [incident incidents]
(update incident :updates
(fn [updates]
(for [update updates]
(translate-update update))))))))
我认为在这里使用for
比使用map
好几英里,尽管它们当然一如既往。重要的区别在于,当您阅读代码时,您首先会看到新的上下文(“好吧,现在我们正在为每个用户做一些事情”),然后在该上下文中发生的事情;使用map
,您可以按照其他顺序看到它们,并且难以随时了解发生的情况。
组合这些并将它们放入defn
,我们得到一个函数,您可以使用示例输入调用它并生成所需的输出(假设get-locale
的合适定义):
(defn translate [users]
(letfn [(choose-translation [translations]
(let [applicable (filter #(= (:locale %) (get-locale))
translations)]
(when (= 1 (count applicable))
(:content (first applicable)))))
(translate-update [update]
(-> update
(assoc :content (or (choose-translation (:translations update))
(:content update)))
(dissoc :translations)))]
(for [user users]
(update user :incidents
(fn [incidents]
(for [incident incidents]
(update incident :updates
(fn [updates]
(for [update updates]
(translate-update update))))))))))
答案 1 :(得分:3)
我们可以尝试在此任务中找到一些模式(基于github gist的片段内容,您已发布):
简单来说,你需要
1)更新数据向量中的每个项目(A)
2)更新A的矢量中的每个项目(B):事件
3)更新B的向量中的每个项目(C):更新
4)翻译C
translate
函数可能如下所示:
(defn translate [{translations :translations :as item} locale]
(assoc item :content
(or (some #(when (= (:locale %) locale) (:content %)) translations)
:no-translation-found)))
它的用法(为简洁起见省略了一些字段):
user> (translate {:id 1
:content "abc"
:severity "101"
:translations [{:locale "fr_FR"
:content "abc"}
{:locale "ru_RU"
:content "абв"}]}
"ru_RU")
;;=> {:id 1,
;; :content "абв",
;; :severity "101",
;; :translations [{:locale "fr_FR", :content "abc"} {:locale "ru_RU", :content "абв"}]}
然后我们可以看到1和2完全相似,所以我们可以概括:
(defn update-vec-of-maps [data k f]
(mapv (fn [item] (update item k f)) data))
使用它作为构建块,您可以构成整个数据转换:
(defn transform [data locale]
(update-vec-of-maps
data :incidents
(fn [incidents]
(update-vec-of-maps
incidents :updates
(fn [updates] (mapv #(translate % locale) updates))))))
(transform data "it_IT")
返回您需要的内容。
然后你可以进一步概括它,使任务深度转换的效用函数:
(defn deep-update-vec-of-maps [data ks terminal-fn]
(if (seq ks)
((reduce (fn [f k] #(update-vec-of-maps % k f))
terminal-fn (reverse ks))
data)
data))
并像这样使用它:
(deep-update-vec-of-maps data [:incidents :updates]
(fn [updates]
(mapv #(translate % "it_IT") updates)))
答案 2 :(得分:1)
我建议您查看https://github.com/nathanmarz/specter
它使得查看和更新clojure数据结构变得非常容易。与手写代码的性能相同,但更短。