Automaton
Automaton

Reputation: 33

Functional solution for converting a string to a vector of integers in Clojure

I've successfully written a function that converts a space delimited string of integers to a vector of integers in Clojure but since I'm (very) new to functional languages I'm worried I'm still thinking too procedurally.

The function uses split to tokenize the string then iterates through the returned vector individually converting the tokens to integers before appending them to a new vector. I'm using read-string because the input is self provided and I'm not really concerned about safety.

    (defn parser [myStr]
        ;;counter
        (def i 0)
        ;;tokenizes string and returns vector of tokens
        (def buffer (clojure.string/split myStr #"\s"))
        ;;reads vector of strings as integers then appends them to a new vector x
        (def x (vector-of :int))
        (while ( < i (count buffer))
            (def x (conj x (read-string (nth buffer i)))) 
            (def i (inc i)))
        (println x))

My code works but I'm concerned that by changing states and iterating through the buffer vector that I'm cheating a bit and sticking to my procedural roots.

Is there a more elegant or functional way to solve this problem?

Upvotes: 3

Views: 624

Answers (1)

Carcigenicate
Carcigenicate

Reputation: 45745

There's a few very notable things here:

  • Never use def inside of defn unless you have a very good reason to. The use case here isn't justified. Just use let instead:

    (defn parser [myStr]
      (let [i 0
            buffer (clojure.string/split myStr #"\s")
            x (vector-of :int)]
         ...)
    

    To see what the difference is, run your function, then check what i holds. def creates globals that persist after the function exits, which leaks the state of the function and pollutes the namespace.

  • You're using read-string to parse. Don't do that. Just use Java's Long/parseLong. read-string has eval behavior, which is never good to abuse. You can also use clojure.edn/read-string which can read Clojure structures and literals, but doesn't execute code.

  • You're using while to carry out side effects when really you could use loop or a number of other functional methods. @xs0 is basically right. I'd write your function as:

    (defn parser [myStr]
      ; The v in mapv means it returns a vector
      ; Just map returns a lazy seq
      (mapv #(Long/parseLong %) (clojure.string/split myStr #"\s"))
    

    Unfortunately Long/parseLong needs to be wrapped in a function since Java interop methods can't be used like normal Clojure functions.

    Long/parseLong is only safe to use if you can guarantee that each token split returns is parsable. Of course, if there's no such guarantee, you'll need to do some error handling, or clean the input before trying to parse.

Upvotes: 6

Related Questions