列表过滤+计数与直接计数?奇怪的测试结果

时间:2015-09-21 22:40:13

标签: list racket counting

这是关于计算列表中有多少元素完成给定测试。

我看到了这个功能

(define (numsof p lst)
  (length (filter p lst)))

并且认为它效率低,因为它必须经过两个列表,最初的一个用于过滤,然后用length计算结果。所以我实现了这个来直接计算有多少元素满足测试p

 (define (amount p lst [acc 0])
  (if (empty? lst)
      acc
      (amount p (cdr lst) (if
                           (p (car lst))
                           (add1 acc)
                           acc))))

接下来,我使用辅助函数运行了一些测试:

; Creates list of natural numbers in [0, range) of given length
(define (random-list length range)
  (if (zero? length)
      null
      (cons (random range) (random-list (sub1 length) range))))

(for ([i 10])
  (display "numsof: ") (time (numsof odd? (random-list 999999 9999999)))
  (display "amount: ") (time (amount odd? (random-list 999999 9999999)))
  (displayln ""))

现在我得到的结果对我来说非常出乎意料,因为我认为我的定义amount的速度应该是numsof的两倍,但我还没有真正进入算法性能,所以这个无论如何,猜测可能显然是假的。

在这里,有一些测试结果y' all:

numsof: cpu time: 2875 real time: 2710 gc time: 2060
amount: cpu time: 2578 real time: 2590 gc time: 1872

numsof: cpu time: 1484 real time: 1494 gc time: 719
amount: cpu time: 2547 real time: 2586 gc time: 1779

numsof: cpu time: 2422 real time: 2449 gc time: 1748
amount: cpu time: 2593 real time: 2608 gc time: 1843

numsof: cpu time: 1375 real time: 1360 gc time: 658
amount: cpu time: 2641 real time: 2662 gc time: 1842

numsof: cpu time: 2609 real time: 2593 gc time: 1873
amount: cpu time: 1406 real time: 1400 gc time: 655

numsof: cpu time: 2640 real time: 2652 gc time: 1938
amount: cpu time: 1360 real time: 1384 gc time: 623

有人可以向我解释我的功能是更快还是更慢;无论如何,为什么?测试结果发生了什么,我无法理解它们。

1 个答案:

答案 0 :(得分:3)

更新:大部分内容都是错误的。最后跳到“更新:只读这个”。

我得到了类似的结果。

一个可能的解释是,如果Racket list - 不可变,记住 - 存储它们的长度,以便length只是查找一个像结构成员的值,而不是遍历所有列表元素。 (我依旧回忆起在Racket邮件列表上读到这样的内容,但遗憾的是现在找不到它。)

可能的证据是,随着列表大小的增加,length是否需要更长的时间:

(for ([len (in-list (list 100 1000 10000 100000 1000000))])
  (define xs (build-list len values))
  (time (length xs)))

cpu time: 0 real time: 0 gc time: 0
cpu time: 0 real time: 0 gc time: 0
cpu time: 0 real time: 0 gc time: 0
cpu time: 1 real time: 0 gc time: 0
cpu time: 4 real time: 3 gc time: 0

好的,最后两个时间非零。但它们非常小。出于实际目的,O(1),而不是O(n),即使是相当大的n。

<强>更新

实际上,我跳过了一大步。我的回答解释了我认为你的散文所问的内容,而不是你所展示的测试代码。我认为你想要的测试代码 - 实际上符合你的问题 - 将是这样的:

(for ([i 10])
  (define xs (random-list 999999 9999))
  (display "numsof: ") (time (numsof odd? xs))
  (display "amount: ") (time (amount odd? xs))
  (displayln ""))

这会创建一个随机列表,然后只会在该列表上运行numsofamount本身的时间。

这为numsofamount提供了基本相同的时间安排。

对此的解释是,如果length实际上是O(1),因为Racket列表存储它们的长度。

至于为什么原始的,提供的测试代码在random-list的调用中显示出如此不同的结果?我认为这仅仅是因为内存分配和垃圾收集时间不太可预测。

更新:刚刚阅读

我在这个答案中说的几乎所有内容都证明是错的。具体做法是:

  • length不会缓存。 list?确实如此。

  • 我打破了第一个剖析规则。我没有使用普通的命令行球拍。因此,我的测量结果受到errortrace注释的影响。

  • 此外,我可能应该在每个(for ([_ 3]) (collect-garbage))之前使用time,专注于独立于垃圾收集时间的算法。

关于我原来答案中唯一的值是,对于足够小的列表,可能很好地执行诸如撰写lengthfilter之类的事情。但实际上,这是一个明显的答案,这个问题就像是,“是否有时候可以在速度上表达清晰度?”。对问题的简短回答是“是的,这取决于”。