Clojure - blend of "get" and "nth"?

Viewed 557

I am struggling to find a function that will find a happy-medium between the get function and nth. I have been doing a lot of research on these sequence type functions, does anyone know a work around for this or know a function that performs as such?

I need nth's ability to grab sublists:

=> (nth '(1 (2 3) 4 5) 1)
(2 3)
=> (get 1 '(1 (2 3) 4 5))
nil

And I need get's ability to return "nil" when out of range:

=> (get -1 '(1 (2 3) 4 5))
nil
=> (nth '(1 (2 3) 4 5) -1)
Execution error (IndexOutOfBoundsException) at user/eval149 (REPL:1).
null

I need this code for a recursive sub-seq function:

(defn sub-seq [coll i j]
  (nth coll i)
  (if (< i (+ i j))
    (sub-seq coll (inc' i) j)
  )
)

(The sub-seq function is supposed to return 'j' elements starting at position 'i'.)

Here are some sample outputs for what I am trying to write:

=> (sub-seq '(1 2 (3 4) (5 (6 7))) 1 2))
(2 (3 4))
=> (sub-seq '(1 2 3 4 5 6 7) 2 4)
(3 4 5 6)

I finally got my function to work, thank you all for your help:

(defn sub-seq [coll i j]
  (conj
    (list*
      (nth coll i nil)
      (if (> j 1)
        (sub-seq coll (+ i 1) (- j 1))))))
2 Answers

nth takes an optional third argument not-found. You can use it to provide a default value if your index is out of bounds:

user=> (nth '(1 (2 3) 4 5) -1)
Execution error (IndexOutOfBoundsException) at user/eval1 (REPL:1).
null
user=> (nth '(1 (2 3) 4 5) -1 nil)
nil

If you had a vector, you could use subvec directly:

(let [s [1 2 3 4 5 6 7]]
  (subvec s 2 6))

If you have a sequence then you could write:

(defn subsequence [coll start n]
  (->> coll
       (drop start)
       (take n)))

(subsequence '(1 2 (3 4) (5 (6 7))) 1 2)
=> (2 (3 4))
(subsequence '(1 2 3 4 5 6 7) 2 4)
=> (3 4 5 6)

Side note: When writing a Clojure program, often you can solve your problem more simply with sequence processing. Sometimes recursive algorithms are necessary, but often you can get by with Clojure's rich set of functions that operate on sequences.

You are misunderstanding get. It works on associative collections like maps and vectors. Consider:

(ns tst.demo.core
  (:use tupelo.core tupelo.test))

(dotest
  (let [data-list '(1 (2 3) 4 5)
        data-vec (vec data-list) ]
    (spyx (nth data-list 1))
    (spyx (nth data-vec 1))
    (spyx (get data-list 1))
    (spyx (get data-vec 1))
    ))

with result

(nth data-list 1)  => (2 3)
(nth data-vec 1)   => (2 3)
(get data-list 1)  => nil
(get data-vec 1)   => (2 3)

Since a Clojure list is not associative like a map, list should not be used at all with lists. Ideally, get would throw an exception when passed a list argument to indicate it doesn't work with them.


For your specific goal, maybe just do something like:

(take j
  (drop i coll))

For additional documentation please review here.

Related