UnmodifiableRandomAccessList 和 “contains?” 函数的问题

huangapple go评论78阅读模式
英文:

Issue with UnmodifiableRandomAccessList and "contains?" function

问题

我正试图为一个名为 stensil 的开源应用做贡献。这个应用是用 Clojure 写的。

stensil 是一个在 .docx 上运行的模板引擎。该模板引擎声明了一系列自定义函数供模板使用。完整的函数列表非常短,可以在这里看到:这里

我需要做的是添加另一个自定义函数,用于检查列表是否包含某个元素。根据现有示例,我做了以下操作:

(defmethod call-fn "contains" [_ item items] (contains? items item))

为了测试这个函数,我创建了一个包含以下片段的 .docx 文件:

{%= contains(5, data) %}

以及一个包含以下内容的 .json 文件:

{
  "data": [9, 4, 1, 6, 3]
}

然后我按照以下方式运行应用程序:

java -jar stencil-core-0.3.8-standalone.jar template.docx sample.json

然而,不幸的是,当使用时,我定义的函数不起作用,抛出以下异常:

java.lang.IllegalArgumentException: contains? not supported on type: java.util.Collections$UnmodifiableRandomAccessList

似乎 items 的类型是 java.util.Collections$UnmodifiableRandomAccessList。实际上,如果我在函数中添加 (println items),我会得到这个输出:

#object[java.util.Collections$UnmodifiableRandomAccessList 0x778ca8ef [9, 4, 1, 6, 3]]

我尝试了使用 some 函数进行不同的方法。这个答案 表示这种方法 大多数情况下 应该可行。然而,以下两种变体都返回了 nil

