3

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.

1

3 Answers 3

3

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.

Sign up to request clarification or add additional context in comments.

4 Comments

Interesting. I am indeed planning to make another function (or to adapt one of the above) for an input of strings contained in the python list in question — so input could be something like "['get', ['me', 'for], 'not']". However, when I try (string->jsexpr "['get', ['me', 'for], 'not']"), I get the following error from dr racket: string::2: string->jsexpr: bad input starting #"'get', ['me', 'for'], 'not']". Thanks for your response, I imagine this will end up being important.
Yes, that's because JSON strings have double quotes. Again: unless you're trying to parse some Python syntax which can't be translated into JSON, I'd suggest using JSON.
Now that I have my racket functions working in the dr racket IDE, I'm trying to figure how actually to pass the lists between the main python program and this racket code. Do you have any sense of how this could be done? I know this might be too involved for comments. Just wondering whether you could point me in the right direction. (The program is running behind a website.)
@LyovinK.: There are lots of ways I think – presumably some kind of network / web thing, but I don't know how those work in Racket.
2

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")

Comments

1

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")

6 Comments

@Lazerbeak12345 -- the syntax highlighting was so bad in this instance, largely because of the replacement characters in the case form, that I had removed it altogether; it seemed more distracting than helpful.
No problem. I had actually suspected that to be the case but thought I'd give it a try to see your (or the communities) opinion on it.
brackets->parenths was too long so I named the function exnihilo. Thanks again. Was wondering if you had any insight about the comment I just posted in response to tfb's answer, regarding how I might plug my racket file into my existing python project.
@LyovinK. -- I don't know that there is an easy way to call Racket code from Python, but you might be interested in Hy, which is a dialect of Lisp embedded in Python. You can call Hy functions directly from Python.
I have never used Hy, but I've heard good things about it. Regarding stack errors, that is probably because Python does not support tail call optimization; it looks like TCO can be enabled in Hy, which should let you avoid stack errors with tail recursion. You can recurse in Python, you just have to avoid large recursions. I think that Hy effectively compiles a Lisp syntax to Python code. You can read more about Hy vs other Lisps here.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.