Clojure Cookbook: Strings

Recipes related to strings.

Accessing substrings

Problem

How do I access or modify just a portion of a string, not the whole thing?

Solution

Strings in Clojure are Java String objects:

(class "Clojure") => java.lang.String

We can extract a portion of a string by means of the substring() instance method. We can specify start and end bounding indices. This returns a copy of the string starting at the first index up to the character before the ending index:

(.substring "Is this not pung?" 3 7) => "this"

Or we can get all of the string from a given point onward:

(.substring "Is this not pung?" 12) => "pung?"

However, Clojure provides the function subs as a wrapper, so we can just as easily do this:

(subs "Is this not pung?" 3 7) => "this"
(subs "Is this not pung?" 12) => "pung?"

If we wish to overwrite part of a string, we have to bear in mind that strings in Clojure (as in Java) are immutable. The answer is to create a new string with the old substring replaced by a new substring:

(defn string-splice
  ([target new offset] (string-splice target new offset (count new)))
  ([target new offset length]
     (str (subs target 0 offset) new (subs target (+ offset length)))) )

Given three arguments, string-splice will replace a portion of the old string at the given offset equal to the length of the replacement. The resulting string will be the same length as the original:

(string-splice "Is this not pung?" "foo" 12) => "Is this not foog?"

We can also splice a smaller or larger value into the original string. The optional fourth argument specifies the length of text to be replaced. If this argument length is greater than the length of the new string, then the result will be shorter than the original string. In other words, a smaller string replaces a larger substring. The opposite is true when length is less than the size of new, namely the string grows:

(string-splice "Is this not pung?" "foo" 12 4) => "Is this not foo?"
(string-splice "Is this not pung?" "yet more" 8 3) => "Is this yet more pung?"

Aligning Strings

Problem

How do I align a string within a field of a given width? I want to center the string or justify it left or right.

Solution

Let's first define a utility function that creates a whitespace string of a given width:

(defn make-space [n]
  (apply str (repeat n \space)))

The trickiest function is center since we must compute how much space to place on either side of the given string:

(defn center [s width]
  (let [len (count s)
        right (quot (- width len) 2)
        left (- width (+ len right))]
    (str (make-space left) s (make-space right))))

Left justification and right justification are simpler:

(defn ljust [s width]
  (str s (make-space (- width (count s)))) )
(defn rjust [s width]
  (str (make-space (- width (count s))) s))

Everything works the way we expect:

(center "I'm gonna break my rusty cage and run." 50) => "      I'm gonna break my rusty cage and run.      "
(ljust "I'm gonna break my rusty cage and run." 50)  => "I'm gonna break my rusty cage and run.            "
(rjust "I'm gonna break my rusty cage and run." 50)  => "            I'm gonna break my rusty cage and run."

However, we have another option in our bag of tricks. Tom Faulhaber's cl-format makes things very simple:

(use '[clojure.contrib.pprint :only (cl-format)])
(defn center [s width]
  (cl-format nil "~V<~;~A~;~>" width s))

(defn ljust [s width]
  (cl-format nil "~V<~A~;~>" width s))

(defn rjust [s width]
  (cl-format nil "~V<~A~>" width s))

For more discussion of cl-format, see the section on commify-seq in the Sequences category of the cookbook.

Or for full details, take a look at the Common Lisp HyperSpec:
http://www.lispworks.com/documentation/HyperSpec/Body/22_c.htm

Controlling Case

Problem

How do I manage the case of a string? I want to change the case or compare strings.

Solution

To change an entire string to upper case or lower case:
(.toUpperCase "double rainbow") => "DOUBLE RAINBOW"
(.toLowerCase "Thibault Et L'Arbre D'Or") => "thibault et l'arbre d'or"

To capitalize only the first letter of a string:

(defn capitalize [s]
  (if (> (count s) 0)
    (str (Character/toUpperCase (.charAt s 0))
         (.toLowerCase (subs s 1)))
    s))

This also lowercases all of the other letters:
(capitalize "") => ""
(capitalize "x") => "X"
(capitalize "clojure") => "Clojure"
(capitalize "LISP") => "Lisp"

To capitalize every word in a string:

(defn capitalize-all [s]
  (let [matcher (re-matcher #"(\w+)" s)
        buffer (new StringBuffer)]
    (while (.find matcher)
      (.appendReplacement matcher buffer (capitalize (.group matcher 1))))
    (.appendTail matcher buffer)
    (.toString buffer)))

(capitalize-all "is this not pung?") => "Is This Not Pung?"
(capitalize-all "this isn't what i expected.") => "This Isn'T What I Expected."

To compare two strings for equality:
(.equals "pung" "foo") => false
(.equals "pung" "pung") => true
(.equals "pung" "Pung") => false

To compare two strings regardless of case:
(.equalsIgnoreCase "pung" "Pung") => true

Clojure also provides the useful compare function. We can use it to not simply check whether two strings are equal but also which comes before the other in lexicographical order:
(compare "pung" "pung") => 0
(compare "pung" "foo") => 10
(compare "foo" "pung") => -10
(compare "pung" "Pung") => 32

Beware "108" is less than "23":
(compare "108" "23") => -1

Back to Clojure Cookbook: Table of Contents


Comments

Add a New Comment
or Sign in as Wikidot user
(will not be published)
- +

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License