如何计算包含NaNs的序列的频率?

时间:2019-10-29 20:10:27

标签: clojure

frequencies的结果用于包含NaN的序列时是错误的,例如:

=> (frequencies [Double/NaN Double/NaN])
{NaN 1, NaN 1}

而不是预期的{NaN 2}

此外,运行时间从预期/平均O(n)恶化到最坏情况的O(n^2),例如

=> (def v3 (vec (repeatedly 1e3 #(Double/NaN))))
=> (def r (time (frequencies v3)))
"Elapsed time: 36.081751 msecs"
...
=> (def v3 (vec (repeatedly 1e3 #(Double/NaN))))
=> (def r (time (frequencies v3)))
"Elapsed time: 3358.490101 msecs"
...

即10倍的元素需要100倍的更长的运行时间。

当序列中有O(n)个时,如何用(预期/平均)运行NaN来计算频率?


作为旁注:

 => (frequencies (repeat 1e3 Double/NaN))
 {NaN 1000}

产生预期结果,可能是因为序列中的所有元素都是同一对象的引用。

2 个答案:

答案 0 :(得分:4)

NaN在许多编程语言中都很奇怪,部分原因是IEEE 754浮点数标准定义了NaN不应该等于任何东西,甚至不等于它。导致您看到的大多数怪异行为的是“甚至不是自身”部分。如果您感到好奇,请在此处查看更多信息:https://github.com/jafingerhut/batman

下面的示例功能可能适合您的需求。它在返回的地图中使用:nan-kw指示找到了多少个NaN。如果用## NaN替换:nan-kw,则由于## NaN的怪异,返回的映射的缺点是您无法使用(获取频率-ret-value ## NaN)来找到计数。

(defn frequencies-maybe-nans [s]
  (let [separate-nans (group-by #(and (double? %) (Double/isNaN %)) s)
        num-nans (count (separate-nans true))]
    (merge (frequencies (separate-nans false))
           (when-not (zero? num-nans)
             {:nan-kw num-nans}))))

(def freqs (frequencies-maybe-nans [1 2 ##NaN 5 5]))
freqs
(get freqs 2)
(get freqs :nan-kw)

答案 1 :(得分:3)

JVM上NaN值的一些背景:https://www.baeldung.com/java-not-a-number


您可以通过在计算频率时临时编码NaN值来解决此问题:

(ns tst.demo.core
  (:use tupelo.core
        tupelo.test))

(defn is-NaN? [x] (.isNaN x))

(defn nan-encode
  [arg]
  (if (is-NaN? arg)
    ::nan
    arg))

(defn nan-decode
  [arg]
  (if (= ::nan arg)
    Double/NaN
    arg))

(defn freq-nan
  [coll]
  (it-> coll
    (mapv nan-encode it)
    (frequencies it)
    (map-keys it nan-decode)))

(dotest
  (let [x [1.0 2.0 2.0 Double/NaN Double/NaN Double/NaN]]
    (is= (spyx (freq-nan x)) {1.0   1,
                              2.0   2,
                              ##NaN 3})))

结果:

-------------------------------
   Clojure 1.10.1    Java 13
-------------------------------

Testing tst.demo.core

(freq-nan x) => {1.0 1, 2.0 2, ##NaN 3}

FAIL in (dotest-line-25) (core.clj:27)
expected: (clojure.core/= (spyx (freq-nan x)) {1.0 1, 2.0 2, ##NaN 3})
  actual: (not (clojure.core/= {1.0 1, 2.0 2, ##NaN 3} {1.0 1, 2.0 2, ##NaN 3}))

请注意,即使计算并打印出正确的结果,单元测试仍然会失败,因为NaN从不等于任何东西,甚至不等于任何东西。如果要通过单元测试,则需要像以下这样保留在占位符::nan中:

(defn freq-nan
  [coll]
  (it-> coll
    (mapv nan-encode it)
    (frequencies it)
  ))

(dotest
  (let [x [1.0 2.0 2.0 Double/NaN Double/NaN Double/NaN]]
    (is= (spyx (freq-nan x)) {1.0   1,
                              2.0   2,
                              ::nan 3})))