Count occurrence of element in a list in Scheme?

Your question wasn’t very specific about what’s being counted. I will presume you want to create some sort of frequency table of the elements. There are several ways to go about this. (If you’re using Racket, scroll down to the bottom for my preferred solution.)

Portable, pure-functional, but verbose and slow

This approach uses an association list (alist) to hold the elements and their counts. For each item in the incoming list, it looks up the item in the alist, and increments the value of it exists, or initialises it to 1 if it doesn’t.

(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))

The incrementing is the interesting part. In order to be pure-functional, we can’t actually change any element of the alist, but instead have to exclude the association being changed, then add that association (with the new value) to the result. For example, if you had the following alist:

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

and wanted to add 1 to baz‘s value, you create a new alist that excludes baz:

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

then add baz‘s new value back on:

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

The second step is what the exclude function does, and is probably the most complicated part of the function.

Portable, succinct, fast, but non-functional

A much more straightforward way is to use a hash table (from SRFI 69), then update it piecemeal for each element of the list. Since we’re updating the hash table directly, it’s not pure-functional.

(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)))

Pure-functional, succinct, fast, but non-portable

This approach uses Racket-specific hash tables (which are different from SRFI 69’s ones), which do support a pure-functional workflow. As another benefit, this version is also the most succinct of the three.

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

You can even use a for comprehension for this:

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

This is more a sign of the shortcomings of the portable SRFI 69 hashing library, than any particular failing of Scheme for doing pure-functional tasks. With the right library, this task can be implemented easily and functionally.

Leave a Comment