Mark Needham

Thoughts on Software Development

Writing a Java function in Clojure

with 9 comments

A function that we had to write in Java on a project that I worked on recently needed to indicate whether there was a gap in a series of data points or not.

If there were gaps at the beginning or end of the sequence then that was fine but gaps in the middle of the sequence were not.

null, 1, 2, 3 => no gaps
1, 2, 3, null => no gaps
1, null, 2, 3 => gaps

The Java version looked a bit like this:

public boolean hasGaps(List<BigInteger> values) {
    Iterator<BigInteger> fromHead = values.iterator();
    while (fromHead.hasNext() && fromHead.next() == null) {
        fromHead.remove();
    }
 
    Collections.reverse(values);
 
    Iterator<BigInteger> fromTail = values.iterator();
    while (fromTail.hasNext() && fromTail.next() == null) {
        fromTail.remove();
    }
 
    return values.contains(null);
}

We take the initial list and then remove all the null values from the beginning of it, then reverse the list and remove all the values from the end.

We then check if there’s a null value and if there is then it would indicate there is indeed a gap in the list.

To write this function in Clojure we can start off by using the ‘drop-while‘ function to get rid of the trailing nil values.

I started off with this attempt:

(defn has-gaps? [list]
    let [no-nils] [drop-while #(= % nil) list]
  no-nils)

Unfortunately that gives us the following error!

Can't take value of a macro: #'clojure.core/let (NO_SOURCE_FILE:16)

It thinks we’re trying to pass around the ‘let’ macro instead of evaluating it – I forgot to put in the brackets around the ‘let’!

I fixed that with this next version:

(defn has-gaps? [list]
    (let [no-nils] [drop-while nil? list]
  no-nils))

But again, no love:

java.lang.IllegalArgumentException: let requires an even number of forms in binding vector (NO_SOURCE_FILE:23)

The way I understand it the ‘let’ macro takes in a vector of bindings as its first argument and what I’ve done here is pass in two vectors instead of one.

In the bindings vector we need to ensure that there are an even number of forms so that each symbol can be bound to an expression.

I fixed this by putting the two vectors defined above into another vector:

(defn has-gaps? [list]
    (let [[no-nils] [(drop-while nil? list)]]
  no-nils))

We can simplify that further so that we don’t have nested vectors:

(defn has-gaps? [list]
    (let [no-nils (drop-while nil? list)]
  no-nils))

The next step was to make ‘no-nils’ a function so that I could make use of that function when the list was reversed as well:

(defn has-gaps? [list]
    (let [no-nils (fn [x] (drop-while nil? x))]
  (no-nils list)))

I then wrote the rest of the function to reverse the list and then check the remaining list for nil:

(defn has-gaps? [list]
    (let [[no-nils] [(fn [x] (drop-while nil? x))]
          [nils-removed] [(fn [x] ((comp no-nils reverse no-nils) x))]]
  (some nil? (nils-removed list))))

The ‘comp‘ function can be used to compose a set of functions which is what I needed.

It seemed like the ‘nils-removed’ function wasn’t really necessary so I inlined that:

(defn has-gaps? [list]
    (let [no-nils (fn [x] (drop-while nil? x))]
  (some nil? ((comp no-nils reverse no-nils) list))))

The function can now be used like this:

user=> (has-gaps? '(1 2 3))
nil
user=> (has-gaps? '(nil 1 2 3))
nil
user=> (has-gaps? '(1 2 3 nil))
nil
user=> (has-gaps? '(1 2 nil 3))
true

I’d be intrigued to know if there’s a better way to do this.

Written by Mark Needham

November 23rd, 2009 at 8:08 pm

Posted in Clojure,Java

Tagged with ,

  • Pingback: Tweets that mention Writing a Java function in Clojure at Mark Needham -- Topsy.com

  • http://blog.thinkrelevance.com Stuart Halloway

    Partition by threes and “some” for a (x nil x)

  • http://blog.thinkrelevance.com Stuart Halloway

    (defn has-gaps? [s]
    (->>
    s
    (drop-while nil?)
    (drop-while #(not? (nil?)))
    (drop-while nil?)
    seq
    boolean))

  • http://blog.thinkrelevance.com Stuart Halloway

    Left the % out of the previous one (iPhone sucks as IDE)

    #(not? (nil? %))

  • http://blog.sbistram.de/ Stefan Bistram

    @Stuart
    cool solution and almost ok: not instead not?

    First I was puzzled about ->>, it’s unknown in the release clojure 1.0.0, but in snapshot clojure-1.0.0-2009-10-28 it’s working:

    user=> (->> 1 (+ 3) (+ 4))
    8

    Woudn’t it be more readable to use empty? false? instead of seq boolean. I would create a more general trim utils function like (if it’s not already in the contrib lib):

    (defn trim [s t]
    (->>
    s
    (drop-while #(= t %))
    reverse
    (drop-while #(= t %))
    reverse))

    and use it like:

    (defn has-nil-gaps? [s]
    (some nil? (trim s nil)))

  • stand

    Stuart, the partition by threes approach won’t work if there are consecutive nils inside the list, or am I missing something?

  • http://code-o-matic.blogspot.com/ Dimitris Andreou

    Using Collections.reverse() in the original java solution is bad. Using a ListIterator from the end of the list should make it at least almost twice as fast.

  • Steve

    You could make your java code a bit more lispish by using subList instead of iterator. Something like:

    public boolean hasGaps(List values) {
    if (values.size() < 3) return false;
    boolean rv = false;
    List sub = values.subList(1, values.size()-1);
    while(! rv && sub.size() > 0) {
    rv = (sub.get(0) == null);
    sub = sub.subList(1, sub.size());
    }
    return rv;
    }

    Similar in Clojure:
    (defn has-gaps [v]
    (cond (< (count v) 3) nil
    :else (some (fn[x] (nil? x)) (subvec (vec v) 1 (dec (count v))))))

  • Steve

    I see, your test cases really like:

    null*, 1, 2, 3, null* => no gaps

    where null* is any number of nulls and not just a single null at the beginning and end. Your original code makes more sense to me now.