Question

I've got an ICollection<SomeClass>.

public class SomeClass
{
   public string Text { get; set; }
   public bool IsPreferred { get; set; }
}

The items in there have been pre-ordered, so "next" does mean something.

In my scenario, the contents of the sequence looks like this:

[0] - "a", false

[1] - "b", true

[2] - "c", false

I'm trying to get the "next" element after the one that IsPreferred == true. So in the above, i want to get element 2, and i want to clear the other IsPreferred value.

So i want to end up with this:

[0] - "a", false

[1] - "b", false

[2] - "c", true

Basically shuffling the preferred item down.

What's the best way to do it? The only thing i can think of is to create a new array, add them one by one, keep track of the index of the one that is preferred, then go grab the element at the aforementioned index +1.

Any better ideas?

Was it helpful?

Solution

Since ICollection<T> doesn't give you an indexer I would opt for a more direct solution rather than rely on LINQ.

Assuming you want to (1) stop as soon as the condition is met, and (2) only change the value if the next item exists, this can be achieved as follows:

bool isFound = false;
SomeClass targetItem = null;
foreach (var item in list)
{
    if (isFound)
    {
        item.IsPreferred = true;
        targetItem.IsPreferred = false;
        break;
    }
    if (item.IsPreferred)
    {
        targetItem = item;
        isFound = true;
    }
}

OTHER TIPS

I would use an enumerator to iterate over the collection - this is what foreach does behind the scenes:

var enumerator = collection.GetEnumerator();

while (enumerator.MoveNext())
{
    if (enumerator.Current.IsPreferred)
    {
        var oldPreferred = enumerator.Current;

        if (enumerator.MoveNext())
        {
            oldPreferred.IsPreferred = false;
            enumerator.Current.IsPreferred = true;
        }

        break;
    }
}

This does assume that you want to stop after finding the first item with IsPreferred, and if this is the last item, still clear IsPreferred.

Edit: Fixed edge case where IsPreferred is always set to false on a collection of a single item

I can only think of a messy way doing this with LINQ:

var x = collection.SkipWhile(z => !z.IsPreferred);
SomeClass a = x.First();
SomeClass b = x.Skip(1).First();

a.IsPreferred = false;
b.IsPreferred = true;

This of course excludes error checking and is not very efficient.


Another possibility (using LINQ) would be to use Ahmad Mageed's solution (as suggested in the comments below):

var x = collection.SkipWhile(z => !z.IsPreferred);
SomeClass a = x.FirstOrDefault();
SomeClass b = x.ElementAtOrDefault(1);

if (a != null) a.IsPreferred = false;
if (b != null) b.IsPreferred = true;

Couple of ideas

  1. If you can iterate over the collection why can't we set i+2 value to true before processing i+1? Be sure to make sure that i+2 exists
  2. Another idea is extending the LinkedList and make the current.next.next = true if it exists.

Since your collection is ordered, can it be an IList instead of an ICollection?

Then you could create an extension method to give you the indexes of the values where some predicate applies:

static IEnumerable<int> IndexWhere<T>(this IList<T> list, 
                                      Func<T, bool> predicate)
{
    for(int i = 0; i < list.Count; i++)
    {
        if(predicate(list[i])) yield return i;
    }
}

Assuming you expect only one element to ever match, it sounds like the rest would just look like this:

var preferredIndex = list.IndexWhere(x=>x.IsPreferred).Single();
list[preferredIndex].IsPreferred = false;
list[preferredIndex + 1].IsPreferred = true;
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top