计算Scheme中列表中元素的出现次数?

时间:2011-04-21 06:20:34

标签: scheme

如果我可以在命令式语言中使用数组或在C ++中使用map(树形结构),这非常容易。在计划中,我不知道如何开始这个想法?任何人都可以帮我吗?

谢谢,

4 个答案:

答案 0 :(得分:9)

你的问题对于计算的内容并不十分具体。我假设您想要创建某种元素的频率表。有几种方法可以解决这个问题。 (如果您使用的是Racket,请向下滚动到底部以获取我的首选解决方案。)

便携式,纯功能,但冗长而缓慢

此方法使用关联列表(alist)来保存元素及其计数。对于传入列表中的每个项目,它会在alist中查找该项目,并增加其存在的值,或者如果不存在则将其初始化为1.

(define (bagify lst)
  (define (exclude alist key)
    (fold (lambda (ass result)
            (if (equal? (car ass) key)
                result
                (cons ass result)))
          '() alist))
  (fold (lambda (key bag)
          (cond ((assoc key bag)
                 => (lambda (old)
                      (let ((new (cons key (+ (cdr old) 1))))
                        (cons new (exclude bag key)))))
                (else (let ((new (cons key 1)))
                        (cons new bag)))))
        '() lst))

增量是有趣的部分。为了实现纯函数,我们实际上不能更改alist的任何元素,而是必须排除正在更改的关联,然后将该关联(使用新值)添加到结果中。例如,如果您有以下列表:

((foo . 1) (bar . 2) (baz . 2))

并想要将{1}的值加1,您创建了一个排除baz的新列表:

baz

然后重新添加((foo . 1) (bar . 2)) 的新值:

baz

第二步是((baz . 3) (foo . 1) (bar . 2)) 函数的作用,可能是函数中最复杂的部分。

便携,简洁,快速但无功能

更直接的方法是使用哈希表(来自SRFI 69),然后为列表的每个元素逐个更新它。由于我们直接更新哈希表,因此它不是纯函数。

exclude

纯功能,简洁,快速但不便携

这种方法使用特定于Racket的哈希表(与SRFI 69的哈希表不同),它们支持纯功能工作流。另一个好处是,这个版本也是三者中最简洁的。

(define (bagify lst)
  (let ((ht (make-hash-table)))
    (define (process key)
      (hash-table-update/default! ht key (lambda (x) (+ x 1)) 0))
    (for-each process lst)
    (hash-table->alist ht)))

你甚至可以使用(define (bagify lst) (foldl (lambda (key ht) (hash-update ht key add1 0)) #hash() lst)) 理解:

for

这更多地表明了便携式SRFI 69哈希库的缺点,而不是任何特定的用于执行纯功能任务的Scheme失败。使用正确的库,可以轻松实现此任务。

答案 1 :(得分:4)

在Racket中,你可以做到

(count even? '(1 2 3 4))

但更严重的是,使用Scheme中的列表执行此操作比您提到的更容易。列表为空,或者是一对包含第一个项目和其余项目。在代码中遵循该定义,您将使其“自行编写”。

这是一个基于HtDP开头的提示(这是一本很好的书,可以了解这些事情)。从函数“header”开始 - 它应该接收一个谓词和一个列表:

(define (count what list)
  ...)

添加输入的类型 - what是一些值,list是一个列表:

;; count : Any List -> Int
(define (count what list)
  ...)

现在,鉴于list的类型,以及列表的定义为空列表或两对内容,我们需要检查它是哪种列表:

;; count : Any List -> Int
(define (count what list)
  (cond [(null? list) ...]
        [else ...]))

第一种情况应该是显而易见的:空列表中有多少what个项目?

对于第二种情况,您知道它是一个非空列表,因此您有两条信息:它的头部(使用firstcar获得)及其尾部(其中)你得到restcdr):

;; count : Any List -> Int
(define (count what list)
  (cond [(null? list) ...]
        [else ... (first list) ...
              ... (rest list) ...]))

现在您只需要弄清楚如何组合这两条信息来获取代码。最后一点非常简单的信息是:由于(非空)列表的尾部本身就是一个列表,因此您可以使用count来计算其中的内容。因此,您可以进一步得出结论,您应该在那里使用(count what (rest list))

答案 2 :(得分:2)

在像Scheme这样的函数式编程语言中,你必须有所不同,并利用列表的构建方式。不是通过递增索引来迭代列表,而是递归地遍历列表。您可以使用car(单个元素)删除列表的头部,您可以使用cdr(列表本身)获得尾部,并且可以将头部及其尾部与{{1}粘合在一起}。你的功能大纲是这样的:

  • 您必须“移交”您正在搜索的元素以及每次调用函数的当前计数
  • 如果您点击空列表,则表明您已完成列表,可以输出结果
  • 如果列表中的汽车等于您要查找的元素,请使用列表的cdr和计数器+ 1
  • 递归调用该函数
  • 如果没有,请使用列表的cdr以及与之前相同的计数器值递归调用该函数

答案 3 :(得分:0)

在Scheme中,您通常使用association lists作为O(n)穷人的哈希表/字典。唯一剩下的问题是如何更新相关元素。