0

I need to convert strings to 26-ary and then be able to convert them back.

My current code is:

(define (26-ary-word s)
  (let ([len (string-length s)])
        (let f ([n 0]
                [acc (+
                      (- (char->integer (string-ref s 0)) 97)
                      1)]) ; adding 1 so that all strings start with 'b'
          (if (< n len)
              (f (add1 n) (+ (* acc 26) (- (char->integer (string-ref s n)) 97)))
              acc))))

(define (word-ary-26 n)
  (let f ([n (/ (- n (modulo n 26)) 26)]
          [acc (cons (integer->char (+ (modulo n 26) 97)) '())])
    (if (> n 0)
        (f (/ (- n (modulo n 26)) 26) (cons (integer->char (+ (modulo n 26) 97)) acc))
        (list->string (cdr acc))))) ; remove "b" from front of string

I add 1 to acc to start with, and remove the "b" at the end. This is because multiplying "a" - 97 by 26 is still 0.

This is already ugly, but it doesn't even work. "z" is recorded as "701" when it's in the first position (26^2), which is translated back as "az".

I can add another if clause detecting if the first letter is z, but that's really ugly. Is there any way to do this that sidesteps this issue?

          (if (and (= n 0) (= acc 26))
              (f (add1 n) 51)
              (f (add1 n) (+ (* acc 26) (- (char->integer (string-ref s n)) 97))))

This is the ugly edge case handling code I've had to use.

4

1 回答 1

1

Honestly, I'm not entirely sure what your code is doing, but either way, it's far more complicated than it needs to be. Converting a base-26 string to an integer is quite straightforward just by using some higher-order constructs:

; (char-in #\a #\z) -> (integer-in 0 25)
(define (base-26-char->integer c)
  (- (char->integer c) (char->integer #\a)))

; #rx"[a-z]+" -> integer?
(define (base-26-string->integer s)
  (let ([digits (map base-26-char->integer (string->list s))])
    (for/fold ([sum 0])
              ([digit (in-list digits)])
      (+ (* sum 26) digit))))

By breaking the problem into two functions, one that converts individual characters and one that converts an entire string, we can easily make use of Racket's string->list function to simplify the implementation.

The inverse conversion is actually slightly trickier to make elegant using purely functional constructs, but it becomes extremely trivial with an extra helper function that "explodes" an integer into its digits in any base.

; integer? [integer?] -> (listof integer?)
(define (integer->digits i [base 10])
  (reverse
   (let loop ([i i])
     (if (zero? i) empty
         (let-values ([(q r) (quotient/remainder i base)])
           (cons r (loop q)))))))

Then the implementation of the string-generating functions becomes obvious.

; (integer-in 0 25) -> (char-in #\a #\z)
(define (integer->base-26-char i)
  (integer->char (+ i (char->integer #\a))))

; integer? -> #rx"[a-z]+"
(define (integer->base-26-string i)
  (list->string (map integer->base-26-char (integer->digits i 26))))
于 2015-03-08T00:44:34.297 回答