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)))