every-nth

1,027 views
Skip to first unread message

Sunil S Nandihalli

unread,
Nov 24, 2010, 11:06:43 PM11/24/10
to clo...@googlegroups.com
Hello everybody,
 I just needed a function for every-nth element in a sequence.. I know it can be trivially implemented as ..

(defn every-nth [n coll]
  (letfn [(evn [cn s]
            (when s
              (if (= cn 1)
                (lazy-seq (cons (first s) (evn n (next s))))
                (evn (dec cn) (next s)))))]
    (evn n coll)))

But I remember seeing inbuilt function .. can anybody help me find it?
Sunil.

Baishampayan Ghose

unread,
Nov 24, 2010, 11:11:46 PM11/24/10
to clo...@googlegroups.com

take-nth should do the job -
http://clojure.github.com/clojure/clojure.core-api.html#clojure.core/take-nth

Regards,
BG

--
Baishampayan Ghose
b.ghose at gmail.com

Ken Wesson

unread,
Nov 24, 2010, 11:45:45 PM11/24/10
to clo...@googlegroups.com

And it can be even more trivially implemented as (map first (partition
n coll)). :)

David Sletten

unread,
Nov 24, 2010, 11:53:35 PM11/24/10
to clo...@googlegroups.com

(map first (partition 1 n coll))


> --
> You received this message because you are subscribed to the Google
> Groups "Clojure" group.
> To post to this group, send email to clo...@googlegroups.com
> Note that posts from new members are moderated - please be patient with your first post.
> To unsubscribe from this group, send email to
> clojure+u...@googlegroups.com
> For more options, visit this group at
> http://groups.google.com/group/clojure?hl=en

Ken Wesson

unread,
Nov 24, 2010, 11:56:01 PM11/24/10
to clo...@googlegroups.com
On Wed, Nov 24, 2010 at 11:53 PM, David Sletten <da...@bosatsu.net> wrote:
>
> On Nov 24, 2010, at 11:45 PM, Ken Wesson wrote:
>
>> On Wed, Nov 24, 2010 at 11:11 PM, Baishampayan Ghose <b.g...@gmail.com> wrote:
>>>>  I just needed a function for every-nth element in a sequence.. I know it
>>>> can be trivially implemented as ..
>>>> (defn every-nth [n coll]
>>>>   (letfn [(evn [cn s]
>>>>             (when s
>>>>               (if (= cn 1)
>>>>                 (lazy-seq (cons (first s) (evn n (next s))))
>>>>                 (evn (dec cn) (next s)))))]
>>>>     (evn n coll)))
>>>> But I remember seeing inbuilt function .. can anybody help me find it?
>>>
>>> take-nth should do the job -
>>> http://clojure.github.com/clojure/clojure.core-api.html#clojure.core/take-nth
>>
>> And it can be even more trivially implemented as (map first (partition
>> n coll)). :)
>
> (map first (partition 1 n coll))

Yes, that seems to work too, but it's also two characters longer. :)

David Sletten

unread,
Nov 24, 2010, 11:56:32 PM11/24/10
to clo...@googlegroups.com
I stand corrected. I thought it required another arg. Yours was right already.

Ken Wesson

unread,
Nov 25, 2010, 12:56:22 AM11/25/10
to clo...@googlegroups.com
Eh. This is weird. It seems that (partition n coll) drops the last
part of coll if it's not a multiple of n in size; e.g. (partition 3 [1
2 3 4 5]) yields ((1 2 3)) and not ((1 2 3) (4 5)). (partition n n []
coll) does do that though.

OTOH,

(mapcat identity (partition 1 n coll))
(apply concat (partition 1 n coll))
(keep-indexed (fn [i x] (if (= 0 (rem i n)) x)) coll)
(map first (partition-all n coll))
(mapcat identity (partition-all n coll))
(apply concat (partition-all n coll))

and the winner is:

(flatten (partition 1 n coll))

Only 30 characters. :)

Sunil S Nandihalli

unread,
Nov 25, 2010, 1:07:46 AM11/25/10
to clo...@googlegroups.com
Thanks everybody,
 Clojure community is awesome .. and also I just wanted to learn to use lazy-seq that is why it was written in the way I showed you all.

Thanks again.
Sunil.

Stuart Campbell

unread,
Nov 25, 2010, 1:26:04 AM11/25/10
to clo...@googlegroups.com
On 25 November 2010 16:56, Ken Wesson <kwes...@gmail.com> wrote:
Eh. This is weird. It seems that (partition n coll) drops the last
part of coll if it's not a multiple of n in size; e.g. (partition 3 [1
2 3 4 5]) yields ((1 2 3)) and not ((1 2 3) (4 5)). (partition n n []
coll) does do that though.

