【问题标题】:How to filter map content by path如何按路径过滤地图内容
【发布时间】:2021-09-03 15:38:36
【问题描述】:

我想选择要保留的深度嵌套地图的路径。

例如:

{:a 1
 :b {:c [{:d 1 :e 1} 
         {:d 2 :e 2}]
     :f 1}
 :g {:h {:i 4 :j [1 2 3]}}}

我想按路径选择,像这样:

(select-paths m [[:a] 
                 [:b :c :e]
                 [:b :f]
                 [:g :h :i]])

这会返回

{:a 1
 :b {:c [{:e 1}
         {:e 2}]
     :f 1}
 :g {:h {:i 4}}}

与 Elasticsearch 的 fields 参数基本相同。路径参数的格式可以是别的,这只是第一个想法。

我尝试了两种不同的解决方案

  1. 遍历整个地图并检查当前元素的完整路径是否在给定路径中。我不知道如何处理地图列表,以便将它们保存为地图列表。
  2. 从给定路径创建 select-keys 语句,但我再次遇到地图列表问题 - 特别是尝试解决具有某些共同深度的不同深度的路径。

我查看了幽灵,但我没有看到任何可以做到这一点的东西。我提出的任何基于mappostwalk 的解决方案都会在某些时候变得令人难以置信的复杂。我一定是想错了。

如果有办法使用原始 json 执行此操作,那也很好。甚至是 Java 解决方案。

【问题讨论】:

  • 多条路径以同一个关键字开头会怎样?如果路径不存在会怎样?因为 nil 传播是默认的,那是你想要的吗?
  • 在示例中,有两条路径以相同的[:b :c :e][:b :f] 开头。基本上你会找到最深的共同元素。虽然我愿意将它定义为[:b [:f [:c :e]]]],如果它更容易处理的话。 nil 缺少路径是期望的结果。
  • 好吧,我的午休时间快结束了,但如果有人没有击败我,我今晚会尝试编写解决方案。

标签: clojure


【解决方案1】:

没有简单的方法可以实现您的目标。 [:b :c]下的序列隐含的自动处理也有问题。

您可以使用Tupelo Forest 库中途到达那里。请参阅 Clojure/Conj 2017 中的 Lightning Talk video

我在数据解构方面做了一些额外的工作,您可能会发现构建tupelo.core/destruct 宏很有用(参见示例here)。您可以按照类似的大纲为您的特定问题构建递归解决方案。

一个相关的项目是Meander。我已经在自己的版本上工作过,它就像tupelo.core/destruct 的通用版本。给定这样的数据

(def skynet-widgets [{:basic-info   {:producer-code "Cyberdyne"}
                      :widgets      [{:widget-code      "Model-101"
                                      :widget-type-code "t800"}
                                     {:widget-code      "Model-102"
                                      :widget-type-code "t800"}
                                     {:widget-code      "Model-201"
                                      :widget-type-code "t1000"}]
                      :widget-types [{:widget-type-code "t800"
                                      :description      "Resistance Infiltrator"}
                                     {:widget-type-code "t1000"
                                      :description      "Mimetic polyalloy"}]}
                     {:basic-info   {:producer-code "ACME"}
                      :widgets      [{:widget-code      "Dynamite"
                                      :widget-type-code "c40"}]
                      :widget-types [{:widget-type-code "c40"
                                      :description      "Boom!"}]}])

您可以使用这样的模板搜索和提取数据:


    (let [root-eid (td/add-entity-edn skynet-widgets)
          results  (td/match
                     [{:basic-info   {:producer-code ?}
                       :widgets      [{:widget-code      ?
                                       :widget-type-code wtc}]
                       :widget-types [{:widget-type-code wtc
                                       :description      ?}]}])]
      (is= results
        [{:description "Resistance Infiltrator" :widget-code "Model-101" :producer-code "Cyberdyne" :wtc "t800"}
         {:description "Resistance Infiltrator" :widget-code "Model-102" :producer-code "Cyberdyne" :wtc "t800"}
         {:description "Mimetic polyalloy" :widget-code "Model-201" :producer-code "Cyberdyne" :wtc "t1000"}
         {:description "Boom!" :widget-code "Dynamite" :producer-code "ACME" :wtc "c40"}])))

此代码有效(请参阅here),但需要更多润色。您可以将其用作构建通用 select-paths 函数的指南。


您能否详细说明这个问题是如何产生的或具体背景?这可能指向替代解决方案的想法。

【讨论】:

  • Meander 看起来很有希望。在我的情况下,结构的模式是已知的,这有帮助。我当然希望有一个通用的解决方案,但我意识到这是一个很大的问题。需要玩这些,谢谢!
  • 至于问题的根源:我有一个rest api,它返回不同的大型json对象列表。我想提供一种过滤大量 json 响应的方法,因为不同的客户端对不同的数据子集感兴趣
  • @siltalau graphQL?
【解决方案2】:

解决此问题的一种方法是生成一组您接受的所有子路径,然后编写一个遍历数据结构并跟踪到当前节点的路径的递归函数。完成的代码不需要很长:

(defn select-paths-from-set [current-path path-set data]
  (cond
    (map? data) (into {}
                      (remove nil?)
                      (for [[k v] data]
                        (let [p (conj current-path k)]
                          (if (contains? path-set p)
                            [k (select-paths-from-set p path-set v)]))))
    (sequential? data) (mapv (partial select-paths-from-set current-path path-set) data)
    :default data))

(defn select-paths [data paths]
  (select-paths-from-set []
                         (into #{}
                               (mapcat #(take-while seq (iterate butlast %)))
                               paths)
                         data))

(select-paths {:a 1
               :b {:c [{:d 1 :e 1} 
                       {:d 2 :e 2}]
                   :f 1}
               :g {:h {:i 4 :j [1 2 3]}}}
              [[:a] 
               [:b :c :e]
               [:b :f]
               [:g :h :i]])
;; => {:a 1, :b {:c [{:e 1} {:e 2}], :f 1}, :g {:h {:i 4}}}

【讨论】:

  • 这很有魅力!而且它非常简洁。
猜你喜欢
  • 1970-01-01
  • 2022-06-10
  • 2022-08-17
  • 2016-10-01
  • 2012-07-11
  • 1970-01-01
  • 2015-08-04
  • 2019-09-01
  • 2018-12-23
相关资源
最近更新 更多