Click here to Skip to main content
Click here to Skip to main content

A Circular List

By , 4 Mar 2007
 

Introduction

I needed a circular list for a moving average algorithm, and seeing that there wasn't one on Code Project, I thought it would be a useful contribution. There is this article, however I have disagreements regarding the implementation (also read the article's message comments).

Implementation

The circular list that I've implemented uses a separate mechanism for managing the circular index from the indexer used by the enumerator. The list also provides a separate mechanism for returning the total number of items in the list and the total number of populated items--in other words, items that have been loaded. Furthermore, the enumerator iterates only of the populated items, so if you have only a partially loaded list, the enumerator returns the populated items, not the entire list.

Points to note:

  • I've used generics in this class so that the issues of "boxing", converting a value type to an object and back, can be eliminated. Using a non-generic List class stores its values as type "object", which takes a performance hit when converting to and from a value type and an object type.
  • I've specifically have not derived the CircularList from List<T> because I don't want to inherit the capabilities of .NET's generic List<T> class.
  • A circular list is typically of a fixed size, otherwise how would you know when to wrap the index to the beginning of the list? This is implemented as a simple array, and is another reason not to derive from .NET's List<T> class.
  • The CircularList implements its own indexer so that we can get and set the value at the current index as the indexer is advanced.
  • I've added a couple methods to clear the list and set it to a specific value.
  • The Count method is very important--it returns the number of items actually loaded into the circular list, not the total length of the list. That's handled by the Length property.
  • The CircularList supports a separate enumeration indexer and is derived from IEnumerator so that the class can be used in a foreach statement.
  • You will note that the enumerator DOES NOT iterate infinitely, even though this is a circular list! That is very important. The "circularity" of the collection is implemented in separate methods so that the enumerator doesn't end up iterating infinitely over the list.

The code:

using System;
using System.Collections.Generic;

namespace Clifton.Collections.Generic
{
  public class CircularList<T> : IEnumerable<T>, IEnumerator<T>
  {
    protected T[] items;
    protected int idx;
    protected bool loaded;
    protected enumIdx;

    /// <summary>
    /// Constructor that initializes the list with the 
    /// required number of items.
    /// </summary>
    public CircularList(int numItems)
    {
      if (numItems <= 0)
      {
        throw new ArgumentOutOfRangeException(
            "numItems can't be negative or 0.");
      }

      items = new T[numItems];
      idx = 0;
      loaded = false;
      enumIdx = -1;
    }

    /// <summary>
    /// Gets/sets the item value at the current index.
    /// </summary>
    public T Value
    {
      get { return items[idx]; }
      set { items[idx] = value; }
    }

    /// <summary>
    /// Returns the count of the number of loaded items, up to
    /// and including the total number of items in the collection.
    /// </summary>
    public int Count
    {
      get { return loaded ? items.Length : idx; }
    }

    /// <summary>
    /// Returns the length of the items array.
    /// </summary>
    public int Length
    {
      get { return items.Length; }
    }

    /// <summary>
    /// Gets/sets the value at the specified index.
    /// </summary>
    public T this[int index]
    {
      get 
      {
        RangeCheck(index);
        return items[index]; 
      }
      set 
      {
        RangeCheck(index);
        items[index] = value;
      }
    }

    /// <summary>
    /// Advances to the next item or wraps to the first item.
    /// </summary>
    public void Next()
    {
      if (++idx == items.Length)
      {
        idx = 0;
        loaded = true;
      }
    }

    /// <summary>
    /// Clears the list, resetting the current index to the 
    /// beginning of the list and flagging the collection as unloaded.
    /// </summary>
    public void Clear()
    {
      idx = 0;
      items.Initialize();
      loaded = false;
    }

    /// <summary>
    /// Sets all items in the list to the specified value, resets
    /// the current index to the beginning of the list and flags the
    /// collection as loaded.
    /// </summary>
    public void SetAll(T val)
    {
      idx = 0;
      loaded = true;

      for (int i = 0; i < items.Length; i++)
      {
        items[i] = val;
      }
    }

