3

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?

Community
  • 1
  • 1
RPM1984
  • 72,246
  • 58
  • 225
  • 350

5 Answers5

5

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

TheEvilPenguin
  • 5,634
  • 1
  • 26
  • 47
  • I like this solution, because a) it works, b) it's fairly efficient, and c) i hid it behind an extension method called `ShuffleDownToNextPreference` so i don't have to worry about it, but when i read the code that uses it, i know what it's doing. Accepted. – RPM1984 Dec 21 '11 at 01:13
  • @RPM1984 and @TheEvilPenguin: the only shortcoming I see with this approach is that it will always set `IsPreferred` to false for the first item whether or not the next item exists. To avoid this see my solution. – Ahmad Mageed Dec 21 '11 at 01:18
  • @AhmadMageed - yes, that's true. An edge case, but true nonetheless. Sorry Pengiun - Ahmad has stolen your accepted answer. :) +1 to make up for it. – RPM1984 Dec 21 '11 at 01:34
  • Thanks :) I fixed the edge case anyway as it would annoy me otherwise – TheEvilPenguin Dec 21 '11 at 01:52
4

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;
    }
}
Ahmad Mageed
  • 94,561
  • 19
  • 163
  • 174
3

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;
Marlon
  • 19,924
  • 12
  • 70
  • 101
  • 2
    My code would probably be more efficient if you have a lot of items, but with modern PCs the best solutions is usually the most readable one. If this one will make more sense when you look at the code again in 6 months, it's the better solution. – TheEvilPenguin Dec 21 '11 at 01:10
  • 1
    @Marlon nice LINQ approach, but the use of `Skip` and `First` will throw an exception if the next element doesn't exist. A better approach would be to use: `SomeClass b = x.ElementAtOrDefault(1);` then, if the OP wanted to change the values *only* if the next item existed, place the last 2 assignment lines inside of this condition: `if (b != null) { ... }`. Granted, you mentioned the absence of error checking :) – Ahmad Mageed Dec 21 '11 at 01:25
0

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.
satyajit
  • 1,470
  • 3
  • 22
  • 43
0

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;
Sean U
  • 6,730
  • 1
  • 24
  • 43
  • 2
    `IEnumerable` and `ICollection` don't have indexers - how would that work? – RPM1984 Dec 21 '11 at 01:03
  • Ah ya got me; I always get ICollection confused with IList. – Sean U Dec 21 '11 at 01:05
  • 1
    `ICollection` doesn't have an indexer either - try your code, it wont compile. – RPM1984 Dec 21 '11 at 01:05
  • That said, ICollection's semantics imply that there is no set ordering to its elements. If you are relying on there being one, shouldn't you be using IList anyway? (I'll edit my comment too.) – Sean U Dec 21 '11 at 01:09
  • yeah that's true. I generally only work with `ICollection` for sequences, and `IEnumerable` for queries. I still think you can have ordering with `ICollection`, otherwise they wouldn't have exposed `OrderBy` and the like (inherited from `IEnumerable`). But i take your point. – RPM1984 Dec 21 '11 at 01:15
  • You can impose an ordering with OrderBy, but that doesn't imply that the collection imposes any sort of canonical ordering. e.g., HashSet is an ICollection, but it is not an IList. – Sean U Dec 21 '11 at 01:20