Clojure 改进复杂数据结构替换
我试图修改数据结构中的特定字段,如下所述(可以找到一个完整的示例: 我已经在许多功能中实现了它,如下所示:Clojure 改进复杂数据结构替换,clojure,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 ((keywo
(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)
基本前提是根据提供的值将每个:update
中的:content
替换为相关的: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))
我会像你一样,从下到上,定义一些看起来有用的函数:如何从翻译列表中选择翻译,以及如何将选择应用到更新。但我不会让这些函数像你的函数那样小:逻辑都分散到很多地方,要得到一个新的结果并不容易n关于正在发生的事情的总体想法。以下是我将从以下两个功能开始:
(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
。这两个函数真的都是有趣的逻辑;其余的只是一些无聊的树形图将此逻辑应用于正确位置的rsal代码
现在,构建letfn
的主体非常简单,如果您使代码与它所处理的数据的形状相同,那么就很容易阅读。我们希望遍历一系列嵌套列表,在途中更新对象,因此我们只需为理解编写一系列嵌套的,调用update
来描述d)输入到正确的键空间:
(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
有一个合适的定义):
我们可以尝试在这个任务中找到一些模式(基于您发布的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)))
(deep-update-vec-of-maps data [:incidents :updates]
(fn [updates]
(mapv #(translate % "it_IT") updates)))
它的用法(为了简洁起见,省略了一些字段):
然后我们可以看到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))
然后像这样使用它:
(defn translate [{translations :translations :as item} locale]
(assoc item :content
(or (some #(when (= (:locale %) locale) (:content %)) translations)
:no-translation-found)))
(deep-update-vec-of-maps data [:incidents :updates]
(fn [updates]
(mapv #(translate % "it_IT") updates)))
我建议你看看
它使读取和更新clojure数据结构变得非常容易。与手写代码的性能相同,但要短得多。您可以使用mapv而不是(vec(map…)@Ertuğrulêetin ty,修正了非常有用的答案,我来试一试。对函数式编程还不熟悉,所以我仍然对它的大部分内容感到困惑。这个答案是可行的,但所有数组都是列表而不是向量?确实如此。通常,关于堆栈溢出的问题都是以向量为框架的,因为这是最简单的输入方式,但是列表在大多数情况下对于答案来说也是可以接受的,并且生成列表的代码(好吧,惰性seq)更简单。如果你真的需要向量(从你的数据来看,我觉得你不需要),你可以很容易地转换它。你能展示一下这个问题在Specter中是如何解决的吗?我很感激你的评论,但是这个问题已经解决了,不需要依赖性。
(deep-update-vec-of-maps data [:incidents :updates]
(fn [updates]
(mapv #(translate % "it_IT") updates)))