从列表中删除已获取的编号

本文关键字:获取 编号 删除 列表 | 更新日期: 2023-09-27 17:59:17

我有一个包含多个数字的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.1的数字的平均值都是2.05。等等)

以下是我的尝试:

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

我成功地从points列表中删除了该点,但是,在我浏览每个点的主视图中,它仍然在被删除的点上迭代,我觉得这很奇怪。

从列表中删除已获取的编号

这个问题描述了一个特殊的逻辑,它看起来有点像移动平均值,但只使用现有值来计算平均值。这需要一种前瞻性方法,因为如果不使用序列中的下一个值,就无法知道"当前"值是否会在最终序列中使用。虽然这种类型的逻辑可以使用像Skip和Take这样的Linq扩展方法来完成,但我认为没有合理的方法来使用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

数组和List的使用旨在证明,只要支持IEnumerable<double>,使用哪种集合来保存值并不重要。

--

在得出上述答案之前,查看所调查的各种结构可能会很有用。OP提到使用"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

OP询问了平均值在"当前"数字的0.5以内的问题。暂时抛开"当前"的定义不谈,这里有一个只对其整数值0.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"循环翻译看起来像这样:

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语法将不起作用。

下面的"for"循环将返回预期的结果,并让我们了解如何使用枚举器来解决问题。答案顶部显示的扩展方法就是从这个逻辑推导出来的。它更灵活,应该表现得同样好。

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

这是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();    

那么您想要x和所有介于xx + 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}

我希望这能有所帮助。

试试这个

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