2

I need an interactive function that works like a generator, whenever it is called it should return "next" or "previous" value from a list. Whenever it reaches the end of the list, it should start over from the beginning (and vice versa when going backwards).

It looks like elisp offers (at least) a couple of ways to iterate forward (generators, cl-loop), but how do I cycle back?

I need something like:

(defvar my-list '(:a :b :c))

(defun cycle-through (previous)
  (interactive)
  (if previous
    ;; get previous --
    ;; get next))

(cycle-through)    => :a
(cycle-through)    => :b
(cycle-through t)  => :a
(cycle-through)    => :b
(cycle-through)    => :c
(cycle-through)    => :a

What's the best and elegant way of achieving something like that?

Mind that the list may have duplicate values, trying to store the current value and determine the current position based on that not desirable.

1

1 Answer 1

2

There are many ways to do it, as you guessed. One simple way is to use a ring, which is a list used cyclicly. Standard library ring.el defines what you need to create and use rings.

This is from the Commentary at the beginning of ring.el:

This code defines a ring data structure. A ring is a (hd-index length . vector) list.

You can insert to, remove from, and rotate a ring. When the ring fills up, insertions cause the oldest elts to be quietly dropped.

In ring-ref, 0 is the index of the newest element. Higher indexes correspond to older elements. When the index equals the ring length, it wraps to the newest element again.

  • hd-index = vector index of the oldest ring item.

    Newer items follow this item. At the end of the vector, they wrap around to the start of the vector.

  • length = number of items currently in the ring.

    This never exceeds the length of the vector itself.

These functions are used by the input history mechanism, but they can be used for other purposes as well.

You can convert an existing sequence (list, vector, etc.) to a ring using function ring-convert-sequence-to-ring.

You can create an empty new ring with function make-ring.

You can easily guess what these other functions do:

  • ring-insert
  • ring-extend
  • ring-insert+extend
  • ring-remove
  • ring-member
  • ring-elements
  • ring-next
  • ring-previous
  • ring-ref
  • ring-size
  • ring-copy
1
  • 1
    Thank you. I just tried it and it is indeed, very nice. I had no idea this existed.
    – iLemming
    Commented Nov 1, 2021 at 5:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.