See also partition-all:

 user=> (partition-all 3 [1 2 3 4 5])
((1 2 3) (4 5))

Regards,
Stuart

Meikel Brandmeyer

unread,
Nov 25, 2010, 2:30:41 AM11/25/10
to Clojure
Hi,

On 25 Nov., 05:06, Sunil S Nandihalli <sunil.nandiha...@gmail.com>
wrote:

> (defn every-nth [n coll]
>   (letfn [(evn [cn s]
>             (when s
>               (if (= cn 1)
>                 (lazy-seq (cons (first s) (evn n (next s))))
>                 (evn (dec cn) (next s)))))]
>     (evn n coll)))

Since you want to learn how lazy-seq works here some feedback.

* Make the lazy-seq the outer-most part of your function
to allow as much laziness as possible. There other
opinions out there about the placement of lazy-seq, but
there are also a lot of people complaining about eg. a
filter doing an expensive predicate call because the
input sequence is not as lazy as it could be. By placing
lazy-seq outermost you leave the decision to them when
to realize an element.

* You should call seq on coll before passing it to evn.
Think of [] as input. It will be truthy in the when check
and we do some unnecessary loop, where we actually could
short circuit.

* Don't call next in the true branch of the if. It realises
an element of the input sequence where it is not
necessary. Use rest.

* Don't call evn in the false branch of the if. It will be
true recursion and might blow the stack for large n. You
Use recur.

Here is the version I would write in this case.

(defn every-nth
[n coll]
(let [step (fn step [s]
(lazy-seq
(loop [s (seq s)
cnt n]
(when s
(if (= cnt 1)
(cons (first s) (step (rest s)))
(recur (next s) (dec cnt)))))))]
(step coll)))

* lazy-seq is outer-most.
* The recur is turned into a loop to avoid stacking lazy-seq
on lazy-seq.
* The input sequence is only realised inside the lazy-seq.
Also in the true branch we use rest to defer realisation
of the next seq step. In the false branch we use next,
because we need the value anyway.

As a rule of thumb: write your generating function with
"normal" recursion first. Then simply wrap a lazy-seq
around it.

Hope that helps.

Sincerely
Meikel

Sunil S Nandihalli

unread,
Nov 25, 2010, 3:40:51 AM11/25/10
to clo...@googlegroups.com
Thanks Meikel for such an insightfull feedback. Its hard to imagine so much thought goes into writing trivial looking functions. 
Sunil.

Meikel Brandmeyer

unread,
Nov 25, 2010, 4:06:36 AM11/25/10
to Clojure
Hi,

On 25 Nov., 09:40, Sunil S Nandihalli <sunil.nandiha...@gmail.com>
wrote:

> Thanks Meikel for such an insightful feedback. Its hard to imagine so much
> thought goes into writing trivial looking functions.

There are several interpretations of the word "trivial." One is "easy"
or "simple" (where one could dispute whether "simple" is always
"easy"). Another one is the "mathematical" trivial. When a
mathematician says, that something "is trivial", this does not mean,
that this something is "easy". But simply well understood. The
understanding itself is maybe a PhD, but was already done by someone.

Here it is basically the same: this issues re-occur every time you
power-up lazy-seq. Then you have to understand the implications once.
From this you derive some "rules" which let you operate in auto-pilot
mode when working on a similar problems in the future.

The catalogue of rules you end up with is then called "best
practices." Best practices are frowned upon. But this is stupid. It
means one repeats the same mistakes again and again, which others did
before.

So - as a beginner - follow best practises. If you deviate, give a
good reason for the deviation. But understand the practises as you
dive in further into the language. Why are they the way they are? Are
they maybe obsolete? &c.

The art is to turn off the autopilot, before crashing into the
cliff. :)

Oh! And no one claimed that things are "easy". ;)

Sincerely
Meikel

PS: This is also true for other fields. Take for example Joseki in Go
(the game, not the prog. language). Joseki are sequences of moves
developed by professional players over years. Deviating from such a
sequence is likely to be punished in some way. So you should know what
you are doing. Nevertheless Joseki are always refined, obsoleted,
modified as time advances and play styles change.

Best practises and Joseki are tools. Use them well, and they will
serve you. Use them blindly, and they will cut off your foot.

Reply all
Reply to author
Forward
0 new messages