在Clojure中如何解释映射键?

在Clojure中如何解释映射键?,clojure,read-eval-print-loop,Clojure,Read Eval Print Loop,我试图创建一个map literal,其键由随机函数确定: user=> {(str (rand-int 5)) "hello" (str (rand-int 5)) "goodbye"} IllegalArgumentException Duplicate key: (str (rand-int 5)) clojure.lang.PersistentArrayMap.createWithCheck

我试图创建一个map literal,其键由随机函数确定:

user=> {(str (rand-int 5)) "hello" (str (rand-int 5)) "goodbye"}                                            
IllegalArgumentException Duplicate key: (str (rand-int 5))  clojure.lang.PersistentArrayMap.createWithCheck (PersistentArrayMap.java:71)
鉴于

user=> {(str (rand-int 5)) "hello" (str (rand-int 6)) "goodbye"}    
{"4" "hello", "2" "goodbye"}
读卡器似乎将该键视为未评估的列表


我在文档中找不到有关此的任何详细信息。有没有人能帮我进一步理解这一点?

我不知道关于读者的问题的答案,但构建此哈希映射的更安全的方法是确保密钥不同。例如:

(let [[k1 k2] (shuffle (range 5))] 
  {k1 "hello" k2 "goodbye"})
读者所产生的一切都是未经评估的。这是阅读器背后的一个主要思想:它以数据的形式读取表单,而不进行任何解释。读取器将未计算的映射提供给编译器

读者通过assoc或conj以增量方式构建地图。然而,在过去,这种方法会为您的代码产生一个更奇怪的结果:
{(str(rand int 5))“再见”}
。也就是说,将应用正常的assoc规则:最后添加的键值对将获胜。人们遇到了这个问题,所以现在读取器在增量添加值之前执行
contains?
检查


本文将更详细地讨论Lisp样式的读取器:

在浏览Clojure编译器的源代码时,我发现了以下内容:

  • 有一个类包含负责读取映射文本的嵌套类。它的方法
    invoke
    读取
    {
    }
    符号之间的Clojure表单,并通过方法返回(Clojure表单的)映射

  • RT.map
    其中实际检查重复的密钥。因为我们正在构建Clojure表单的映射,所以即使有两个相同的表单计算为不同的值(如您的示例中),也会触发检查

  • 所有Clojure表单的求值都在类中进行,特别是映射表单在其嵌套类中进行求值。它的方法评估map的键和值,并再次使用
    RT.map
    构建持久映射。因此,也将针对评估值执行重复键检查,这就是为什么以下代码也会失败:


  • 我不知道作者为什么决定在表单映射和值映射上执行重复键检查。很可能,这是某种“快速失败策略”:这种实现会在编译阶段早期报告错误(即使可能存在误报),并且这种检查不会被提交到运行时。

    读者没有评估映射,这是正确的

    记住,评估是在阅读之后进行的

    从:

    也就是说,大多数Clojure程序都是从文本文件开始的,读者的任务是解析文本并生成编译器将看到的数据结构。这不仅仅是编译器的一个阶段。阅读器和Clojure数据表示在许多可能使用XML或JSON等的相同上下文中都有自己的实用程序

    有人可能会说读卡器的语法是用字符定义的,Clojure语言的语法是用符号、列表、向量、映射等定义的。读卡器由函数read表示,函数read从流中读取下一个表单(不是字符),并返回该表单表示的对象

    求值器需要未求值的映射遍历它并求值它的键和值。如果该映射与键多次具有相同的形式,则该映射是无效的映射文本

    您可以改用
    哈希映射
    函数

    (hash-map (rand-int 5) "hello"
              (rand-int 5) "goodbye")`.
    
    请注意,结果贴图可能有一个或两个关键点,这取决于关键点是否不同

    如果你想强制执行两个键,那么做

    (zipmap (distinct (repeatedly #(rand-int 5)))
            ["hello" "goodbye"])
    

    谢谢你的回答。他们结合在一起回答了问题。回答中的赢家!我想你已经回答了你自己的问题,为什么在这两个地方都有检查-读者构建了一个地图(可能有重复的键),评估的地图也可能有重复的键。回答得很好。谢谢@olegthecatnoteffective for large N,有O(N)复杂度,应该是O(1)。@leograpenthin您的需求来自哪里?效率。如果在0-N范围内生成两个不同的随机数的线性复杂度为O(N),则该算法会给问题带来不必要的开销。@leon此示例未针对大数进行优化,因为OP不需要这样做。这是正确的。但是,您的
    n
    key/values示例的复杂性是什么?由于游戏中存在随机性,我不太确定。这是怎么回事呢?
    O(1)
    ?上面的示例避免了生成重复项并在以后删除它们。有人可能会说,如果表达式
    (范围n)
    中的n需要接近或等于n key/vals的数字,则该方法更有效。谢谢。回答得好@leon-grapenthin@stuartrexking谢谢,请重新考虑接受它。您接受的答案依赖于未记录的实现细节,这些细节可能会发生更改,并且不会涉及语言设计方面,这对于回答问题至关重要。谢谢。很好的解释。
    (zipmap (distinct (repeatedly #(rand-int 5)))
            ["hello" "goodbye"])