    /// <summary>
    /// Internal indexer range check helper. Throws
    /// ArgumentOutOfRange exception if the index is not valid.
    /// </summary>
    protected void RangeCheck(int index)
    {
      if (index < 0)
      {
        throw new ArgumentOutOfRangeException(
           "Indexer cannot be less than 0.");
      }

      if (index >= items.Length)
      {
        throw new ArgumentOutOfRangeException(
           "Indexer cannot be greater than or equal to the number of 
            items in the collection.");
      }
    }

    // Interface implementations:

    public IEnumerator<T> GetEnumerator()
    {
      return this;
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
      return this;
    }

    public T Current
    {
      get { return this[enumIdx]; }
    }

    public void Dispose()
    {
    }

    object IEnumerator.Current
    {
      get { return this[enumIdx]; }
    }

    public bool MoveNext()
    {
      ++enumIdx;
      bool ret = enumIdx < Count;

      if (!ret)
      {
        Reset();
      }

      return ret;
    }

    public void Reset()
    {
      enumIdx = -1;
    }
  }
}

Usage

Here's an example of using the circular list. It populates 5 out 10 items, sets the first item to a different value, then iterates over the collection. The resulting display is five items: "11, 1, 2, 3, 4".

static void Test()
{
  CircularList<int> cl = new CircularList<int>(10);

  for (int i = 0; i < 5; i++)
  {
    cl.Value = i;
    cl.Next();
  }

  cl[0] = 11;

  foreach (int n in cl)
  {
    Console.WriteLine(n);
  }
}

Conclusion

I hope people find this class helpful when the need a circular list, for example when working with moving averages.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Marc Clifton
United States United States
Member
Marc is the creator of two open source projets, MyXaml, a declarative (XML) instantiation engine and the Advanced Unit Testing framework, and Interacx, a commercial n-tier RAD application suite.  Visit his website, www.marcclifton.com, where you will find many of his articles and his blog.
 
Marc lives in Philmont, NY.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionLicensing questionmemberFahadAsh9 Sep '11 - 10:06 
Am I free to use it in my code ? Can I change the namespace ?
Its never too late.

Questiontype is missing?memberRichard Savoie14 Aug '11 - 19:41 
shouldn't enumIdx be of type int ?
 
Other than that, it's exactly what I needed to keep logs of commands and responses for a XNA drawn console Wink | ;)
GeneralYou've have a Typomemberaspdotnetdev16 Aug '09 - 11:48 
I know this article is ancient, but...
 
"I've specifically have not derived the CircularList" -- You have specifically have?
GeneralRe: You've have a TypoprotectorMarc Clifton16 Aug '09 - 12:05 
aspdotnetdev wrote:
"I've specifically have not derived the CircularList" -- You have specifically h

 
I have not. It's correct as I wrote it. Smile | :)
 
Marc
 
Will work for food.
Interacx


I'm not overthinking the problem, I just felt like I needed a small, unimportant, uninteresting rant! - Martin Hart Turner


GeneralRe: You've have a Typomemberaspdotnetdev16 Aug '09 - 12:13 
Really? Compare the two sentences below. The top is yours, and the bottom is what I think the corrected version should be.
 
"I've specifically have not derived the CircularList from List because I don't want to inherit the capabilities of .NET's generic List<T> class."
"I've specifically not derived the CircularList from List because I don't want to inherit the capabilities of .NET's generic List<T> class."
 
Anyway, it's a simple typo, so I'm sure most will not even notice it. Smile | :)
Questionfor value types only?memberFederico Sasso7 Mar '07 - 3:17 
Hi Marc,
neat article, as always, thank you. I have a few minor questions:
 
Seeing the Clear() and SetAll() methods implementations I understand that the collection is meant for value types only (am I correct?), but how do you enforce it?
 
Wouldn't it be worth to loose the constraint, and change the two methods?
 
I see that the Clear() method cleans all the array content, but is it really needed? wouldn't just be enough writing:

public void Clear()
{
idx = -1;
loaded = false;
}

(please also note that I wrote idx = -1; why did you use zero?)
 
thank you in advance, I learnt a lot from your past articles.

 
Federico
AnswerRe: for value types only?mvpColin Angus Mackay7 Mar '07 - 3:36 
Federico Sasso wrote:
Seeing the Clear() and SetAll() methods implementations I understand that the collection is meant for value types only

 
I wouldn't have thought so. You can use any object type. What makes you think it is for value types only?
 

Federico Sasso wrote:
Wouldn't it be worth to loose the constraint, and change the two methods?

 
What constraint? Am I missing something, because I don't see a constraint for value types only.
 

Federico Sasso wrote:
wouldn't just be enough writing...

 
No, because that won't release the references to the objects in the array, and therefore won't release the objects to the garbage collector.
 

 

Upcoming events:
* Edinburgh: Web Security Conference Day for Windows Developers (12th April)
* Glasgow: AJAX, SQL Server, Mock Objects

 
My: Website | Blog | Photos

GeneralRe: for value types only?protectorMarc Clifton7 Mar '07 - 4:03 
Hi Colin - Thanks for the excellent answer! Smile | :)
 
Marc
 

Thyme In The Country

Interacx

People are just notoriously impossible. --DavidCrow
There's NO excuse for not commenting your code. -- John Simmons / outlaw programmer
People who say that they will refactor their code later to make it "good" don't understand refactoring, nor the art and craft of programming. -- Josh Smith


GeneralRe: for value types only? [modified]memberFederico Sasso7 Mar '07 - 11:04 
Hi Colin, thank you for your reply
 
