2
votes

I am experimenting with Racket's 'match' form and would like to match sequences of items in a list. Each item will have particular properties. For example, if I wanted to match alternating sequences of numbers and strings corresponding (roughly) to the regular expression:

#rx"([0-9]+ \"[a-zA-Z0-9]+\")+"

The code below seems to do the job:

(define (match-sequence L)
  (let ([n-lst '()]     ; Used to collect numbers found.
        [s-lst '()])    ; Used to collect strings found.
    (define  (m-test L)
      (match L
             [(list-rest (? number? n) (? string? s)  ... (? m-test))
              (set! n-lst `(,@n-lst ,n))
              (set! s-lst `(,@s-lst ,(car s)))                
              (list (reverse n-lst) (reverse s-lst))]
             ['()
              #t]
             [else
             #f]))
    (m-test L)))

I realize that the #rx and code above don't quite match the same sequences but it is just an analogy.

Is this the most succinct way of writing this in Racket?

I tried patterns like:

(list ((? number? n) (? string? s)) ...)

and Racket did not accept this.

Patterns like: (list (? number? n) (? string? s) ...) require the first item of the list being matched to be numeric and all others to be strings.

I tried quasiquotation and splicing in several ways with no success.

There must be a more elegant formation but I can't seem to find it. Any help would be appreciated. Thanks.

1

1 Answers

5
votes

It looks like you're trying to separate numbers from the rest, which is not that difficult:

(define (match-sequence L)
  (match L
    [(list* (? number? n) (? string? s) ... rest)
     (let ([r (match-sequence rest)])
       (list `(,n ,@(car r)) `(,@s ,@(cadr r))))]
    ['() '(() ())]))

but in that case, you could just use filter, or even better, partition:

(partition number? '(1 "a" "b" 2 3 "c" "d" 4))

But maybe you want to group together the string subsequences, where the above code is closer:

(define (match-sequence L)
  (match L
    [(list* (? number? n) (? string? s) ... rest)
     (let ([r (match-sequence rest)])
       (list `(,n ,@(car r)) `(,s ,@(cadr r))))]
    ['() '(() ())]))

and in that case it's easier to go with multiple values instead of making up a list wrapper:

(define (match-sequence L)
  (match L
    [(list* (? number? n) (? string? s) ... rest)
     (let-values ([(ns ss) (match-sequence rest)])
       (values `(,n ,@ns) `(,s ,@ss)))]
    ['() (values '() '())]))