2016-07-28 3 views
0

У меня есть List<double> с несколькими номерами. То, что я пытаюсь сделать, - посмотреть, какие doubles похожи друг на друга; добавить все похожие числа и получить среднее значение. Так, например, в списке: { 2.1, 2.2, 4, 4.1, 8, 8.2} станут: {2.15, 4.05, 8.1}Удаление принятых номеров из списка

Что у меня возникают некоторые проблемы с, когда я смотрю на подобные чисел, используя это заявление LINQ: tempList = points.Where(p => Abs(p - currentPoint) < 0.25).ToList();, как я и одновременно удалить все точки из списка points что я выбрал в tempList, так что я не смотрю на одни и те же номера снова и снова (например, ссылаясь на мой пример, если я просто посмотрел на 2.1, я не хочу смотреть на 2.2 на следующей итерации, потому что Я только что нашел среднее значение 2.05 для всех чисел, подобных 2.1 .etc).

Здесь были мои попытки:

points = points.Except(tempList).ToList(); 
//and 
foreach (var t in tempList) 
{ 
    points.Remove(t); 
} 

Я успешно удалить точку из списка points, НО, на мой основной вид, где я буду через каждую из точек, он все еще перебирает DELETED пунктов, который я нахожу очень странным.

+0

Вы ищете ответ с помощью Linq, или тонкая петля? – ispiro

+0

@ispiro Либо один. Кроме того, я уже пробовал использовать 'Except()', в моем цикле, который я использую, чтобы пройти через список «points», но я все еще заканчиваю повторение числа, которое я удалил, используя 'Except()'. – John

+0

