如何在Clojure中深层嵌套数据结构(向量和列表)中查找索引?

时间:2017-08-18 20:56:17

标签: clojure

我正在尝试从嵌套数据结构中获取值的索引路径。

我写了一些代码,但它无法正常工作

(defn helper
  [val form c index]
  (loop [f form
         i index
         l 0]
    (if (seq f)
      (if (or (list? (first f)) (vector? (first f)))
        (helper val (first f) (conj c l) (inc i))
        (if (= val (first f))
          (conj c l)
          (recur (rest f) (inc i) (inc l))))
      nil)))

(defn find-indexes
  [val form c index]
  (when (seq form)
    (if-let [result (helper val form c index)]
      result
      (find-indexes val (rest form) c (inc index)))))

(defn find-index-route
  [val form]
  (find-indexes val form [] 0))

当前行为:

(find-index-route :my-key '(1 2 ("a" :my-key))) ;=> [2 1] "Works"

(find-index-route :my-key '(1 2 ("a" ["b" 3 :my-key]))) ;=> [2 1 2] "Works"

(find-index-route :my-key '(1 2 ("a" ["b" 3 () :my-key]))) ;=> nil "Does NOT Work"

(find-index-route :my-key '(1 2 ("a" [] ["b" 3 :my-key]))) ;=> nil "Does NOT Work"

(find-index-route :my-key '(1 2 [] ("a" ["b" 3 :my-key]))) ;=> [0 1 2] "It has to be [3 1 2]"

问题是如果函数命中空列表或向量,然后才能找到它返回的值 nil或0 (仅适用于第一级)

我需要的行为:

                                          ;=> [indexes...]

(find-index-route :my-key '(1 2 :my-key)) ;=> [2]

(find-index-route :my-key '(1 2 "a" :my-key "b")) ;=> [3]

(find-index-route :my-key '(1 2 [:my-key] "c")) ;=> [2 0]

(find-index-route :my-key '(1 2 [3 [:my-key]])) ;=> [2 1 0]

(find-index-route :my-key '(1 2 [3 [[] :my-key]])) ;=> [2 1 1]

(find-index-route :my-key '(1 2 [3 [4 5 6 (:my-key)]])) ;=> [2 1 3 0]

(find-index-route :my-key '(1 2 [3 [[]]])) ;=> nil or []

4 个答案:

答案 0 :(得分:3)

(defn find-index-route [x coll]
  (letfn [(path-in [y]
            (cond
              (= y x) '()
              (coll? y) (let [[failures [success & _]]
                              (->> y
                                   (map path-in)
                                   (split-with not))]
                          (when success (cons (count failures) success)))))]
    (path-in coll)))

示例:

(find-index-route :my-key '(1 2 :my-key)) ;=> [2]
=> (2)

(find-index-route :my-key '(1 2 "a" :my-key "b")) ;=> [3]
=> (3)

(find-index-route :my-key '(1 2 [:my-key] "c")) ;=> [2 0]
=> (2 0)

(find-index-route :my-key '(1 2 [3 [:my-key]])) ;=> [2 1 0]
=> (2 1 0)

(find-index-route :my-key '(1 2 [3 [[] :my-key]])) ;=> [2 1 1]
=> (2 1 1)

(find-index-route :my-key '(1 2 [3 [4 5 6 (:my-key)]])) ;=> [2 1 3 0]
=> (2 1 3 0)

(find-index-route :my-key '(1 2 [3 [[]]])) ;=> nil or []
=> nil

考虑表现

split-with使用take-whiledrop-while扫描序列两次,以生成两个惰性序列。如果性能很差,您可以编写一个drop-while版本,它还会告诉您已丢弃的项目数量:

(defn counted-drop-while [pred coll]
  (loop [takes 0, tail coll]
    (if (and (seq tail) (pred (first tail)))
      (recur (inc takes) (rest tail))
      [takes tail])))

它只进行一次扫描。我们可以轻松地调整find-index-route来使用它:

(defn find-index-route [x coll]
  (letfn [(path-in [y]
            (cond
              (= y x) '()
              (coll? y) (let [[fail-count [success & _]]
                              (->> y
                                   (map path-in)
                                   (counted-drop-while not))]
                          (when success (cons fail-count success)))))]
    (path-in coll)))

......结果相同。

答案 1 :(得分:3)

这个问题对于Spectre库来说似乎是完美的,它是关于导航和转换嵌套数据结构的。我问Nathan Marz关于昨天Clojurians可能解决这个问题的方法 - 请参阅他原来的答案here

(defn find-index-route [v data]
  (let [walker (recursive-path [] p
                               (if-path sequential?
                                        [INDEXED-VALS
                                         (if-path [LAST (pred= v)]
                                                  FIRST
                                                  [(collect-one FIRST) LAST p])]))
        ret    (select-first walker data)]
    (if (vector? ret) ret [ret])))

请注意,如果:my-key不存在,则返回[nil]。您可以通过将最后一个“if”部分更改为此来轻松修改它以返回nil:

(if (or (vector? ret) (nil? ret)) ret [ret])

答案 2 :(得分:1)

我制作的版本找到全部路线

(defn find-index-route
  "find all routes to some value"
  ([k x] (find-index-route k x []))
  ([k x p]
   (cond
     (= k x) [p]
     (coll? x) (->> (if (map? x) x (vec x))
                    (reduce-kv (fn [acc i v]
                                 (into (vec (find-index-route k v (conj p i))) acc)) []))
     :else nil)))

编辑:也适用于地图

(find-index-route :my-key '{:bar 33
                            :foo [{:my-key ("0" :my-key)}]
                            "bar" ["key" {:foo ([:my-key])}]})
;=> [["bar" 1 :foo 0 0] [:foo 0 :my-key 1]]

答案 3 :(得分:0)

我还找到了另一个解决方案:

(defn find-index-route
  [x form]
  (letfn [(get-nodes [form]
            (tree-seq coll? identity form))

          (get-tree [form]
            (rest (get-nodes form)))

          (get-level [form]
            (if (or (not (coll? form)) (not (seq form)))
              0
              (count (filter coll? (get-nodes form)))))

          (get-result [x form]
            (reduce (fn [v form]
                      (let [[idx lvl _] (last v)
                            form-lvl  (get-level form)
                            contains? ((set (get-nodes form)) x)]
                        (conj v [(if (or (not idx) (< form-lvl lvl)) 0 (+ idx 1))
                                 form-lvl
                                 contains?]))) [] (get-tree form)))

          (get-indices [x form]
            (map (fn [[idx _ _]] idx)
                 (filter
                   (fn [[_ _ contains?]] contains?) (get-result x form))))]

    (get-indices x form)))