Как выбрать каждый шестой элемент из списка (используя Linq)

17

У меня есть список «двойных» значений. Мне нужно выбрать каждую 6-ю запись. Это список координат, где мне нужно получить минимальное и максимальное значение каждого 6-го значения.

Список координат (образец): [2.1, 4.3, 1.0, 7.1, 10.6, 39.23, 0.5, ... ] с hundrets координат.

Результат должен выглядеть так: [x_min, y_min, z_min, x_max, y_max, z_max] с ровно 6 координатами.

Следующий код работает, но требуется много времени для итерации по всем координатам. Я бы хотел использовать Linq вместо (возможно, быстрее?)

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

Любые предложения? Большое спасибо! Поздравил!

    
задан iDog 16.03.2010 в 12:10
источник

8 ответов

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

Ответ от Webleeuw был опубликован до этого, но IMHO яснее использовать индекс в качестве аргумента вместо использования метода IndexOf .

    
ответ дан Martin R-L 16.03.2010 в 12:52
источник
6

Существует перегрузка метода Where, позволяющая напрямую использовать индекс:

coordinateRange.Where((c,i) => (i + 1) % 6 == 0);
    
ответ дан codymanix 16.03.2010 в 12:51
источник
5

Что-то вроде этого могло бы помочь:

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

Вы можете использовать его следующим образом:

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

ИЗМЕНИТЬ

Если вы хотите пропустить первые несколько записей, вы можете использовать метод расширения Skip ():

foreach (int i in list.Skip(2).Every(6))
{ Console.WriteLine(i); }
    
ответ дан Hans Kesting 16.03.2010 в 12:50
источник
4

Любая конкретная причина, по которой вы хотите использовать LINQ для этого?

Почему бы не написать цикл, который каждый раз выполняет 6 приращений и напрямую получает доступ к значению?

    
ответ дан Philip Fourie 16.03.2010 в 12:13
источник
4

Чтобы найти более быстрое решение запустите профиль !

Измерьте, сколько времени потребуется для каждого шага в цикле for и попытайтесь избежать самого большого узкого места.

После второго взгляда на ваш код, кажется, ваша проблема в том, что вы выполняете шесть раз над своим большим списком. Таким образом, требуемое время всегда шесть раз превышает размер списка.

Вместо этого вы должны запускать один раз по всему списку и помещать каждый элемент в правильный слот.

Чтобы сделать тест производительности для себя, вы должны проверить эти два подхода:

Пример класса для хранения данных

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

Инициализация держателя значений

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;

Использование цикла for, если оператор индекса равен 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;
    }
}

Использование foreach, если IEnumerator - 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 16.03.2010 в 12:50
источник
2

Ну, это не LINQ, но если вы беспокоитесь о производительности, это может помочь.

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 16.03.2010 в 13:15
источник
1

Предложение:

coordRange.Where (c = & gt; (координатаRange.IndexOf (c) + 1)% 6 == 0);

Я согласен, спасибо за комментарии. Как указано codymanix, правильный ответ действительно:

coordinateRange.Where((c, i) => (i + 1) % 6 == 0);
    
ответ дан Webleeuw 16.03.2010 в 12:14
источник
1

Лучший способ сделать это - рефакторинг структуры данных, чтобы каждое измерение было его собственным массивом. Таким образом, x1_max будет всего x1.Max() . Если вы не можете изменить структуру входных данных, следующий лучший способ - перебрать массив по одному и сделать все доступ локально. Это помогает оставаться в памяти, хранящейся в L1:

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 16.03.2010 в 13:04
источник