20

I've got a list of 'double' values. I need to select every 6th record. It's a list of coordinates, where I need to get the minimum and maximum value of every 6th value.

List of coordinates (sample): [2.1, 4.3, 1.0, 7.1, 10.6, 39.23, 0.5, ... ] with hundrets of coordinates.

Result should look like: [x_min, y_min, z_min, x_max, y_max, z_max] with exactly 6 coordinates.

Following code works, but it takes to long to iterate over all coordinates. I'd like to use Linq instead (maybe faster?)

for (int i = 0; i < 6; i++)
{
    List<double> coordinateRange = new List<double>();

    for (int j = i; j < allCoordinates.Count(); j = j + 6)
        coordinateRange.Add(allCoordinates[j]);

    if (i < 3) boundingBox.Add(coordinateRange.Min());
    else boundingBox.Add(coordinateRange.Max());
}

Any suggestions? Many thanks! Greets!

iDog
  • 361
  • 1
  • 3
  • 13
  • 5
    Duplicate - http://stackoverflow.com/questions/682615/how-can-i-get-every-nth-item-from-a-listt - there is a discussion about the performance there too. – ChrisF Mar 16 '10 at 11:11
  • 1
    This doesn't make sense, what distinguishes x, y and z from the rest of the list? Why are you only looking at every 6th item in the list? – cjk Mar 16 '10 at 11:13
  • @ck Because the list is already sorted. Every point has 3 coordinates. And each 2 points belong together. Like this (x1,y1,z1)(x2,y2,z2) – iDog Mar 16 '10 at 11:21

9 Answers9

22
coordinateRange.Where( ( coordinate, index ) => (index + 1) % 6 == 0 );

The answer from Webleeuw was posted prior to this one, but IMHO it's clearer to use the index as an argument instead of using the IndexOf method.

Martin R-L
  • 4,039
  • 3
  • 28
  • 28
  • Very interesting! Maybe a community wiki about uncommon overloads in the linq library is in order :-) – Dested Mar 16 '10 at 12:06
7

Something like this could help:

public static IEnumerable<T> Every<T>(this IEnumerable<T> source, int count)
{
    int cnt = 0;
    foreach(T item in source)
    {
        cnt++;
        if (cnt == count)
        {
            cnt = 0;
            yield return item;
        }
    }
}

You can use it like this:

    int[] list = new []{1,2,3,4,5,6,7,8,9,10,11,12,13};
    foreach(int i in list.Every(3))
        { Console.WriteLine(i); }

EDIT:

If you want to skip the first few entries, you can use the Skip() extension method:

foreach (int i in list.Skip(2).Every(6))
{ Console.WriteLine(i); }
Hans Kesting
  • 38,117
  • 9
  • 79
  • 111
  • +1 for this idea, even when the OP probably looked for LINQ explicitly. – Marcel Mar 16 '10 at 12:29
  • Very good, but I need to get every 6th element with variable start index. Something like: public static IEnumerable Every(this IEnumerable source, int startIndex, int count) I think I'll manage that myself. Thanks!! – iDog Mar 17 '10 at 00:17
  • That's easy, using the Skip() method. See my extended answer. – Hans Kesting Mar 17 '10 at 08:23
6

There is an overload of the Where method with lets you use the index directly:

coordinateRange.Where((c,i) => (i + 1) % 6 == 0);
codymanix
  • 28,510
  • 21
  • 92
  • 151
4

Any particular reason you want to use LINQ to do this?

Why not write a loop that steps with 6 increments each time and get access the value directly?

Philip Fourie
  • 111,587
  • 10
  • 63
  • 83
4

To find a faster solution start a profile!

Measure how long it takes for every step in your for loop and try to avoid the biggest bottleneck.

After making a second look at your code, it seems, your problem is that you run six times over your big list. So the time needed is always six times of the list size.

Instead you should run once over the whole list and put every item into the correct slot.

Just to make a performance test for yourself you should test these two approaches:

Sample class to hold data

public class Coordinates
{
    public double x1 { get; set; }
    public double x2 { get; set; }

    public double y1 { get; set; }
    public double y2 { get; set; }