Colin Angus Mackay wrote:
I wouldn't have thought so. You can use any object type. What makes you think it is for value types only?

 
MSDN clearly states that Array.Initialize() must not be used on reference-type arrays. I still didn't try its behaviour on them.
 

Colin Angus Mackay wrote:
No, because that won't release the references to the objects in the array, and therefore won't release the objects to the garbage collector.

 
good point if we are talking about reference types
 

-- modified at 17:09 Wednesday 7th March, 2007
GeneralRe: for value types only?mvpColin Angus Mackay7 Mar '07 - 11:53 
Federico Sasso wrote:
I still didn't try its behaviour on them.

 
I've just tried it. It is a null operation on reference types.
 
class MyObject
{
    int a = 0;
 
    public override string ToString()
    {
        return string.Format("{{ a = {0} }}", this.a);
    }
}
class Program
{
    static void Main(string[] args)
    {
        MyObject[] a = new MyObject[10];
        a[5] = new MyObject();
        a[5].a = 10;
        a.Initialize();
        for (int i = 0; i < a.Length; i++)
        {
            Console.WriteLine("{0} = {1}", i, a[i]);
        }
        Console.ReadLine();
    }
}
 
The result is that element 5 still has the reference type MyObject as inserted before the Initialise is called.
 

Upcoming events:
* Edinburgh: Web Security Conference Day for Windows Developers (12th April)
* Glasgow: AJAX, SQL Server, Mock Objects

 
My: Website | Blog | Photos

GeneralRe: for value types only?memberFederico Sasso7 Mar '07 - 13:20 
I tried it too but you've been quicker on posting Smile | :)
Being it a nop on reference types, it looks like the code has a little memory retention bug. Besides, the fix is quite simple.
Thank you
 
Federico
Generalvery useful articlememberCIDev6 Mar '07 - 8:30 
This is a very useful article. I was about to write a circular list, now I don't have to roll my own Wink | ;) .
 
Thanks,
BillW

GeneralQuickly define the circular list in the introduction.memberChristian Klauser5 Mar '07 - 9:18 
You should quickly define what a CircularList is because people might not know it with that name.
GeneralWell, this is an odd coincidence.....memberJames Curran5 Mar '07 - 6:40 
I wrote this just last week....
 
http://honestillusion.com/blogs/blog_0/archive/2007/02/28/implementing-a-circular-iterator.aspx[^]
 
Truth,
James

GeneralRe: Well, this is an odd coincidence.....protectorMarc Clifton5 Mar '07 - 7:55 
James Curran wrote:
I wrote this just last week....

 
Interesting also, because I spent a long time thinking about your comments in that other circular list article. It finally dawned on me that I needed more than an iterator, which is really only useable for read-only operations.
 
Thanks for the blog link though.
 
Marc
 

Thyme In The Country

Interacx

People are just notoriously impossible. --DavidCrow
There's NO excuse for not commenting your code. -- John Simmons / outlaw programmer
People who say that they will refactor their code later to make it "good" don't understand refactoring, nor the art and craft of programming. -- Josh Smith


GeneralenumeratiommemberLuc Pattyn4 Mar '07 - 18:19 
Hi Marc,
 
I've just read and enjoyed your articles on Moving Average and Circular List.
 
I do have an issue with the enumerator provided by your circular list:
IMO GetEnumerator() should create and return a new object (not "this")
since it must be able to keep track of an enumeration state, even when more
than one enumeration of the collection is going on, as in:
 
int total = 0;
foreach (int a in collection) {
    foreach (int b in collection) {
        total += a*b;
    }
}
 
AFAIK the ArrayList in ssCLI agrees with me:
        public virtual IEnumerator GetEnumerator() {
    		return new ArrayListEnumeratorSimple(this);
        }
 
Regards,
 

 

 
Luc Pattyn
 
[My Articles]

GeneralRe: enumeratiommemberS. Senthil Kumar4 Mar '07 - 18:35 
You're right, and the yield keyword would solve the problem cleanly.
 
IEnumerator GetEnumerator()
{
   for (int i = 0; i<items.length; ++i)
   {
      yield return items[i];
   }
}

 
Regards
Senthil [MVP - Visual C#]
_____________________________
My Blog | My Articles | My Flickr | WinMacro

GeneralRe: enumeratiomprotectorMarc Clifton5 Mar '07 - 4:13 
Luc Pattyn wrote:
should create and return a new object

 
Thanks for the feedback and your comment. You are of course correct! I'll update the code shortly.
 
Marc
 

Thyme In The Country

Interacx

People are just notoriously impossible. --DavidCrow
There's NO excuse for not commenting your code. -- John Simmons / outlaw programmer
People who say that they will refactor their code later to make it "good" don't understand refactoring, nor the art and craft of programming. -- Josh Smith


General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130516.1 | Last Updated 4 Mar 2007
Article Copyright 2007 by Marc Clifton
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid