球拍:如何对点分隔的数字进行排序

Racket: how to sort dot-separated numbers

如何在 Racket 中用点分隔数字,例如软件的版本号?

例如

'("1.1.2" "1.0.0" "1.3.3" "1.0.7" "1.0.2")

分类为

'("1.0.0" "1.0.2" "1.0.7" "1.1.2" "1.3.3")

#\. 上拆分每个字符串并将其转换为数字列表,并根据该转换进行排序。使用 SRFI-67 比较列表的示例:

#lang racket
(require srfi/67)

(define versions '("1.1.2" "1.0.0" "1.12.1" "1.3.3" "1.0.7" "1.0.2"))

(define (sort-versions vlst)
  (sort vlst (lambda (a b) (< (list-compare integer-compare a b) 0))
        #:key (lambda (v) (map string->number (string-split v ".")))
        #:cache-keys? #t))

(writeln (sort-versions versions))

从头开始的普通方案:

;; Convert a character into a number
;; Example: (char->number #) => 3

(define (char->number char)
  (case char
    ((#[=10=]) 0)
    ((#) 1)
    ((#) 2)
    ((#) 3)
    ((#) 4)
    ((#) 5)
    ((#) 6)
    ((#) 7)
    ((#) 8)
    ((#) 9)))

;; Convert a list of characters into a decimal number.
;; Example: (list->decimal '(# # #)) => 123

(define (list->decimal digits)
  (let loop ((digits digits)
             (value 0))
    (if (pair? digits)
        (loop (cdr digits)
              (+ (* value 10)
                 (char->number (car digits))))
        value)))

;; Convert a version string into list of decimals.
;; Example: (version->list "1.2.3") => (1 2 3)

(define (version->list version)
  (let loop ((chars    (string->list version))
             (fragment '())
             (result   '()))
    (if (pair? chars)
        (let ((char (car chars))
              (chars (cdr chars)))
          (if (char=? char #\.)
              (loop chars
                    '()
                    (cons (list->decimal fragment)
                          result))
              (loop chars
                    (cons char fragment)
                    result)))
        (reverse (cons (list->decimal fragment)
                       result)))))

;; Convert a list of version numbers into a string.
;; Example: (list->version '(1 2 3)) => "1.2.3"

(define (list->version numbers)
  (let loop ((numbers numbers)
             (result "")
             (delimiter ""))
    (if (pair? numbers)
        (loop (cdr numbers)
              (string-append result
                             delimiter
                             (number->string (car numbers)))
              ".")
        result)))

;; Check if a version is lower than the other.
;; Example: (version<? '(1 2 3) '(1 2)) => #f

(define (version<? v1 v2)
  (if (pair? v1)
      (if (pair? v2)
          (let ((m1 (car v1))
                (m2 (car v2)))
            (cond
             ((< m1 m2) #t)
             ((> m1 m2) #f)
             (else (version<? (cdr v1)
                              (cdr v2)))))
          #f)
      (if (pair? v2)
          #t
          #f)))

;; Sort versions.

(define (sort-versions versions)
  (map list->version
       (sort version<?
             (map version->list versions))))

;; Example

(let ((unsorted '("1.1.2" "1.0.0" "1.3.3" "1.0.7" "1.0.2"))
      (sorted   '("1.0.0" "1.0.2" "1.0.7" "1.1.2" "1.3.3")))
  (equal? (sort-versions unsorted)
          sorted))

以上将所有版本字符串转换为小数列表,对列表进行排序并将小数列表转换回字符串。正如肖恩的回答中所解释的那样,可以避免最后一步。这使得在对版本进行排序时有必要保留原始版本字符串。这可以通过装箱版本的两个表示来实现。比较必须拆箱列表表示。结果是通过拆箱字符串表示创建的。下面显示了这个替代实现。

;; Convert a character into a number
;; Example: (char->number #) => 3

(define (char->number char)
  (case char
    ((#[=11=]) 0)
    ((#) 1)
    ((#) 2)
    ((#) 3)
    ((#) 4)
    ((#) 5)
    ((#) 6)
    ((#) 7)
    ((#) 8)
    ((#) 9)))

;; Convert a list of characters into a decimal number.
;; Example: (list->decimal '(# # #)) => 123

(define (list->decimal digits)
  (let loop ((digits digits)
             (value 0))
    (if (pair? digits)
        (loop (cdr digits)
              (+ (* value 10)
                 (char->number (car digits))))
        value)))

;; Convert a version string into list of decimals.
;; Example: (version->list "1.2.3") => ("1.2.3" 1 2 3)

(define (version->list version)
  (let loop ((chars    (string->list version))
             (fragment '())
             (result   '()))
    (if (pair? chars)
        (let ((char (car chars))
              (chars (cdr chars)))
          (if (char=? char #\.)
              (loop chars
                    '()
                    (cons (list->decimal fragment)
                          result))
              (loop chars
                    (cons char fragment)
                    result)))
        (cons version
              (reverse (cons (list->decimal fragment)
                             result))))))

;; Convert a list of version numbers into a string.
;; Example: (list->version '("1.2.3" 1 2 3)) => "1.2.3"

(define list->version car)

;; Check if a version is lower than the other.
;; Example: (version<? '("1.2.3" 1 2 3) '("1.2" 1 2)) => #f

(define (version<? v1 v2)
  (let loop ((v1 (cdr v1))
             (v2 (cdr v2)))
    (if (pair? v1)
        (if (pair? v2)
            (let ((m1 (car v1))
                  (m2 (car v2)))
              (cond
               ((< m1 m2) #t)
               ((> m1 m2) #f)
               (else (loop (cdr v1)
                           (cdr v2)))))
            #f)
        (if (pair? v2)
            #t
            #f))))

;; Sort versions.

(define (sort-versions versions)
  (map list->version
       (sort version<?
             (map version->list versions))))

;; Example

(let ((unsorted '("1.1.2" "1.0.0" "1.3.3" "1.0.7" "1.0.2"))
      (sorted   '("1.0.0" "1.0.2" "1.0.7" "1.1.2" "1.3.3")))
  (equal? (sort-versions unsorted)
          sorted))

您可以将每个字符串 "X.Y.Z" 转换为数字,对数字列表进行排序,然后将数字转换回字符串。您可以使用许多将 X.Y.Z 编码为数字的想法,例如 Goedel's encoding, chinese remainder theorem