    public double z1 { get; set; }
    public double z2 { get; set; }
}

Initializing of the value holder

Coordinates minCoordinates = new Coordinates();

//Cause we want to hold the minimum value, it will be initialized with
//value that is definitely greater or equal than the greatest in the list
minCoordinates.x1 = Double.MaxValue;
minCoordinates.x2 = Double.MaxValue;
minCoordinates.y1 = Double.MaxValue;
minCoordinates.y2 = Double.MaxValue;
minCoordinates.z1 = Double.MaxValue;
minCoordinates.z2 = Double.MaxValue;

Using a for loop if the index operator is O(1)

for (int i = 0; i < allCoordinates.Count; i++)
{
    switch (i % 6)
    {
        case 0:
            minCoordinates.x1 = Math.Min(minCoordinates.x1, allCoordinates[i]);
            break;
        case 1:
            minCoordinates.x2 = Math.Min(minCoordinates.x2, allCoordinates[i]);
            break;
        case 2:
            minCoordinates.y1 = Math.Min(minCoordinates.y1, allCoordinates[i]);
            break;
        case 3:
            minCoordinates.y2 = Math.Min(minCoordinates.y2, allCoordinates[i]);
            break;
        case 4:
            minCoordinates.z1 = Math.Min(minCoordinates.z1, allCoordinates[i]);
            break;
        case 5:
            minCoordinates.z2 = Math.Min(minCoordinates.z2, allCoordinates[i]);
            break;
    }
}

Using foreach if IEnumerator is O(1)

int count = 0;
foreach (var item in allCoordinates)
{
    switch (count % 6)
    {
        case 0:
            minCoordinates.x1 = Math.Min(minCoordinates.x1, item);
            break;
        case 1:
            minCoordinates.x2 = Math.Min(minCoordinates.x2, item);
            break;
        case 2:
            minCoordinates.y1 = Math.Min(minCoordinates.y1, item);
            break;
        case 3:
            minCoordinates.y2 = Math.Min(minCoordinates.y2, item);
            break;
        case 4:
            minCoordinates.z1 = Math.Min(minCoordinates.z1, item);
            break;
        case 5:
            minCoordinates.z2 = Math.Min(minCoordinates.z2, item);
            break;
    }
    count++;
}
Oliver
  • 43,366
  • 8
  • 94
  • 151
2

Well its not LINQ, but If you are worrying about performance, this might help.

public static class ListExtensions
{
    public static IEnumerable<T> Every<T>(this IList<T> list, int stepSize, int startIndex)
    {
        if (stepSize <= 0)
            throw new ArgumentException();

        for (int i = startIndex; i < list.Count; i += stepSize)
            yield return list[i];
    }
}
Janko R
  • 137
  • 1
  • 9
1

Suggestion:

coordinateRange.Where(c => (coordinateRange.IndexOf(c) + 1) % 6 == 0);

I stand corrected, thanks for the comments. As stated by codymanix, the correct answer is indeed:

coordinateRange.Where((c, i) => (i + 1) % 6 == 0);
Webleeuw
  • 7,222
  • 33
  • 34
1

The best way to do this would be refactoring the datastructure so that every dimension would be its own array. That way x1_max would be just x1.Max(). If you cannot change the input data structure, the next best way is to iterate over the array once and do all accesses locally. This helps with staying within the L1-cached memory:

var minValues = new double[] { Double.MaxValue, Double.MaxValue, Double.MaxValue };
var maxValues = new double[] { Double.MinValue, Double.MinValue, Double.MinValue };

for (int i = 0; i < allCoordinates.Length; i += 6) 
{
    for (int j = 0; i < 3; i++)
    {
        if (allCoordinates[i+j] < minValues[j])
            minValues[j] = allCoordinates[i+j];
        if (allCoordinates[i+j+3] > maxValues[j])
            maxValues[j] = allCoordinates[i+j+3];
    }
}
David Schmitt
  • 58,259
  • 26
  • 121
  • 165
1

Use Skip and combintaion with Take.

coordinateRange.Skip(6).Take(1).First();

I recommend you move this to a extension method.

Ewald
  • 698
  • 7
  • 10