schemedoc / cookbook

New Scheme Cookbook
https://cookbook.scheme.org
29 stars 3 forks source link

[Recipe] check if all elements are the same #70

Closed jcubic closed 3 months ago

jcubic commented 3 months ago

Problem

Check if all elements are the same using a predicate.

Solution

I'm not sure if there is already a utility like this in some of SRFI. But there are few solutions on StackOverflow, but they use equal? instead of predicate: Scheme: How to check if all elements of a list are identical.

I came up with a solution using fold:

(define (ordered? fn . args)
  (let ((first (car args)))
    (fold (lambda (arg result)
            (and result (equal? arg first)))
          #t
          (cdr args))))

Credit: @jcubic

Usage

It can be used to check if strings are sorted (for R5RS implementation where string comparators accept 2 arguments) or check if all elements are equal. You can also check if any sequence is monotonous.

(ordered? eq? 1 1 1 1 1 1)
(define (identical? . args)
   (apply ordered? eq? args))

(identical? 1 1 1 1 1 1 1)
;; ==> #t
(identical? 1 1 1 1 1 1 0)
;; ==> #f
jcubic commented 3 months ago

It seems one of them is in open PR #43, but this one is much better IMHO.

arthurgleckler commented 3 months ago

It would be good to have a version that short-circuits the rest of the tests as soon as one is not identical to the others.

lassik commented 3 months ago

Yes. More generally, a fold with a "stop condition" might be useful.

lassik commented 3 months ago
(define (ordered? fn . args)
  (let ((first (car args)))
    (fold (lambda (arg result)
            (and result (equal? arg first)))
          #t
          (cdr args))))

(equal? arg first) should probably be (fn result arg)

jcubic commented 3 months ago

I was testing with equal? now I see that it will not work. You need to have a version of reduce that get a pair of arguments. Like int the linked PR for "Map over n consecutive elements inside a list" but instead of map there should be version of fold so you can exit early. There can be two versions.

I need revisit #43

lassik commented 3 months ago

lol, I was negligent too.

Here's one that should work:

(define (ordered-list? <= list)
  (or (null? list)
      (let loop ((prev (car list))
                 (list (cdr list)))
        (or (null? list)
            (and (<= prev (car list))
                 (loop (car list)
                       (cdr list)))))))

(define (ordered? <= . args)
  (ordered-list? <= args))

(ordered? <) => #t
(ordered? < 1 2 3 4 5) => #t
(ordered? < 1 2 3 4 4) => #f
(ordered? <= 1 2 3 4 4) => #t
(ordered? string<? "a" "ab" "ba") => #t
(ordered? string<? "a" "ab" "ab") => #f
lassik commented 3 months ago

<= is perhaps not a good name for the compator in the general case.