views:

136

answers:

7

Hi to everyone, I was wondering about which is the best (clojuresque) way to compare a character and a string in Clojure. Obviously something like that returns false:

(= (first "clojure") "c")

because first returns a java.lang.Character and "c" is a single character string. Does exists a construct to compare directly char and string without invoking a cast? I haven't found a way different from this:

(= (str (first "clojure")) "c")

but I'm not satisfied. Any ideas? Bye, Alfredo

A: 

You can just use str, as you did in your second example. There isn't really anything wrong with that. I mean, you could call first on "c" as well to make it a character, but it wont really make a difference. Is there any reason why you don't like this? It's not really adding much to your code by calling str on the character.

Rayne
There isn't a particular reason, I just wondering how I can manage the comparison of Character and String in a way that doesn't fill my code with cast :)
Alfredo Di Napoli
+3  A: 

Character literals are written \a \b \c ... in Clojure so you can simply write

(= (first "clojure") \c)
Jonas
Ok, but this only move the problem from the string to the character.. I mean, if I need to take in input a single character string, in order to compare such string I need to cast in char :) However it's a good tip, I didn't know that I can use \c to express char. Thanks ^^
Alfredo Di Napoli
See also the [reader documentation](http://clojure.org/reader).
kotarak
+1  A: 

You could use the take function from clojure.contrib.string. Or write your own function that returns the first char if that's something you need frequently.

Matti Pastell
+8  A: 

How about the straight forward String interop?

(= (.charAt "clojure" 0) \c)

or

(.startsWith "clojure" "c")

It should be as fast as it can get and doesn't allocate a seq object (and in your second example an additional string) which is immediately thrown away again just to do a comparison.

kotarak
What I like about Clojure is that it doesn't hide Java when it doesn't need to, so +1.
ponzao
+1  A: 

strings can be directly indexed without building a sequence from then and taking the first of that sequence.

(= (nth "clojure" 0) \c) 
=> true

nth calls through to this java code:

static public Object nth(Object coll, int n){
    if(coll instanceof Indexed)
        return ((Indexed) coll).nth(n);    <-------
    return nthFrom(Util.ret1(coll, coll = null), n);
}

which efficiently reads the character directly.

first call through to this java code:

static public Object first(Object x){
    if(x instanceof ISeq)
        return ((ISeq) x).first();
    ISeq seq = seq(x);    <----- (1)
    if(seq == null)
        return null;
    return seq.first();   <------ (2)
}

which builds a seq for the string (1) (building a seq is really fast) and then takes the first item from that seq (2). after the return the seq is garbage.

Seqs are clearly the most idomatic way of accessing anything sequential in clojure and I'm not knocking them at all. It is interesting to be aware of what you are creating when. switching out all your calls to first with calls to nth is likely to be a case of premature optimization. if you want the 100th char in the string i would suggest using an indexed access function like nth

in short: don't sweat the small stuff :)

Arthur Ulfeldt
+1  A: 

Fundamentally (at least on the Clojure level — though see Kotarak's answer and others for alternatives to this), you're comparing two sequences: "clojure" and "c". The condition of equality is that the first element of each sequence is equal. So if you want to express this directly you can do

(apply = (map first ["clojure" "c"]))

or the other way around, where you create a lazy sequence over the equality comparison between each pair of characters, and just take the first element of it:

(first (map = "clojure" "c"))
intuited
You can use `every?` to extend this to arbitrary prefixes (read: similar to .startsWith): `(every? identity (map = "clojure" "clo"))`.
kotarak
A: 
user=> (= (subs "clojure" 0 1) "c")
true
user=> (= (str (first "clojure") "c"))
true
lazy1