Опираясь на приоритет слева направо? Для '{1.1,1.2,1.3,1.4,1.5,1.6}' ли '{1.1,1.2,1.3}' сглаживается вместе с {{1.4.1.5,1.6} '. Потому что это также может быть '{1.1.1.2}' then '{1.3,1.4,1.5}' then '{1.6}' или многие другие комбинации. – Derpy

ответ

4

Вопрос описывает специализированную логику, которая, кажется, несколько, как скользящее среднее, но только с использованием существующих значений для вычисления среднего значения. Для этого требуется подход «вперед», поскольку вы не можете знать, будет ли «текущее» значение использоваться в окончательной последовательности без использования следующего значения в последовательности. Хотя этот тип логики может быть выполнен с использованием методов расширения Linq, таких как Skip и Take, я не вижу разумного способа использования синтаксиса Linq для этого. Это дает хорошие академические учения. Но в реальном мире этот случай использования требует простого подхода. Даже если бы вы могли заставить его работать, синтаксис Linq был бы гораздо менее читабельным и поддерживаемым и почти наверняка поразил бы производительность по сравнению с более прямыми подходами.

Однако одной из наиболее полезных функций Linq является библиотека методов расширения. Они обеспечивают «свободный интерфейс», который позволяет выполнять несколько операций в одном заявлении. Следующий пример использует тот же подход и возвращает результаты, ожидаемые OP (как упоминалось в вопросе и некоторых комментариях). Он гибкий, выразительный и должен хорошо работать. И это близко к Linq, поскольку вы собираетесь получить этот вид приложения.

static public class MyExtensions 
{ 
    static public IEnumerable<double> GetPointAverages(this IEnumerable<double> points) 
    { 
     var e = points.GetEnumerator(); 
     var reading = e.MoveNext(); 
     while (reading) 
     { 
      var value = e.Current; 
      reading = e.MoveNext(); 
      if (reading && e.Current - value < .5) 
      { 
       value = (value + e.Current)/2; 
       reading = e.MoveNext(); 
      } 
      yield return value; 
     } 
    } 
} 

class Program 
{ 
    static void Main() 
    { 
     var pointsArray = new[] { 2.1, 2.2, 2.6, 4, 4.2, 4.7, 4.8 }; 
     var averages = String.Join(", ", 
      pointsArray.GetPointAverages().Select(p => p.ToString()) 
      ); 
     Console.WriteLine("Result: {0}", averages); 
     Console.WriteLine(); 
     var pointsList = new List<double>() { 8.8, 9.0 }; 
     averages = String.Join(", ", 
      pointsList.GetPointAverages().Select(p => p.ToString()) 
      ); 
     Console.WriteLine("Result: {0}", averages); 
    } 
} 

-

Result: 2.15, 2.6, 4.1, 4.75 

Result: 8.9 

Использование как массив и список предназначен для демонстрации, что это не имеет значения, какой из коллекции используется для хранения значений до тех пор он поддерживает IEnumerable<double> ,

-

Это может быть полезно, чтобы увидеть различные конструкции, которые были расследованы до прибытия в ответе выше. ОП упоминается с использованием цикла «while». Это привело к некоторому экспериментированию для воспроизведения желаемых результатов с целью преобразования тестируемого подхода в синтаксис Linq.

Вот пример Linq, используя один оператор, и предполагает «аналогичные» означает иметь то же целое значение:

var points = 
    from p in new[] { 2.1, 2.2, 4, 4.1, 8, 8.2 } 
    group p by (int) p into avgs 
    select avgs.Average(); 

Console.WriteLine(String.Join(", ", points.Select(p => p.ToString()))); 

Result: 2.15, 4.05, 8.1 

ОП спросил о среднем в пределах .5 «текущего» номер. Отменив определение «текущий» на данный момент, вот пример усреднения только чисел, которые находятся в пределах .5 их целочисленного значения.

var points = 
    from p in new[] { 2.1, 2.2, 2.6, 4, 4.2, 4.7, 4.8 } 
    let intp = (double)((int)p) 
    let grp = (p - intp < .5) ? intp : p 
    group p by grp into avgs 
    select avgs.Average(); 

var averages = String.Join(", ", points.Select(p => p.ToString())); 

Console.WriteLine(averages); 

Result: 2.15, 2.6, 4.1, 4.7, 4.8  

Концепция «текущего» номера исключает синтаксис Linq в качестве опции. При использовании Linq, поскольку он предназначен для работы, вы всегда смотрите только на один элемент в последовательности. Вы, теоретически, не знаете положения внутри последовательности или других элементов в последовательности. Механизм группировки позволяет использовать методы агрегирования, которые накапливают значения в режиме «как вы». Использование «текущего» числа, заданного в вопросе, требует перспективного подхода и возрастающей упорядоченной последовательности. Знания и их использование не являются частью дизайна Linq. Однако перевод того, что мы сделали в Linq в логику циклов, может помочь привести к решению, подобному Linq.

выше Linq заявление будет трансформироваться в нечто петли, как это:

var points = new[] { 2.1, 2.2, 2.6, 4, 4.2, 4.7, 4.8 }; 
var groups = new Dictionary<double, List<double>>(); 

foreach (var p in points) 
{ 
    var intp = (double)((int)p); 
    if (p - intp < .5) 
    { 
     if (!groups.ContainsKey(intp)) 
     { 
      groups[intp] = new List<double>(); 
     } 
     groups[intp].Add(p); 
    } 
    else 
    { 
     groups[p] = new List<double> { p }; 
    } 
} 

points = groups.Select(dict => dict.Value.Average()).ToArray(); 

«За» перевод цикл будет выглядеть примерно так:

for (int i = 0; i < points.Length; i++) 
{ 
    var p = points[i]; 
    var intp = (double)((int)points[i]); 
    if (p - intp < .5) 
    { 
     if (!groups.ContainsKey(intp)) 
     { 
      groups[intp] = new List<double>(); 
     } 
     groups[intp].Add(p); 
    } 
    else 
    { 
     groups[p] = new List<double> { p }; 
    } 
} 

Как правило, если вы можете представить, что итерация с использованием foreach, вы почти наверняка можете использовать Linq. Если вам нужен доступ к другим элементам в последовательности во время итерации, синтаксис Linq не будет работать.

Следующий цикл «для» возвращает ожидаемые результаты и дает нам представление о том, как мы могли бы использовать перечислитель для решения нашей проблемы. Метод расширения, показанный в верхней части ответа, был получен из этой логики. Он более гибкий и должен работать так же хорошо.

var tmpPoints = new List<double>(); 
for (var i = 0; i < points.Length;) 
{ 
    var value = points[i]; 
    var next = i + 1; 
    if (next < points.Length && points[next] - points[i] < .5) 
    { 
     value = (points[i] + points[next])/2; 
     i = next + 1; 
    } 
    else 
    { 
     i++; 
    } 
    tmpPoints.Add(value); 
} 

points = tmpPoints.ToArray(); 

// Results using the two example sequences 

points = new[] { 2.1, 2.2, 2.6, 4, 4.2, 4.7, 4.8 }; 

Result: 2.15, 2.6, 4.1, 4.75 

points = new[] { 8.8, 9.0 }; 

Result: 8.9 
+0

Что делать, если я хотел рассчитать среднее значение для чисел в пределах '0,5' от текущего числа. Например, скажем, я: '{2.1, 2.2, 2.6, 4, 4.2, 4.7, 4.8}'. Поэтому в этом случае, когда я смотрю на «2.1», в пределах диапазона находится только «2.2», и когда я смотрю «2.6», внутри диапазона нет ничего, поэтому его просто «2.6» ... так по существу, средние значения будут выглядеть следующим образом: '{2.15, 2.6, 4.1, 4.75}'. У меня есть цикл while, который может это сделать, однако я хотел бы знать, есть ли способ сделать это проще, используя LINQ. – John

+0

Я обновил пример. Я все еще пытаюсь понять, как понятие «ток» может работать в Linq. Это интересный вариант использования. – rhaben

0

Итак, вы хотите получить среднее значение суммы x и всех номеров, которые находятся между x и x + 1? Тогда вот он:

List<double> numbers = new List<double>() { 2.1, 2.2, 4, 4.1, 8, 8.2 }; 
List<double> averages = new List<double>(); 

// This compares the first number in the sequence to all others. When all "matches" have been found, the matches and the first number is deleted. Repeat until no numbers are left 
while (numbers.Count > 0) 
{ 
    int numberOfMatches = 1; // The number at numbers[0] is a match 
    double sum = numbers[0]; // Add numbers[0] to the sum 

    for (int i = 1; i < numbers.Count; i++) // Go through all number except the first 
    { 
     if (numbers[0] <= numbers[i] && numbers[i] < (int)numbers[0] + 1) // If numbers[i] is withing the x to x + 1 range 
     { 
      sum += numbers[i]; // Add the new number 
      numberOfMatches++; // Increase the number of numbers summerised 

      numbers.RemoveAt(i); // Remove the current number since it's already been used 
      i--; // Go back one step to not skip a number, since a number was just removed 
     } 
    } 

    numbers.RemoveAt(0); // Remove numbers[0] 

    averages.Add(sum/numberOfMatches); // Add the average to averages 
} 

Результат: средние?

{2,1500000000000004, 4,05, 8,1}

Я надеюсь, что это помогает.

0

Попробуйте

var list = new List<double> { 2.1, 2.2, 4, 4.1, 8, 8.2}; 
    var avereges = new List<double>(); 


    list.Sort(); 


    double sum = list[0], original = list[0], count = 1; 


    for (int i = 1; i < list.Count; i++) 
    { 
     var isSimiliar = Math.Abs(original-list[i]) < 0.25; 

     if (isSimiliar) 
     { 
      sum += list[i]; 
      count++; 
     } 

     if (!isSimiliar || i == list.Count-1) 
     { 
      avereges.Add(sum/count); 
      count = 1; 
      sum = list[i]; 
      original = list[i]; 
     } 
    } 


    Console.WriteLine(String.Join(" ", avereges)); 
2

Это другая форма ответа rhaben в:

var list = new List<double> { 2.1, 2.2, 4, 4.1, 8, 8.2 }; 

var newList = list.GroupBy(d => (int)d).Select(g => g.Average()).ToList();  
+0

Что делать, если я хотел рассчитать среднее значение для чисел в пределах 0,5 от текущего числа. Например, скажем, у меня есть: {2.1, 2.2, 2.6, 4, 4.2, 4.7, 4.8}. Таким образом, в этом случае, когда я смотрю на 2.1, только 2.2 находится в пределах диапазона, и когда я смотрю на 2.6, в пределах диапазона нет ничего, поэтому его просто 2,6 ... так что средние значения будут выглядеть следующим образом: { 2,15, 2,6, 4,1, 4,75}. У меня есть цикл while, который может это сделать, однако я хотел бы знать, есть ли способ сделать это проще, используя LINQ. – John

+0

что-то вроде '.GroupBy (d => (int) (d * 2))' – Slai

+0

Это вроде работает. Проблема в том, что когда у меня есть числа, такие как '{8.8, 9.0}' и ничего больше, я хочу, чтобы среднее из этих двух чисел, так как '9.0' находится в пределах диапазона 8.8, но из-за группировки это doesn ' t:/ – John

Смежные вопросы