Создать ведра с Linq

Я хочу создать сегменты для List<double>, например, разделить на n группы, например:

List<double> list = new List<double>() { 
  0, 0.1, 1.1, 2.2, 3.3, 4.1, 5.6, 6.3, 7.1, 8.9, 9.8, 9.9, 10 
};

n = 5

Я хочу получить что-то вроде этого

  bucket     values
---------------------------------
[0 ..  2] -> {0, 0.1, 1.1}
[2 ..  4] -> {2.2, 3.3}
...
[8 .. 10] -> {8.9, 9.8, 9.9, 10} 

Проблема в том, что если я делаю GroupBy, используя:

return items
    .Select((item, inx) => new { item, inx })
    .GroupBy(x => Math.Floor(x.item / step))
    .Select(g => g.Select(x => x.item));

Я всегда получаю нежелательное первое или последнее ведро, например [10 .. 12] (обратите внимание, что все значения находятся в диапазоне [0 .. 10]) или [0 .. 0] (обратите внимание на неправильный диапазон корзины), который содержит только экстремальные значения (0 или 10 в приведенном выше примере).

любая помощь ?


person Skander    schedule 16.03.2021    source источник
comment
Итак, у вас есть список и словарь? Как связаны эти две вещи? Что такое step? Я вообще не понимаю этого вопроса.   -  person John Wu    schedule 16.03.2021
comment
не могли бы вы уточнить, что вы подразумеваете под экстремальными значениями? если у вас есть какие-либо ошибки, которые возникают только в пограничных случаях, вы должны включить в свой вопрос хотя бы один из этих пограничных случаев. кроме того, ваш вопрос довольно неясен. пожалуйста, постарайтесь привести более подробную информацию в вашем описании   -  person Franz Gleichmann    schedule 16.03.2021
comment
С каким входом вы получаете неожиданный результат? Что это за результат и чего вы ожидали?   -  person Magnus    schedule 16.03.2021
comment
Вы ищете кластер ?en.wikipedia.org/wiki/K-means_clustering. Можете ли вы рассмотреть базовый пограничный случай с помощью вашего минимально воспроизводимого примера? Группируем ли мы {3.3, 4.1}, потому что между этими двумя меньше 1?   -  person Drag and Drop    schedule 16.03.2021


Ответы (1)


Что ж, для произвольного списка вам нужно вычислить диапазон: [min..max], а затем

  step = (max - min) / 2;

Код:

  // Given

  List<double> list = new List<double>() {
    0, 0.1, 1.1, 2.2, 3.3, 4.1, 5.6, 6.3, 7.1, 8.9, 9.8, 9.9, 10
  };

  int n = 5; 

  // We compute step

  double min = list.Min();
  double max = list.Max();

  double step = (max - min) / 5;

  // And, finally, group by:

  double[][] result = list
    .GroupBy(item => (int)Math.Clamp((item - min) / step, 0, n - 1))
    .OrderBy(group => group.Key)
    .Select(group => group.ToArray())
    .ToArray();

  // Let's have a look:

  string report = string.Join(Environment.NewLine, result
    .Select((array, i) => $"[{min + i * step} .. {min + i * step + step,2}) : {{{string.Join("; ", array)}}}"));

  Console.WriteLine(report);

Результат:

[0 ..  2) : {0; 0.1; 1.1}
[2 ..  4) : {2.2; 3.3}
[4 ..  6) : {4.1; 5.6}
[6 ..  8) : {6.3; 7.1}
[8 .. 10) : {8.9; 9.8; 9.9; 10}

Обратите внимание на метод Math.Clamp, чтобы обеспечить диапазон [0..n-1] для групповых ключей. Если вам нужен Dictionary<int, double[]>, где Key - это индекс корзины:

  Dictionary<int, double[]> buckets = list
    .GroupBy(item => (int)Math.Clamp((item - min) / step, 0, n - 1))
    .ToDictionary(group => group.Key, group => group.ToArray());
person Dmitry Bychenko    schedule 16.03.2021
comment
Спасибо! впервые использую метод Math.Clamp, очень полезно - person Skander; 16.03.2021