Clojure hashmaps作为广义向量

时间:2013-10-29 21:54:24

标签: vector clojure hashmap linear-algebra addition

我想使用Clojure哈希映射来定义广义向量。在该图中,散列映射{:x 3:y 2:z( - 3)}表示符号表达式3x + 2y-3z。

我想要一个函数gen +,它作为散列图的附加操作,满足以下约束条件:

  • 将gen +应用于具有重叠键的两个哈希映射返回一个哈希映射,其中这些键值相加。如,

(gen+ {:x 1} {:x 2})

;= {:x 3}

  • 将gen +应用于具有不同键的两个哈希映射返回一个哈希映射,其中组合了这些键值。如,

(gen+ {:x 1} {:y 2})

;= {:x 1 :y 2}

  • 空hashmap {}是gen +函数的附加标识。如,

(gen+ {:x 1} {})

:= {:x 1}

  • 通过上述约束,任何具有零条目的散列映射也是附加标识。例如,{:z 0}。由于这种冗余,gen +函数应始终返回没有任何0值的哈希映射。如,

(gen+ {:x 1 :y 0} {:z 0})

;= {:x 1}

  • ({:x 3} :y) ;= nil一样,Clojure将缺失的密钥解释为值为nil而不是0。因此,gen +应该以与0相同的方式处理nil值,例如

(gen+ {:x 1 :y 0} {:x nil :y nil})

{:x 1}

  • 我的问题:我们如何编写满足上述约束的函数gen +?一旦这个到位,是否可以使用gen +函数重载+运算符,使用散列图启用基本添加?

为什么这种形式主义将散列图视为广义向量应该是显而易见的。 Clojure解释像[x0 x1 x2]这样的向量几乎与hashmap {:0 x0:1 x1:2 x2}相同,略有不同,我不太明白。因此,如果我们将gen +应用于两个向量,那么它应该与应用于它们的+实际上相同。这使我们能够轻松使用稀疏向量,以及添加不同大小的向量。如,

(gen+ [0 0 0 4] [0 0 0 0 0 0 0 0 0 9])

;= {:4 4 :9 9}

这是我对哈希映射和向量的不了解。如果我将hashmap作为函数调用,我需要应用一个关键参数,如:2。另一方面,如果我将一个向量称为函数,我需要应用索引参数,如2.例如,

({:2 2} :2)

;= 2

([0 1 2] 2]

;= 2

即使你无法帮助gen +功能,你能不能解释一下为什么hashmaps和vector在被称为函数时表现不同?

2 个答案:

答案 0 :(得分:4)

第一个问题的答案是使用merge-with请参阅http://clojuredocs.org/clojure_core/clojure.core/merge-with

来自文档:

返回一个地图,该地图由其他地图组成 首先。如果一个键出现在多个映射中,则映射(s) 从后者(从左到右)将与映射相结合 通过调用(结果为val-in-later)来得到结果。

然后,您可以编写一个函数来合并合并的值(可能是+)。然后抛出零和0值。

重载+在Clojure中并不是一个好主意,因为它并没有真正超载而是替换。

地图和矢量之间的差异类似于数组,而地图更像是键值对的字典。两种结构也是它们的成员的功能。对于其成员可通过偏移访问的向量/数组,它接受偏移是有意义的。对于按密钥访问其成员的地图/字典,它接受密钥是有意义的。

答案 1 :(得分:2)

这将首先删除nil0值,然后按照M Smith的建议添加merge-with地图:

(defn filter-vals
  [pred m]
  (into {} (filter (fn [[k v]] (pred v))
                   m)))

(defn gen+
  [m1 m2]
  (letfn [(keep-val? [val]
            (and (not (nil? val))
                 (not (zero? val))))]
    (merge-with +
                (filter-vals keep-val? m1)
                (filter-vals keep-val? m2))))

您需要在开始添加之前过滤掉任何nil - 否则您最终会尝试将nil添加到某个数字,这是一个错误。但是,我概述的gen+可能会返回一个0值的条目(考虑(gen+ {:x 1} {:x -1}))。

如果可以根据您的输入进行,并且需要避免,则需要在合并后添加另一个过滤器:

(defn gen+
  [m1 m2]
  (letfn [(keep-val? [val]
            (and (not (nil? val))
                 (not (zero? val))))]
    (->> (merge-with +
                     (filter-vals keep-val? m1)
                     (filter-vals keep-val? m2))
         (filter-vals keep-val?))))

最后,这是一个可以处理可变数量输入映射的版本:

(defn gen+
  [& maps]
  (letfn [(keep-val? [val]
            (and (not (nil? val))
                 (not (zero? val))))]
    (->> (apply merge-with
                +
                (map #(filter-vals keep-val? %) maps))
         (filter-vals keep-val?))))

所以,例如:

(gen+ {:x 1} {:x 3} {:x 4 :y 5}) ;=> {:x 8, :y 5}

关于地图和矢量之间的区别,可以这样考虑:地图是从键到值的函数,而矢量是从索引到值的函数。在这两种情况下,如果你有一个“键”(矢量是索引),你可以用它来查找相关的值。

我同意你到目前为止关于重载clojure.core/+的答案和评论(我不推荐);这就是说,这是排序的一种方式,有很多警告:

(ns overload.example
  (:refer-clojure :exclude [+])
  (:require [clojure.core :as clj]))

(in-ns 'overload.example)

(defn gen+ ...)

(defn + [& xs]
  (if (every? number? xs)
    (reduce clj/+ 0 xs)
    (apply gen+ xs)))