递归/嵌套“ rest”参数

时间:2019-11-14 00:48:55

标签: clojure

我经常想用所有键构建地图,但是不希望键的值显示为零时显示它们。所以:

From: "My Company" <myemail@mydomain.com>
Subject: Your subject
To: xxxx@hotmail.com.br
Bcc: myotheremail@mydomain.com
Content-Type: text/plain; charset=us-ascii
MIME-Version: 1.0
Content-Transfer-Encoding: base64
Sender: My Company <myemail@mydomain.com>
Organization: My Organization
Date: Mon, 18 Nov 2019 09:19:05 -0300

VGhpcyBpcyBhIHRlc3Qgw6kgw6Egw7Mgw6cNCg==
.

将产生{:a 1:c 3:e 5}。 (显然,我事先不知道这些值,但出于演示目的对其进行硬编码很方便。)我决定构建自己的忽略nil的assoc型函数,称为“ asif”:

(building-map :a 1 :b nil :c 3 :d nil :e 5)

(我从查看 assoc 开始,所以我知道这就是所谓的“幼稚”实现。但是现在就运行它。)当然,因为我很贪婪,我希望它可以递归工作:

(defn asif [m & kvs]
  (if (empty? kvs)
  m
    (if (second kvs)
      (recur (assoc m (first kvs) (second kvs)) (drop 2 kvs))
      (recur m (drop 2 kvs)))))

这应该返回{:a 1:b 2:c 3:e {:sub1 1:a 1}}。但这当然不会,因为它只是将“ e”上的地图视为单个值。

目前,我的解决方案是:

(asif {} :a 1 :b 2 :c 3 :d nil :e {:sub1 1 :sub2 nil :a 1})

这行得通,而且很好,但是这种情况“我已经用&收集了parm”,现在想对它们进行递归操作,但是我做不到,这总是让我感到困惑。我知道我可以做到:

  • 传入整个地图以开始,而不是使用&。
  • 写一个宏

但是反正可以使用&来做到这一点吗? (在其他方法中,我很讨厌“应用”。)或者是当您使用&收集了Parms时,模头已铸好了?

更新:只是为了澄清,这并不是删除nil,而是使用rest参数(因此是主题标题)。这只是我定期遇到的一个问题,并且(在此特定情况下)我今天想到“我可以用[&rest]编写一个递归忽略nil值的assoc模拟物”时碰巧又遇到了这个问题吗?

2 个答案:

答案 0 :(得分:2)

我认为问题在于,您在顶级调用中所做的操作与递归调用中所做的不匹配。在顶级调用中,您具有一个映射和多个键/值对,但是在递归调用中,您仅具有一个映射,因此您不再真正assoc进行操作,而是从现有映射中删除nil

所以我要说的是,由于您要做的是两种不同的事情,因此需要两种不同的功能。

这是一种从嵌套地图递归删除nil的简单方法:

(defn recursively-prune-nils [m]
  (into {} (map (fn [[k v :as pair]] 
                  [k
                   (if (map? v)
                     (recursively-prune-nils v)
                     v)])
                (filter second m))))

我们在这里不能使用recur,因为它不会处于尾部位置,因此,如果地图嵌套得非常深,最终将导致爆炸。

然后可以在asif函数中使用它:

(defn asif [m & kvs]
        (if (empty? kvs)
          m
          (if (second kvs)
            (if (map? (second kvs))
              (recur (assoc m (first kvs) (recursively-prune-nils (second kvs))) (drop 2 kvs))
              (recur (assoc m (first kvs) (second kvs)) (drop 2 kvs)))
            (recur m (drop 2 kvs)))))

这似乎可以满足您的要求:

user> (asif {} :a 1 :b 2 :c 3 :d nil :e {:sub1 1 :sub2 nil :a 1})
{:a 1, :b 2, :c 3, :e {:sub1 1, :a 1}}

答案 1 :(得分:0)

这就是我要怎么做(作为单元测试编写)。基本上,除非确实需要,否则不要重新发明轮子。只需使用postwalk

The code:

(ns tst.demo.core
  (:use tupelo.core tupelo.test)
  (:require
    [clojure.walk :as walk]
    [schema.core :as s]
    [tupelo.schema :as tsk]
  ))

(def kv-data [:a 1 :b nil :c 3 :d nil :e {:x 1 :y nil :z 1}])

(s/defn map-remove-nil-vals :- tsk/Map
  [map-in :- tsk/Map]
  (reduce-kv
    (fn [accum k v]
      (if (nil? v)
        accum
        (assoc accum k v)))
    {}
    map-in))

(defn walk-drop-mapentry-nil-vals
  [arg]
  (walk/postwalk (fn [item]
                   (if (map? item)
                     (map-remove-nil-vals item)
                     item))
    arg))

(dotest
  (let [map-data (apply hash-map kv-data)
        result   (walk-drop-mapentry-nil-vals map-data)]
    (is= (spyx-pretty result)
      {:e {:x 1, :z 1}, :c 3, :a 1}
      )))

和结果:

Testing _bootstrap

-------------------------------
   Clojure 1.10.1    Java 13
-------------------------------

Testing tst.demo.core

result => {:e {:x 1, :z 1}, :c 3, :a 1}

Ran 2 tests containing 1 assertions.
0 failures, 0 errors.

Passed all tests