【问题标题】:Count occurrence of element in a list in Scheme?计算Scheme列表中元素的出现次数?
【发布时间】:2011-08-10 01:32:54
【问题描述】:

例如,如果我可以在命令式语言中使用数组或在 C++ 中使用映射(树结构),这将非常容易。在计划中,我不知道如何开始这个想法?谁能帮我解决这个问题?

谢谢,

【问题讨论】:

    标签: scheme


    【解决方案1】:

    您的问题对计数的内容不是很具体。我假设您想创建某种元素的频率表。有几种方法可以解决这个问题。 (如果您使用的是 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 添加到 baz 的值,您创建一个排除 baz 的新 alist:

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

    然后重新添加baz 的新值:

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

    第二步是exclude函数的作用,可能是函数中最复杂的部分。

    便携、简洁、快速,但不实用

    更直接的方法是使用哈希表(来自 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)))
    

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

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

    (define (bagify lst)
      (foldl (lambda (key ht)
               (hash-update ht key add1 0))
             #hash() lst))
    

    您甚至可以为此使用 for 推导:

    (define (bagify lst)
      (for/fold ((ht #hash()))
                ((key (in-list lst)))
        (hash-update ht key add1 0)))
    

    这更多地表明了可移植 SRFI 69 散列库的缺点,而不是 Scheme 在执行纯功能任务方面的任何特定失败。有了合适的库,这项任务就可以轻松且功能性地实现。

    【讨论】:

    • 首先,非常感谢您的回答。但是,您的解决方案远远超出了我目前所知道的,尤其是没有 cmets。我真的不知道它是如何工作的。如果你有时间,你会不会把它拆成一个小盒子,并添加一些 cmets。在避免弄清楚自己时,我可能听起来很懒惰,但这对我来说真的太多了。我想我不会从你的例子中学到很多东西。
    • @Chan:如果您愿意坚持使用 Racket,我会用更简单的方法更新我的答案。 (是的,这是一个基于fold 的解决方案!:-O)
    【解决方案2】:

    在 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 的类型,以及 list 的定义为空列表或一对两个事物,我们需要检查它是哪种列表:

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

    第一种情况应该很明显:空列表中有多少what 项?

    对于第二种情况,您知道它是一个非空列表,因此您有两条信息:它的头部(使用 firstcar 获得)和尾部(使用 @ 获得987654332@或cdr):

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

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

    【讨论】:

    • @Eli Barzilay:说实话,我不是在寻找完整的代码。此外,我没有看到您的回答中有任何提示。我了解cdrcar ...等。我正在寻找的是如何构建每个元素出现的列表。比如我遇到a,增加1,但是a应该存储在哪个结构中,怎么存储?
    • 添加了详细的解释——不是参考书:你真的应该读那本书!
    • @Eli Barzilay:非常感谢。你能给我推荐一本书吗?我只是通过谷歌搜索学习 Scheme。
    • (“not”应该是“note”。)
    • 如果您正在自学该语言,您绝对应该阅读它。它是免费的,而且是在线的,所以没有任何借口可以避免它。
    【解决方案3】:

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

    • 您必须“传递”您正在搜索的元素以及每次调用函数的当前计数
    • 如果你点击了空列表,你就完成了列表,你可以输出结果
    • 如果列表的 car 等于您要查找的元素,则使用列表的 cdr 和计数器 + 1 递归调用函数
    • 如果不是,则使用列表的 cdr 和与之前相同的计数器值递归调用该函数

    【讨论】:

    • 我正在寻找一个想法,一个提示而不是一个完整的解决方案。另外,除了 Scheme 背后的理论,我在您的回答中看不到任何给定的提示。
    • @Chan:我给了你一个提示。通过移除头部、检查头部并递归地继续尾部来移动列表。
    • 谢谢,好多了。但是,我需要花一些时间来思考。我会告诉你的。
    • 我把 car 和 cdr 搞混了。我修好了。
    【解决方案4】:

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

    【讨论】:

    • 错了。它与问题无关,如今,如果您需要在方案中使用哈希表,那么您可以使用...哈希表。 (参见 R6RS。)
    • 我稍后发表评论,其中 OP 表示他们想要创建一个包含每个元素出现的列表。不过,感谢有关 r6rs 哈希表的提示。
    • (这不是问题。)
    猜你喜欢
    • 2020-06-09
    • 1970-01-01
    • 2018-11-20
    • 2012-08-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多