myermian
myermian

Reputation: 32505

Is there a better way to return the next item in a list and loop from the end to the front?

I have the following list of distinct strings:

"A"
"B"
"C"

If I want the item after A, I get B. After B, I get C. After C, I get A. Currently I have the following code, but for some reason it feels to me that there is a better way to go about this (maybe?).

private string GetNext(IList<string> items, string curr)
{
    if (String.IsNullOrWhitespace(curr))
        return items[0];

    var index = items.IndexOf(curr);
    if (index == -1)
        return items[0];

    return (index + 1 == items.Count) ? items[0] : items[index + 1];
}

I'm definitely open to a LINQ-esque way of doing this as well :)

Upvotes: 8

Views: 5102

Answers (7)

JaredPar
JaredPar

Reputation: 754515

The solution you have is functionally correct but it's performance leaves a little to be desired. Typically when dealing with a list style structure you would expect that GetNext would return a result in O(1) time yet this solution is O(N).

public sealed class WrappingIterator<T> {
  private IList<T> _list;
  private int _index;
  public WrappingIterator<T>(IList<T> list, int index) {
    _list = list;
    _index = index;
  }
  public T GetNext() {
    _index++;
    if (_index == _list.Count) {
      _index = 0;
    }
    return _list[_index];
  }

  public static WrappingIterator<T> CreateAt(IList<T> list, T value) {
    var index = list.IndexOf(value);
    return new WrappingIterator(list, index);
  }
}

The initial call to CreateAt is O(N) here but subsequent calls to GetNext are O(1).

IList<string> list = ...;
var iterator = WrappingIterator<string>.CreateAt(list, "B");
Console.WriteLine(iterator.GetNext());  // Prints C
Console.WriteLine(iterator.GetNext());  // Prints A
Console.WriteLine(iterator.GetNext());  // Prints B

Upvotes: 7

Andres
Andres

Reputation: 3414

I think maybe you can change the line

return (index + 1 == items.Count) ? items[0] : items[index + 1];

for something like

return items[(index + 1) % items.Count];

Upvotes: 6

Joe
Joe

Reputation: 82554

A linq way:

var result = (from str in list
              let index = list.IndexOf(curr) + 1
              select list.ElementAtOrDefault(index) ?? list[0]).First();

Upvotes: 1

Ravi Vanapalli
Ravi Vanapalli

Reputation: 9942

I think the best solution is in the below link, I tried it out and works like charm.

http://www.dotnetperls.com/sort-list

Upvotes: 0

Tim Schmelter
Tim Schmelter

Reputation: 460018

LINQ is not the appropriate tool here.

It sounds as if a LinkedList<T> would be the better collection here:

var linkedItems = new LinkedList<String>(items);
LinkedListNode current = linkedItems.Find("C");
String afterC = current.Next == null ? linkedItems.First.Value : current.Next.Value;

Here are the pros and cons of a LinkedList compared to a List.

Upvotes: 1

MCattle
MCattle

Reputation: 3157

I can see some optimization if you track the current index rather than the current string, but to do that the list of items would have to be fixed, i.e. not change.

You could also return items[(index + 1) % items.Count];

Otherwise that code looks fine to me, but perhaps someone has a more clever solution.

Upvotes: 1

Jan
Jan

Reputation: 16032

You can use the mod operator to simplify this a bit and combine everything into one statement:

return items[((String.IsNullOrWhitespace(curr) 
                ? 0 
                : items.IndexOf(curr)) + 1) % items.Count]

Its definitely shorter, but i'm not sure weather its more readable, too :)

Upvotes: 0

Related Questions