【问题标题】:Clojure 2d list to hash-mapClojure 2d 列表到哈希映射
【发布时间】:2013-04-09 00:37:27
【问题描述】:

我有一个这样的无限列表:
((1 1)(3 9)(5 17)...)
我想用它制作一个哈希图:
{:1 1 :3 9 :5 17 ...)

“内部”列表的第一个元素基本上是关键字,而第二个元素是值。我不确定在创建时创建我使用的列表是否更容易:

(迭代 (fn [[a b]] [(计算 a) (计算 b)]) [1 1])

(b) 的计算需要 (a),所以我认为在这一点上 (a) 不能是关键字...重点是可以轻松访问给定 (a) 的值 (b)。

任何想法将不胜感激......

--编辑--
好的,我想通了:

(def my-map (into {} (map #(hash-map (keyword (str (first %))) (first (rest %))) my-list)))

问题是:它似乎并不懒惰......即使我没有消费它,它也会永远消失。有没有办法让它偷懒?

【问题讨论】:

  • 拥有无限哈希映射的意义何在?您无法在创建时构建该结构,因为哈希映射不支持第 n 次操作。
  • 我从不对 nth 感兴趣,但我总是知道关键字,并且我对与该关键字关联的值感兴趣
  • 我已经发布了一种在惰性序列创建后创建哈希映射的方法,如果可以,请告诉我
  • Clojure 的地图并不懒惰,而且很难看出它们在哪里是正确的。
  • 是的,现在我想这很有意义......

标签: clojure hashmap lazy-sequences


【解决方案1】:

问题是哈希映射既不能无限也不能惰性。他们设计用于快速键值访问。因此,如果您有一个哈希映射,您将能够执行快速键查找。键值访问是hash-map的核心思想,但它使得惰性无限hash-map的创建变得不可能。

假设,我们有一个无限的 2d 列表,那么您可以使用 into 创建 hash-map:

(into {} (vec (map vec my-list)))

但是没有办法使这个哈希映射无限。因此,您唯一的解决方案是创建自己的哈希映射,例如 Chouser suggested。在这种情况下,您将拥有一个无限的 2d 序列和一个在其中执行惰性键查找的函数。

其实他的解决方案可以稍微改进一下:

(def my-map (atom {}))

(def my-seq (atom (partition 2 (range))))

(defn build-map [stop]
  (when-let [[k v] (first @my-seq)]
    (swap! my-seq rest)
    (swap! my-map #(assoc % k v))
    (if (= k stop)
        v
        (recur stop))))

(defn get-val [k]
  (if-let [v (@my-map k)]
    v
    (build-map k)))

my-map 在我的示例中存储当前的哈希映射,my-seq 存储尚未处理的元素序列。 get-val 函数执行惰性查找,使用 my-map 中已处理的元素来提高其性能:

(get-val 4)
=> 5
@my-map
=> {4 5, 2 3, 0 1}

还有一个加速:

(time (get-val 1000))
=> Elapsed time: 7.592444 msecs
(time (get-val 1000))
=> Elapsed time: 0.048192 msecs

【讨论】:

    【解决方案2】:

    为了懒惰,计算机必须在每次请求键时对输入序列进行线性扫描,至少如果键超出了目前已扫描的范围。一个简单的解决方案是每次都扫描序列,如下所示:

    (defn get-val [coll k] (一些 (fn [[a b]] (当 (= k a) b)) coll)) (get-val '((1 1)(3 9)(5 17)) 3) ;=> 9

    一个稍微不那么天真的解决方案是使用memoize 来缓存get-val 的结果,尽管这仍然会扫描输入序列而不是严格必要的。更积极的缓存解决方案是使用原子(如 memoize 在内部所做的那样)缓存所看到的每一对,从而仅在查找需要尚未看到的内容时消耗更多的输入序列。

    无论如何,我不建议将其包装在哈希映射 API 中,因为这意味着可能不需要但难以实现的高效不可变“更新”。我通常也不建议对键进行关键字化。

    【讨论】:

    • 感谢您的深入解释。在我看来,这太麻烦了,所以我会留下一个清单。因为我知道键都是奇数,所以对于键 X,我可以通过 (nth mylist (/ (- X 1) 2)) 快速查找
    • 很公平,但是如果输入是惰性序列,nth 仍然会从头开始对每次查找进行线性扫描。
    • 我知道,但总是这样,我迟早要消耗序列到第 n 个元素,对吧?无论我以某种方式将其转换为地图,将其包装在一个函数中以获取第 n 个元素还是只是(第 n 个)它......
    • 第一次调用 get-val 函数没有区别,但是通过将你的惰性序列转换为地图,你会在以后的调用中节省很多。
    【解决方案3】:

    如果你用 flatten 将它展平为 (k v k v k v k v) 的列表,那么你可以使用 apply 来调用带有该列表的 hash-map,因为它的参数将为你提供你寻找的列表。

    user> (apply hash-map (flatten '((1 1)(3 9)(5 17))))
    {1 1, 3 9, 5 17}
    

    虽然它没有关键字化第一个参数。

    至少在 clojure 中 与键关联的最后一个值被称为该键的值。如果不是这种情况,那么您不能为已经在映射中的键生成具有不同值的新映射,因为查找函数将返回第一个(现在是阴影键)。如果查找函数搜索到最后,那么它不是懒惰的。您可以通过编写自己的使用关联列表的地图实现来解决这个问题,尽管它会缺乏 Clojure 基于 Trei 的地图的性能保证,因为在最坏的情况下它会退化为线性时间。

    我不确定保持输入序列惰性是否会产生预期的结果。

    【讨论】:

    • 而且也不懒惰 (def testme (apply hash-map (lazy-seq-here))) 阻塞 CPU
    【解决方案4】:

    要从您的序列中制作哈希图,您可以尝试:

    (defn to-map [s] (zipmap (map (comp keyword str first) s) (map second s)))
    
    => (to-map '((1 1)(3 9)(5 17)))
    => {:5 17, :3 9, :1 1}
    

    【讨论】:

      【解决方案5】:

      您可以稍后通过这种方式将该结构转换为哈希映射

      (def it #(iterate (fn [[a b]] [(+ a 1) (+ b 1)]) [1 1])) 
      (apply hash-map (apply concat (take 3 (it))))
      => {1 1, 2 2, 3 3}
      

      【讨论】:

      • 这可行,但我需要哈希映射是无限的(和懒惰的),就像列表一样......
      • 但是,如果您有一个序列,其中每个值都是从前一个值构造的,那么如果不实现所有先前的值,就无法获得特定的键。在这种情况下,只需重新定义 get 函数即可(实现序列,直到找到所需的密钥)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-12-18
      • 1970-01-01
      • 1970-01-01
      • 2023-04-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多