开发者

How do I select every 6th element from a list (using Linq) [duplicate]

开发者 https://www.devze.com 2022-12-22 21:38 出处:网络
This question already has answers here: How can I get every nth item from a List<T>? (10 answers)
This question already has answers here: How can I get every nth item from a List<T>? (10 answers) Closed 2 years ago.

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(c开发者_如何学运维oordinateRange.Min());
    else boundingBox.Add(coordinateRange.Max());
}

Any suggestions? Many thanks! Greets!


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.


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

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


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); }


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?


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++;
}


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];
    }
}


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);


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];
    }
}


Use Skip and combintaion with Take.

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

I recommend you move this to a extension method.

0

精彩评论

暂无评论...
验证码 换一张
取 消