Monday 28 May 2012

Drop every nth item revised

A friendly Clojurian has pointed out that I gave up too soon on my first attempt to create a function that drops every nth element from a sequence.

If you start with a sequence:-

user=> (def s (range 1 20))
#'user/s
user=> s
(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19)


You can break this into sections where all except the last one have n elements and the final one has up to n elements depending on how many are left: this is the function partition-all:-

user=> (partition-all 4 s)
((1 2 3 4) (5 6 7 8) (9 10 11 12) (13 14 15 16) (17 18 19))
user=> (partition-all 5 s)
((1 2 3 4 5) (6 7 8 9 10) (11 12 13 14 15) (16 17 18 19))
user=> (partition-all 6 s)
((1 2 3 4 5 6) (7 8 9 10 11 12) (13 14 15 16 17 18) (19))


This is different from function partition, which would silently omit the final elements if there were not enough to make another batch of n.

So now I can take only n-1 elements from each batch, there's a function drop-last which does this:

user=> (map drop-last (partition-all 6 s))
((1 2 3 4 5) (7 8 9 10 11) (13 14 15 16 17) (19))


So the final element from each batch of 6 goes.  Then flatten the batches to make a single sequence:-

user=> (flatten (map drop-last (partition-all 6 s)))
(1 2 3 4 5 7 8 9 10 11 13 14 15 16 17 19)


There we are, each 6th element dropped.  Far more functional than the solution that works through the list.

Therefore the function to drop every nth item from a sequence looks like this:

(defn drop-nth [n s]
  (flatten (map drop-last (partition-all n s))))


Isn't that honey?
 

3 comments:

  1. Thank you for nice function. However as it is, it drops also the last item of non-complete batch.

    To fix this I have used custom version of drop-last:

    (defn drop-last-limited [minLength s]
    (if (>= (count s) minLength) (drop-last s) s)
    )


    The drop-nth function then becomes this:

    (comment "note partial binding of new version of drop-last")

    (defn drop-nth [n s]
    (flatten
    (map (partial drop-last-limited n)
    (partition-all n s)
    )
    )
    )








    (defn drop-nth [n s]
    (flatten
    (map (partial drop-last-limited n)
    (partition-all n s)
    )
    )
    )

    ReplyDelete
  2. This was my cheap answer to the "drop the last element of an incomplete batch" problem:

    (defn dropn [coll p] (flatten
    (map drop-last
    (partition p p [nil] coll))))

    ReplyDelete
  3. partition-all has a stepping as an optional argument, and "flatten + map" is "mapcat":

    (defn drop-every-nth [n xs]
    (apply concat (partition-all (dec n) n xs)))

    user=> (drop-every-nth 7 (range 1 42))
    (1 2 3 4 5 6 8 9 10 11 12 13 15 16 17 18 19 20 22 23 24 25 26 27 29 30 31 32 33 34 36 37 38 39 40 41)

    ReplyDelete