【问题标题】:Clojure hashmaps as generalized vectorsClojure 哈希图作为广义向量
【发布时间】:2013-10-29 21:54:23
【问题描述】:

我想使用 Clojure 哈希图来定义广义向量。在这张图中,hashmap {: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}

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

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

{:x 1}

  • 我的问题: 我们如何编写满足上述约束的函数 gen+?一旦到位,是否可以使用 gen+ 函数重载 + 运算符,从而使用哈希图实现基本加法?

应该很明显为什么这种形式将哈希图视为广义向量。 Clojure 将像 [x0 x1 x2] 这样的向量解释为与哈希图 {:0 x0 :1 x1 :2 x2} 几乎相同,但有一点我不太理解的差异。因此,如果我们将 gen+ 应用于两个向量,那么它实际上应该与应用于它们的 + 相同。这使我们能够轻松地使用 sparse 向量,以及添加不同大小的向量。例如,

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

;= {:4 4 :9 9}

以下是我对哈希图和向量的不理解。如果我将哈希图称为函数,我需要应用一个关键参数,例如:2。另一方面,如果我将向量作为函数调用,我需要应用像 2 这样的索引参数。例如,

({:2 2} :2)

;= 2

([0 1 2] 2]

;= 2

即使您无法使用 gen+ 函数,您能否解释一下为什么哈希图和向量在作为函数调用时表现不同?

【问题讨论】:

  • 就像 M Smith 一样,我不建议重载 +, but here's some dark magic 反正。
  • 如果这是一个学校项目,请支持你的学校接受clojure。
  • @muhuk:我是一名专业数学家,主要从事泛函分析(无限维线性代数)。当感兴趣的空间/类型是线性空间、点/项是向量、地图/函数是线性运算符时,这些工具适用。能够将哈希图用作向量意味着我们可以部署现代数学技术,而不必重新发明轮子。这种观点在 MapReduce 架构中特别有用,其中感兴趣的基本对象是键/值对,就像哈希图一样。
  • 但我也认为学校应该对各种编程语言开放,尤其是功能性语言:)

标签: vector clojure hashmap linear-algebra addition


【解决方案1】:

你第一个问题的答案是使用merge-withhttp://clojuredocs.org/clojure_core/clojure.core/merge-with

来自文档:

返回一个地图,它由结合到的其余地图组成 首先。如果一个键出现在多个映射中,则映射 从后者(从左到右)将与映射相结合 通过调用 (f val-in-result val-in-latter) 得到结果。

然后您可以编写一个函数来组合合并的值(可能是+)。然后折腾 nil 和 0 的值。

在 Clojure 中重载 + 并不是一个好主意,因为它实际上并不是重载而是替换。

map 和 vector 的区别就像一个数组,而 map 更像是一个键值对的字典。这两种结构也是它们成员的功能。对于通过偏移量访问其成员的向量/数组,接受偏移量是有意义的。对于通过键访问其成员的地图/字典,它接受一个键是有意义的。

【讨论】:

  • 谢谢你,马修! merge-with + 是一个很好的解决方案,看起来就像我想要的那样工作。但是,我怎样才能折腾 nil & 0 值?我不确定如何按值过滤地图。
  • @TomLaGatta,你可以做点什么like this
【解决方案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))))

您需要在开始添加之前过滤掉任何nils - 否则您最终会尝试将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/+ 的答案和cmets(我不推荐它);也就是说,这是一种 的方法,但有很多注意事项:

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

【讨论】:

  • @TomLaGatta,很高兴我能帮上忙!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-08
  • 1970-01-01
  • 1970-01-01
  • 2022-01-09
  • 1970-01-01
  • 2011-06-03
相关资源
最近更新 更多