3
votes

I want a function that takes a string, consisting of a python-formatted tree, like this

"[0, [1, 0]]"

and outputs a useable racket list, like this

'(0 (1 0))

My first move was to convert the string to a list of chars, with this

(string->list treestring)

which, when called on the string above, results in this

'(#\[ #\0 #\, #\space #\[ #\1 #\, #\space #\0 #\] #\])

I was going then to check for the various chars I am expecting and to parse them accordingly, like this

(define (py-treestringlst-to-rkt-lst treestringlst result)
 (cond
  [(empty? treestringlst) result]
  [(char=? (car treestringlst)  #\[) (append (append result

But I stopped short here, because I realized I don't know how to build an unclosed parenthesis in racket.

I imagine the more elegant solution would involve build-list; an open bracket #\[ would be interpreted as running build-list on every subsequent char, until a closing bracket #\] be found.

But maybe there is an even simpler method that I am missing because (if it was not already clear) I am very much a novice racketeer.

Edit:

Thank you so much, ex nihilo and Óscar López. I believe I can better appreciate the elegance of your solutions, and the mastery they evince, now that I have committed the atrocity below. However, I do not pretend to being so avid a student of Racket that I would not rather have read yours before the sequel and so saved myself the heartache.

; On the other hand, it works — kind of. Always gives a result two layers deep, but I could fix that.... enough... to bed with me
(define (py-treestringlst-to-rkt-lst treestringlst result)
 (cond
  [(empty? treestringlst) result]
  [(char=? (car treestringlst)  #\[) (py-treestringlst-to-rkt-lst (cdr treestringlst) (append result (build-list 1 (lambda (x) '()))))]
  [(char=? (car treestringlst)  #\]) (py-treestringlst-to-rkt-lst (cdr treestringlst) (append result '(bracket)))]
  [(and (char=? (car treestringlst)  #\0) (list? (last result))) (py-treestringlst-to-rkt-lst (cdr treestringlst) (reverse (cons (reverse (cons 0 (reverse (car (reverse result))))) (cdr (reverse result)))))]
  [(and (char=? (car treestringlst)  #\1) (list? (last result))) (py-treestringlst-to-rkt-lst (cdr treestringlst) (reverse (cons (reverse (cons 1 (reverse (car (reverse result))))) (cdr (reverse result)))))]
  [(and (char=? (car treestringlst)  #\0) (not (list? (last result)))) (py-treestringlst-to-rkt-lst (cdr treestringlst) (append result '(0)))]
  [(and (char=? (car treestringlst)  #\1) (not (list? (last result)))) (py-treestringlst-to-rkt-lst (cdr treestringlst) (append result '(1)))]
  [else (py-treestringlst-to-rkt-lst (cdr treestringlst) result)]))


(define (cleanit lst)
  (filter (lambda (x) (not (equal? x '()))) (filter (lambda (x) (not (equal? x 'bracket))) lst)))

; Example call
(cleanit (py-treestringlst-to-rkt-lst '(#\[
  #\[
  #\0
  #\,
  #\space
  #\1
  #\]
  #\,
  #\space
  #\0
  #\,
  #\space
  #\1
  #\,
  #\space
  #\[
  #\0
  #\,
  #\space
  #\1
  #\]
  #\,
  #\space
  #\0
  #\,
  #\space
  #\1
  #\,
  #\space
  #\0
  #\,
  #\space
  #\[
  #\1
  #\,
  #\space
  #\0
  #\]
  #\]) '()))

Edit 2 (re: ex nihilo's edit and tfb's response and comment):

Indeed, ex nihilo, I just came up with the same thing, using your original brackets->parenths function.

Call me crazy, tfb, but this

(define (brackets->parenths s)
  (read
   (open-input-string
    (list->string (map (lambda (x)
                         (case x
                           [(#\[) #\(]
                           [(#\]) #\)]
                           [(#\') #\"]
                           [else x]))
                       (remove* '(#\,)
                                (string->list s)))))))

(brackets->parenths "['get', ['me', 'for'], 'not']")

Actually seems cleaner, to me, than this

(require json)

(string->jsexpr (string-replace "['get', ['me', 'for'], 'not']" "\'" "\""))

Maybe because it seems like it would be easier to manipulate if my inputs get weird.

3
Racket does not allow single quotes for string literals; a single quote has a different meaning in Lisps. Also, your example string is missing a single quote ;) I extended my answer to handle your new input.ad absurdum

3 Answers

3
votes

If your input is fairly constrained – in particular if it's the subset of Python literals which are syntactically the same as JSON like your example, then you can just to this:

(require json)
(string->jsexpr "[0, [1, 0]]")

which will result in (0 (1 0)).

If your Python literal subset is not in the one which is identical in JSON I'd strongly suggest using Python's JSON support to write JSON and then reading JSON back into Racket. Manually reimplementing a great chunk of JSON is not a wheel I'd want to reinvent.

2
votes

Here is a simple way, using built-in procedures in Racket:

(define (py-treestringlst-to-rkt-lst treestringlst)
  (call-with-input-string
   (string-replace (string-replace treestringlst "," "") "'" "\"")
   read))

In Racket [ and ] can be used as substitutes for ( and ), so we don't have to do anything about them. We just need to remove the commas, replace single quotes with double quotes and read the expression using a string port. For example:

(py-treestringlst-to-rkt-lst "[0, [1, 0]]")
=> '(0 (1 0))

(py-treestringlst-to-rkt-lst "['get', ['me', 'for'], 'not']")
=> '("get" ("me" "for") "not")
1
votes

One solution would be to create a list of characters in the input string, and then to remove the commas and change the square brackets to parentheses before finally using the reader to convert the string to a datum. open-input-string can be used to create a string port that can be given to read:

(define (brackets->parenths s)
  (read
   (open-input-string
    (list->string (map (lambda (x)
                         (case x
                           [(#\[) #\(]
                           [(#\]) #\)]
                           [else x]))
                       (remove* '(#\,)
                                (string->list s)))))))

Sample interaction:

scratch.rkt> (brackets->parenths "[1, [2, 3]]")
'(1 (2 3))
scratch.rkt> (brackets->parenths "[1, [2, [3, 4, [[5, 6]]], 7], 8, 9]")
'(1 (2 (3 4 ((5 6))) 7) 8 9)
scratch.rkt> (brackets->parenths "[]")
'()

OP has expressed in comments a desire to put string literals in the Python lists. Python allows string literals with either single or double quotes, but vanilla Racket does not. The Racket reader must encounter a double quote character before beginning to parse a string object. The above code can be extended to handle the desired input by converting single quote characters found in the input to double quote characters:

(define (brackets->parenths s)
  (read
   (open-input-string
    (list->string (map (lambda (x)
                         (case x
                           [(#\[) #\(]
                           [(#\]) #\)]
                           [(#\') #\"]
                           [else x]))
                       (remove* '(#\,)
                                (string->list s)))))))

Sample interaction:

scratch.rkt> (brackets->parenths "['get', ['me', 'for'], 'not']")
'("get" ("me" "for") "not")