philip.mcgrath
2019-1-21 01:29:13

@aqazi786 This is fun! For real purposes, I would definitely use a parsing library like @lexi.lambda’s megaparsack or parser-tools: it will let you write the code clearly in the terms of the problem you’re solving, and ideally you will get things like good error messages built in. But, for learning purposes, I wrote up a version without any extra libraries: #lang racket ;; We could solve this using strings directly, ;; but it's easier to visualize with the input as ;; a list of characters. ;; This version also returns a string as a value, ;; rather than printing as a side-effect. ;; We will start with a recursive helper function: ;; decode/list : string? (listof char?) -> (values string? (listof char?)) ;; Given two arguments: ;; - the string accumulated so far, and ;; - a list of characters still to be decoded ;; Returns two values: ;; - a new (longer) output string, and ;; - a new (shorter) list of characters still to be decoded (define (decode/list so-far to-go) (match to-go ;; The easy case is a letter: we just put it onto the end ;; of what's already been decoded and recur on the rest of ;; the list. [(list-rest (? char-alphabetic? c) to-go) (decode/list (string-append so-far (string c)) to-go)] ;; The hard case is the ( int + "[" + ... + "]" ) pattern. ;; This clause recognizes the begining of such a pattern: ;; one or more digits followed by a [. [(list-rest (? char-numeric? digit...) ... #\[ to-go) ;; Convert the list of digits to an integer (define n (string->number (list->string digit...))) (let-values ([(part to-go) ;; Recur on the rest of the list with an empty ;; so-far string. (decode/list "" to-go)]) ;; The part result is the string for the repetition group (match to-go ;; We check that next character is a ] to clist the group. [(list-rest #\] to-go) ;; If so, we keep going, adding the right number of ;; repetitions of part onto so-far. (decode/list (string-append* so-far (make-list n part)) to-go)] [_ ;; Otherwise, the input was bad. (error "missing closing ]")]))] ;; A digit or [ in any other position is a syntax error in the input string ;; (This doesn't strictly need to be a special case, but we can get ;; a slightly nicer error message this way.) [(list-rest (or #\[ (? char-numeric?)) to-go) (error "mis-used digit or [")] ;; If the next thing isn't a letter or the beginning of a repition group, ;; we stop here! ;; Why can we stop now? ;; - We might be inside a repition group, in which case ;; we already wrote the code to check for a chosing ]. ;; - We might be at the top level. We'll handle that case in a minute. [to-go (values so-far to-go)])) ;; Now for the main function: ;; decode : string? -> string? ;; Given the input string, ;; Returns the output string (define (decode s) ;; We call our helper to do most of the work (define-values (decoded to-go) (decode/list "" (string->list s))) (cond ;; There should be no characters left to go, ;; in which case we return the decoded string successfully. [(null? to-go) decoded] ;; Otherwise there might be: ;; - an unmatched ] ;; - some other character (like ?) ;; In either case, it's an error. [else (error "unexpected character")])) ;; some tests: (module+ test (require rackunit) (check-equal? (decode "ab2[ac]") "abacac") (check-equal? (decode "vV2[i]Q") "vViiQ") (check-equal? (decode "a2[Gcc5[Y]dd]vv") "aGccYYYYYddGccYYYYYddvv"))