#lang racket/base
(require racket/contract/base
         racket/dict
         (for-syntax racket/base))

; list-prefix? : list? list? -> boolean?
; Is l a prefix or r?
(define (list-prefix? ls rs)
  (or (null? ls)
      (and (pair? rs)
           (equal? (car ls) (car rs))
           (list-prefix? (cdr ls) (cdr rs)))))

;; Eli: How about a version that removes the equal prefix from two lists
;; and returns the tails -- this way you can tell if they're equal, or
;; one is a prefix of the other, or if there was any equal prefix at
;; all.  (Which can be useful for things like making a path relative to
;; another path.)  A nice generalization is to make it get two or more
;; lists, and return a matching number of values.

(define (internal-split-common-prefix as bs same? keep-prefix?)
  (let loop ([as as] [bs bs])
    (if (and (pair? as) (pair? bs) (same? (car as) (car bs)))
        (let-values ([(prefix atail btail) (loop (cdr as) (cdr bs))])
          (values (and keep-prefix? (cons (car as) prefix)) atail btail))
        (values null as bs))))

(define (split-common-prefix as bs #:same? [same? equal?])
  (internal-split-common-prefix as bs same? #t))

(define (take-common-prefix as bs #:same? [same? equal?])
  (let-values ([(prefix atail btail) (internal-split-common-prefix as bs same? #t)])
    prefix))

(define (drop-common-prefix as bs #:same? [same? equal?])
  (let-values ([(prefix atail btail) (internal-split-common-prefix as bs same? #f)])
    (values atail btail)))

(provide/contract
 [list-prefix? (list? list? . -> . boolean?)]
 [split-common-prefix
  (->* (any/c any/c) (#:same? procedure?) (values list? any/c any/c))]
 [take-common-prefix
  (->* (any/c any/c) (#:same? procedure?) list?)]
 [drop-common-prefix
  (->* (any/c any/c) (#:same? procedure?) (values any/c any/c))])


(define (filter-multiple l . fs)
  (apply values
         (map (lambda (f) (filter f l)) fs)))

;; Listof[A] Listof[B] B -> Listof[B]
;; pads out t to be as long as s
(define (extend s t extra)
  (append t (build-list (max 0 (- (length s) (length t))) (lambda _ extra))))

(provide filter-multiple extend)

;; ryanc added:

(provide/contract
 [check-duplicate
  (->* (list?)
       (#:key (-> any/c any/c)
        #:same? (or/c dict? (-> any/c any/c any/c)))
       any)])

;; check-duplicate : (listof X)
;;                   #:key (X -> K)
;;                   #:same? (or/c (K K -> bool) dict?)
;;                -> X or #f
(define (check-duplicate items
                        #:key [key values]
                        #:same? [same? equal?])
  (cond [(procedure? same?)
         (cond [(eq? same? equal?)
                (check-duplicate/t items key (make-hash) #t)]
               [(eq? same? eq?)
                (check-duplicate/t items key (make-hasheq) #t)]
               [(eq? same? eqv?)
                (check-duplicate/t items key (make-hasheqv) #t)]
               [else
                (check-duplicate/list items key same?)])]
        [(dict? same?)
         (let ([dict same?])
           (if (dict-mutable? dict)
               (check-duplicate/t items key dict #t)
               (check-duplicate/t items key dict #f)))]))
(define (check-duplicate/t items key table mutating?)
  (let loop ([items items] [table table])
    (and (pair? items)
         (let ([key-item (key (car items))])
           (if (dict-ref table key-item #f)
               (car items)
               (loop (cdr items) (if mutating?
                                     (begin (dict-set! table key-item #t) table)
                                     (dict-set table key-item #t))))))))
(define (check-duplicate/list items key same?)
  (let loop ([items items] [sofar null])
    (and (pair? items)
         (let ([key-item (key (car items))])
           (if (for/or ([prev (in-list sofar)])
                 (same? key-item prev))
               (car items)
               (loop (cdr items) (cons key-item sofar)))))))

;; Eli: Just to have a record of this: my complaint about having this
;; code separately from `remove-duplicates' still stands.  Specifically,
;; that function decides when to use a hash table to make things faster,
;; and this code would benefit from the same.  It would be much better
;; to extend that function so it can be used for both tasks rather than
;; a new piece of code that does it (only do it in a worse way, re
;; performance).  Doing this can also benefit `remove-duplicates' -- for
;; example, make it accept a container so that users can choose how
;; when/if to use a hash table.

;; sam added from carl

(define-syntax (values->list stx)
  (syntax-case stx ()
    [(vl expr)
     (syntax/loc stx
       (call-with-values (lambda () expr) list))]))

(define (map/list n f ls)
  (cond
   [(andmap null? ls) (build-list n (lambda (i) null))]
   [(andmap pair? ls)
    (let* ([vs (values->list (apply f (map car ls)))]
           [k (length vs)])
      (unless (= k n)
        (error 'map/values
               "~a produced ~a values, not ~a: ~e"
               f k n vs))
      (map cons vs (map/list n f (map cdr ls))))]
   [else (error 'map/values "list lengths differ")]))

(define (map/values n f . ls)
  (apply values (map/list n f ls)))

(define (map2 f . ls)
  (apply values (map/list 2 f ls)))

(provide map2 map/values)

;; dvanhorn added:

(define (remf f ls)
  (cond [(null? ls) '()]
        [(f (car ls)) (cdr ls)]
        [else 
         (cons (car ls)
               (remf f (cdr ls)))]))

(provide/contract [remf (-> procedure? list? list?)])


;; stamourv added:

;; (y y -> bool) (listof x) #:key (x -> y) -> (listof (listof x))
;; groups together elements that are considered equal
;; =? should be reflexive, transitive and commutative
(define (group-by =? l #:key [key values])
  (for/fold ([res '()]) ; list of lists
      ([elt (in-list l)])
    (let loop ([classes     res] ; "zipper" of the equivalence classes
               [rev-classes '()])
      (cond [(null? classes)
             ;; did not find an equivalence class, create a new one
             (cons (list elt) res)]
            [(=? (key elt) (key (car (car classes))))
             ;; found the equivalence class
             (append rev-classes ; we keep what we skipped
                     ;; we extend the current class
                     (list (cons elt (car classes)))
                     (cdr classes))] ; and add the rest
            [else ; keep going
             (loop (cdr classes)
                   (cons (car classes) rev-classes))]))))

(provide/contract
 [group-by (->* (procedure? list?) (#:key procedure?)
                list?)])