(defmethod call-fn "contains" [_ item items] (some #{item} items))
(defmethod call-fn "contains" [_ item items] (some #(= item %) items))

我之前从未写过一行 Clojure 代码,现在有些迷茫。我做错了什么?将 UnmodifiableRandomAccessList 列表转换为 somecontains? 可以处理的类型是否有意义?如果是,如何在 items 变量上执行这种转换?

更新。尝试使用 .contains,就像答案中的 cfrick 建议的那样,像这样:

(defmethod call-fn "contains" [_ item items] (.contains items item))

这不会引发运行时错误,但输出始终为 false。通过 println 调试揭示了这一点:

(defmethod call-fn "contains" [_ item items]
  (println item)
  (println items)
  (.contains items item))
1
#object[java.util.Collections$UnmodifiableRandomAccessList 0x778ca8ef [9, 4, 1, 6, 3]]

为什么呢?

英文:

I'm trying to contribute to an open-source application called stensil. It is written in Clojure.

stensil is a templating engine that works on top of .docx. The templating engine declares a bunch of custom functions to be used in a template. A full list of functions is quite short and can be seen here.

What I need is to add another custom function that checks if a list contains an element. Following existing examples, I did this:

(defmethod call-fn "contains" [_ item items] (contains? items item))

To test this, I created a .docx file that contains the following snippet:

{%= contains(5, data) %}

and a .json file that contains this:

{
  "data": [9, 4, 1, 6, 3]
}

Then I run the application like this:

java -jar stencil-core-0.3.8-standalone.jar template.docx sample.json

Unfortunately, when used, the function I defined doesn't work, throwing the following exception:

java.lang.IllegalArgumentException: contains? not supported on type: java.util.Collections$UnmodifiableRandomAccessList

It appears that items is of type java.util.Collections$UnmodifiableRandomAccessList. Indeed, if I add a (println items) to the function I get this:

#object[java.util.Collections$UnmodifiableRandomAccessList 0x778ca8ef [9, 4, 1, 6, 3]]

I tried doing it differently by using some function. This answer suggests it should work most of the time. Unfortunately both variants below return nil:

(defmethod call-fn "contains" [_ item items] (some #{item} items))
(defmethod call-fn "contains" [_ item items] (some #(= item %) items))

I've never written a line of Clojure in my life before, and a bit lost now. What am I doing wrong? Would it make sense to convert the list from UnmodifiableRandomAccessList to something that some or contains? can work with? If yes, how do I perform such conversion on items variable?

Upd. Tried using .contains as suggested by cfrick in an answer, like this:

(defmethod call-fn "contains" [_ item items] (.contains items item))

This doesn't raise a runtime error, yet the output is always false now. println debugging reveals this:

(defmethod call-fn "contains" [_ item items]
  (println item)
  (println items)
  (.contains items item))
1
#object[java.util.Collections$UnmodifiableRandomAccessList 0x778ca8ef [9, 4, 1, 6, 3]]

Why?

答案1

得分: 4

以上的回答都是正确的,因为contains?不能用于列表的这个目的,而且我们试图比较不同类型。

在这个特定的示例中,第一个参数中的数字是java.lang.Long,而第二个参数中的列表包含java.math.BigDecimal实例。这是因为默认的JSON解析器stencil在独立模式下使用将数字反序列化为BigDecimal

一个快速而不太规范的解决方案是在所有地方使用字符串。

您还可以定义一个专门处理数字的函数变体:

(defmethod call-fn "ncontains" [_ item items]
  (boolean (some #{(double item)} (map double items))))

或者将所有数字强制转换为相同的类型:

(defmethod call-fn "contains" [_ item items]
  (letfn [(norm [x] (if (number? x) (double x) x))]
    (boolean (some #{(norm item)} (map norm items)))))
英文:

The answers above are all correct, because contains? can not be used on lists for this purpose and also we are trying to compare different types.

The problem in this specific example is that the number in the first argument is a java.lang.Long, whereas the list in the second argument contains java.math.BigDecimal instances. This is because the default JSON parser stencil uses in standalone mode deserializes numbers as BigDecimal.

A quick and dirty solution is to use strings everywhere.

You can also define a variation of the function that works specifically with numbers:

(defmethod call-fn "ncontains" [_ item items]
  (boolean (some #{(double item)} (map double items))))

Or coerce all numbers to the same type:

(defmethod call-fn "contains" [_ item items]
  (letfn [(norm [x] (if (number? x) (double x) x))]
    (boolean (some #{(norm item)} (map norm items)))))

答案2

得分: 2

contains?无论如何都不能达到您想要的效果——它适用于支持查找的内容(例如,您可以检查列表是否有索引,而不是值是否在其中)。

从文档中可以看出:

> 如果给定集合中存在键,则返回true,否则返回false。 注意,对于数字索引的集合(例如向量和Java数组),这将测试数值键是否在索引范围内。 'contains?' 花费恒定或对数时间;
它不会为了查找一个值执行线性搜索。另请参阅'some'。

您可以退回到Java在这里提供的内容 Collection.contains:

> 如果此集合包含指定的元素,则返回true。更正式地说,仅当此集合包含至少一个元素e,使得(o==null ? e==null : o.equals(e))成立时,才返回true。

因此,您可以执行以下操作:

(.contains items item)

进一步的探索:

user=> (.contains (java.util.Collections/unmodifiableList [42]) 42)
true
user=> (.contains (java.util.Collections/unmodifiableList [42]) 64)
false
user=> (.contains [42] 42)
true
user=> (.contains [42] 64)
false
英文:

contains? would not do what you want anyway - it is for things that support lookup (e.g. you can check, if a list has an index - not if a value is in there).

From the docs:

> Returns true if key is present in the given collection, otherwise
> returns false. Note that for numerically indexed collections like
> vectors and Java arrays, this tests if the numeric key is within the
> range of indexes.
'contains?' operates constant or logarithmic time;
> it will not perform a linear search for a value. See also 'some'.

You can fall back to what Java has to offer here Collection.contains:

> Returns true if this collection contains the specified element. More formally, returns true if and only if this collection contains at least one element e such that (o==null ? e==null : o.equals(e)).

So you can do:

(.contains items item)

Further exploration:

user=> (.contains (java.util.Collections/unmodifiableList [42]) 42)
true
user=> (.contains (java.util.Collections/unmodifiableList [42]) 64)
false
user=> (.contains [42] 42)
true
user=> (.contains [42] 64)
false

huangapple
  • 本文由 发表于 2020年9月17日 02:28:59
  • 转载请务必保留本文链接:https://go.coder-hub.com/63925973.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定