tags:

views:

257

answers:

4

Given a list of elements like so:

int[] ia = new int[] { -4, 10, 11, 12, 13, -1, 9, 8, 7, 6, 5, 4, -2, 
                        6, 15, 32, -5, 6, 19, 22 };

Is there an easy way in Linq to do something along the lines of "Select the elements from the -1 up to the next negative number (or the list exhausts)"? A successful result for -1 would be (-1, 9, 8, 7, 6, 5, 4). Using -2 would give the result (-2, 6, 15, 32).

Not a homework problem. I'm just looking at an implementation using a bool, a for loop, and an if wondering if there's a cleaner way to do it.

+4  A: 

Take a look at the TakeWhile Linq extension method. Takes items from the list as long as the condition is true, skips the rest.

Example:

int[] ia = new int[] { -4, 10, 11, 12, 13, -1, 9, 8, 7, 6, 5, 4, -2, 
                        6, 15, 32, -5, 6, 19, 22 };

var result = ia
             .SkipWhile(i => i != -1)
             .Skip(1)
             .TakeWhile(i => i >= 0);

Note the Skip(1) after the SkipWhile. SkipWhile skips everything up to, but not including the matching element. TakeWhile then Takes items, up to but not including the matching element. Because -1 is not greater than or equal to zero, you get an empty result.

Erik van Brakel
I tried `SkipWhile`/`TakeWhile` and made the same mistake as Anders. It didn't dawn on me to skip over the one. Going to leave this open a bit longer to see what variety falls out.
clintp
That solution will not include the first `-1` in the result set.
Anders Abel
It doesn't, true. But since the -1 was the indicator for the start of the set -- it's the one, known value -- it can be inserted into the results.
clintp
I like this answer better because it is more understandable. This can give the correct complete result by changing .Skip(1) to .Take(1).
DRBlaise
@clintp and @Erik - if the -1 is omitted from the result, how do you know it was there to start with (unless you perform another scan)? In this method { -1, -2 } => {} but also { 3, 5, 7} => {} ...
Robert Paulson
@DRBlaise, you cannot obtain the -1 in the results by replacing Skip(1) with Take(1) as that always yields {}. See my answer for a working version.
Robert Paulson
+5  A: 
Anders Abel
Thought of that and this gives an empty result.
clintp
You're right that TakeWhile(i => i >= 0) gives an empty result. See my updated (and tested) effort above.
Anders Abel
This does give the correct results now.
clintp
A: 

Does it have to be Linq? You can use Extensions methods to get a cleaner solution.

int[] ia = new int[] { -4, 10, 11, 12, 13, -1, 9, 8, 7, 6, 5, 4, -2, 
                        6, 15, 32, -5, 6, 19, 22 };

// Call the Extension method
int[] results = ia.SelectRangeLoop(-2);

// Print Results
for (int i = 0; i < results.Length; i++)
{
    Console.Write(" {0} ", results[i]);
}

The method for SelectRangeLoop is below.

public static int[] SelectRangeLoop(this int[] value, int startNumber)
{
    List<int> results = new List<int>();

    bool inNegative = false;

    for (int i = 0; i < value.Length; i++)
    {
        if (value[i] == startNumber)
        {
            inNegative = true;

            results.Add(value[i]);

            continue;
        }

        if (inNegative && value[i] < 0)
        {
            break;
        }

        if (inNegative)
        {
            results.Add(value[i]);
        }
    }

    return results.ToArray();
}
David Basarab
Doesn't have to be Linq but it seemed an obvious choice.. You've merely moved the `for`, `if`, and `bool` someplace else. If I were doing this a lot and didn't have a more concise option, then it might be a useful extension method.
clintp
A: 
var first = ia.Select((i, index) => new {i, index}).Where((i, index) => i.i == x).First();
var ind = first.index;
var second = ia.SkipWhile( (i, index) => index <= ind).TakeWhile(i => i > 0);
var ints = new[]{first.i}.Union(second);
Rony
{7, -1, 4, -1, 5} gives {-1, 4, -1, 5} instead of {-1, 4} with that solution.
Anders Abel