繁体   English   中英

如何使用LINQ比较List <>中的顺序“邻居”的值?

[英]How to use LINQ to compare values of sequential “neighbors” inside a List<>?

看看这段代码:

        ColorResult contains Index, Color Name, and Probability

        Colors.Add(new ColorResult(1, "Unknown", 5f));
        Colors.Add(new ColorResult(2, "Blue", 80f));
        Colors.Add(new ColorResult(3, "Blue", 80f));
        Colors.Add(new ColorResult(4, "Green", 40f));
        Colors.Add(new ColorResult(5, "Blue", 80f));
        Colors.Add(new ColorResult(6, "Blue", 80f));
        Colors.Add(new ColorResult(7, "Red", 20f));
        Colors.Add(new ColorResult(8, "Blue", 80f));
        Colors.Add(new ColorResult(9, "Green", 5f));

使用LINQ,您将如何完成以下任务:

1)按顺序工作,当后面的概率高于60的前两个项具有相同的值(“未知”变为“蓝色”,因为#时,替换概率低于60的List <>开头的所有项目2和#3是蓝色,概率为60+)

2)用FOUR邻居包围的概率低于60的任何项目替换具有相同的值(“Green”变为“Blue”,因为#2,#3,#5和#6是蓝色并且概率为60+)

3)按顺序工作,替换List <>末尾的两个具有相同值的两个项目的项目(与第一个部分相同,但相反)。 在样本数据中,#9不会发生任何事情,因为#7需要是“蓝色”并且需要60+概率。

循环这很容易,但我绝对不知道如何比较LINQ中的顺序“邻居”。

这是我对第1部分的原始解决方案:

        bool partOneCompleted = false;
        for (int i = 0; i < Colors.Count; i++)
        {
            if (Colors[i].ResultConfidence > 60)
            {
                // This item does not need to be changed
                partOneCompleted = true;
            }
            if (!partOneCompleted)
            {
                int twoItemsAway = i + 2;
                if (twoItemsAway < Colors.Count)
                {
                    if (Colors[twoItemsAway].Name == Colors[twoItemsAway - 1].Name && Colors[twoItemsAway].ResultConfidence > 60 && Colors[twoItemsAway - 1].ResultConfidence > 60)
                    {
                        // The next item, and the one after that both have the same value and 60+ confidence
                        for (int loopBack = i; loopBack >= 0; loopBack--)
                        {
                            Colors[loopBack].Name = Colors[twoItemsAway].Name;
                        }

                        partOneCompleted = true;
                    }
                }
            }
        }

任何LINQ专家都能分享最有效的实施吗?

这是第一个让你前进的例子。 关键是使用Enumerable.Range因此您拥有索引。 如前所述,循环可读性更强。

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;

namespace ConsoleApplication1
{
    public class ColorResult
    {
        public int Index;
        public string Name;
        public float Prob;

        public ColorResult(int Index, string Name, float Prob)
        {
            this.Index = Index;
            this.Name = Name;
            this.Prob = Prob;
        }

        public override string ToString()
        {
            return Index.ToString() + ", " + Name + ", " + Prob.ToString();
        }
    }

    class Program
    {
        public static void Main()
        {
            List<ColorResult> Colors = new List<ColorResult>();

            Colors.Add(new ColorResult(1, "Unknown", 5f));
            Colors.Add(new ColorResult(2, "Blue", 80f));
            Colors.Add(new ColorResult(3, "Blue", 80f));
            Colors.Add(new ColorResult(4, "Green", 40f));
            Colors.Add(new ColorResult(5, "Blue", 80f));
            Colors.Add(new ColorResult(6, "Blue", 80f)); 
            Colors.Add(new ColorResult(7, "Red", 20f)); 
            Colors.Add(new ColorResult(8, "Blue", 80f));   
            Colors.Add(new ColorResult(9, "Green", 5f));


            var test1 = from i in Enumerable.Range(0, Colors.Count)
                        select (i < Colors.Count - 2 &&
                               (Colors[i].Prob < 60f) &&
                               (Colors[i + 1].Name == Colors[i + 2].Name) &&
                               (Colors[i+1].Prob > 60f) &&
                               (Colors[i+2].Prob > 60f)) ?
                        new ColorResult(1, Colors[i + 1].Name, Colors[i].Prob) :
                        Colors[i];


        }
    }
}

这是一个基于产量的解决方案,我不是严格意义上的LINQ,但它与LINQ类似。 快速实施; 我相信有更好的方法可以做到这一点。 为乔治窃取他的测试框架道歉(和+1)。

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;

namespace ConsoleApplication1
{
  public class ColorResult
  {
    public int Index;
    public string Name;
    public float Prob;

    public ColorResult(int Index, string Name, float Prob)
    {
      this.Index = Index;
      this.Name = Name;
      this.Prob = Prob;
    }

    public override string ToString()
    {
      return Index.ToString() + ", " + Name + ", " + Prob.ToString();
    }
  }

  class Program
  {
    // Iterate through the list remembering the last two elements
    // to implement rule 1
    public static IEnumerable<ColorResult> Rule1(IEnumerable<ColorResult> input)
    {
      ColorResult last2 = null;
      ColorResult last1 = null;
      foreach (var color in input)
      {
        if ((color.Prob < 60f)
            && (last1 != null) && (last1.Prob >= 60f)
            && (last2 != null) && (last2.Prob >= 60f)
            && (last2.Name == last1.Name))
        {
          color.Name = last2.Name;
        }
        yield return color;
        last2 = last1;
        last1 = color;
      }
    }

    // Iterate through the list with two element look-ahead
    // to implement rule 3
    public static IEnumerable<ColorResult> Rule3(IEnumerable<ColorResult> input)
    {
      ColorResult color = null;
      ColorResult ahead1 = null;
      foreach (var ahead2 in input)
      {
        if ((color != null) && (color.Prob < 60f)
            && (ahead1 != null) && (ahead1.Prob >= 60f)
            && (ahead2 != null) && (ahead2.Prob >= 60f)
            && (ahead1.Name == ahead2.Name))
        {
          color.Name = ahead1.Name;
        }
        yield return color;
        color = ahead1;
        ahead1 = ahead2;
      }
      // Using a null check here as a cheat way to test we've
      // actually had two inputs.
      // NB Will not preserve trailing nulls in the list;
      // you'll need to count inputs if you need that.
      if (color != null) yield return color;
      if (ahead1 != null) yield return ahead1;
    }

    public static void Main()
    {
      List<ColorResult> Colors = new List<ColorResult>();

      Colors.Add(new ColorResult(1, "Unknown", 5f));
      Colors.Add(new ColorResult(2, "Blue", 80f));
      Colors.Add(new ColorResult(3, "Blue", 80f));
      Colors.Add(new ColorResult(4, "Green", 40f));
      Colors.Add(new ColorResult(5, "Blue", 80f));
      Colors.Add(new ColorResult(6, "Blue", 80f));
      Colors.Add(new ColorResult(7, "Red", 20f));
      Colors.Add(new ColorResult(8, "Blue", 80f));
      Colors.Add(new ColorResult(9, "Green", 5f));

      var processed = Rule3(Rule1(Colors));
      foreach (var color in processed)
      {
        Console.WriteLine(color);
      }
    }
  }
}

我开始测试第1部分:

[Test]
public void Should_convert_leading_low_probability_colors()
{
    var colors = new List<ColorResult>
        {
            new ColorResult(1, "Unknown", 5f),
            new ColorResult(2, "Blue", 80f),
            new ColorResult(3, "Blue", 80f),
            new ColorResult(4, "Green", 40f),
            new ColorResult(5, "Blue", 80f),
            new ColorResult(6, "Blue", 80f),
            new ColorResult(7, "Red", 20f),
            new ColorResult(8, "Blue", 80f),
            new ColorResult(9, "Green", 5f)
        };

    ConvertLeadingLowProbabilityColors(colors);

    foreach (var colorResult in colors)
    {
        Console.WriteLine(colorResult.Index + " " + colorResult.Color);
    }

    colors[0].Color.ShouldBeEqualTo("Blue");
    colors[1].Color.ShouldBeEqualTo("Blue");
    colors[2].Color.ShouldBeEqualTo("Blue");
    colors[3].Color.ShouldBeEqualTo("Green");
    colors[4].Color.ShouldBeEqualTo("Blue");
    colors[5].Color.ShouldBeEqualTo("Blue");
    colors[6].Color.ShouldBeEqualTo("Red");
    colors[7].Color.ShouldBeEqualTo("Blue");
    colors[8].Color.ShouldBeEqualTo("Green");
}

和实施

private void ConvertLeadingLowProbabilityColors(IList<ColorResult> colors)
{
    var leadingBelow60 = Enumerable
        .Range(0, colors.Count)
        .TakeWhile(index => colors[index].Probability < 60)
        .ToList();
    if (leadingBelow60.Count > 0 && leadingBelow60.Count < colors.Count - 2)
    {
        int lastIndex = leadingBelow60.Last();
        var firstNext = colors[lastIndex + 1];
        var secondNext = colors[lastIndex + 2];
        if (firstNext.Probability > 60 &&
            secondNext.Probability > 60 &&
            firstNext.Color == secondNext.Color)
        {
            leadingBelow60.ForEach(index => colors[index].Color = firstNext.Color);
        }
    }
}

然后为第3部分添加了测试,因为它是第1部分的变体:

[Test]
public void Should_convert_trailing_low_probability_colors()
{
    var colors = new List<ColorResult>
        {
            new ColorResult(1, "Unknown", 5f),
            new ColorResult(2, "Blue", 80f),
            new ColorResult(3, "Blue", 80f),
            new ColorResult(4, "Green", 40f),
            new ColorResult(5, "Blue", 80f),
            new ColorResult(6, "Blue", 80f),
            new ColorResult(7, "Red", 20f),
            new ColorResult(8, "Blue", 40f),
            new ColorResult(9, "Green", 5f)
        };

    ConvertTrailingLowProbabilityColors(colors);

    foreach (var colorResult in colors)
    {
        Console.WriteLine(colorResult.Index + " " + colorResult.Color);
    }

    colors[0].Color.ShouldBeEqualTo("Unknown");
    colors[1].Color.ShouldBeEqualTo("Blue");
    colors[2].Color.ShouldBeEqualTo("Blue");
    colors[3].Color.ShouldBeEqualTo("Green");
    colors[4].Color.ShouldBeEqualTo("Blue");
    colors[5].Color.ShouldBeEqualTo("Blue");
    colors[6].Color.ShouldBeEqualTo("Blue");
    colors[7].Color.ShouldBeEqualTo("Blue");
    colors[8].Color.ShouldBeEqualTo("Blue");
}

和实施:

private void ConvertTrailingLowProbabilityColors(IList<ColorResult> colors)
{
    var trailingBelow60 = Enumerable
        .Range(0, colors.Count)
        .Select(i => colors.Count - 1 - i)
        .TakeWhile(index => colors[index].Probability < 60)
        .ToList();
    if (trailingBelow60.Count > 0 && trailingBelow60.Count < colors.Count - 2)
    {
        int lastIndex = trailingBelow60.Last();
        var firstPrevious = colors[lastIndex - 1];
        var secondPrevious = colors[lastIndex - 2];
        if (firstPrevious.Probability > 60 &&
            secondPrevious.Probability > 60 &&
            firstPrevious.Color == secondPrevious.Color)
        {
            trailingBelow60.ForEach(index => colors[index].Color = firstPrevious.Color);
        }
    }
}

然后,我解决了第2部分。我再次开始测试:

[Test]
public void Should_convert_surrounded_low_probability_colors()
{
    var colors = new List<ColorResult>
        {
            new ColorResult(1, "Unknown", 5f),
            new ColorResult(2, "Blue", 80f),
            new ColorResult(3, "Blue", 80f),
            new ColorResult(4, "Green", 40f),
            new ColorResult(5, "Blue", 80f),
            new ColorResult(6, "Blue", 80f),
            new ColorResult(7, "Red", 20f),
            new ColorResult(8, "Blue", 80f),
            new ColorResult(9, "Green", 5f)
        };

    ConvertSurroundedLowProbabilityColors(colors);

    foreach (var colorResult in colors)
    {
        Console.WriteLine(colorResult.Index + " " + colorResult.Color);
    }

    colors[0].Color.ShouldBeEqualTo("Unknown");
    colors[1].Color.ShouldBeEqualTo("Blue");
    colors[2].Color.ShouldBeEqualTo("Blue");
    colors[3].Color.ShouldBeEqualTo("Blue");
    colors[4].Color.ShouldBeEqualTo("Blue");
    colors[5].Color.ShouldBeEqualTo("Blue");
    colors[6].Color.ShouldBeEqualTo("Red");
    colors[7].Color.ShouldBeEqualTo("Blue");
    colors[8].Color.ShouldBeEqualTo("Green");
}

这个实现:

private void ConvertSurroundedLowProbabilityColors(IList<ColorResult> colors)
{
    var surrounding4Modification = new Surrounding4ModificationStrategy();
    foreach (int index in Enumerable
        .Range(0, colors.Count)
        .Where(index => surrounding4Modification.IsMatch(colors, index)))
    {
        surrounding4Modification.Update(colors, index);
    }
}

这次抽出帮助类看起来更干净:

public class Surrounding4ModificationStrategy
{
    public bool IsMatch(IList<ColorResult> input, int index)
    {
        if (index < 2)
        {
            return false;
        }
        if (index >= input.Count - 2)
        {
            return false;
        }
        if (input[index].Probability >= 60)
        {
            return false;
        }

        var secondPrevious = input[index - 2];
        if (secondPrevious.Probability < 60)
        {
            return false;
        }
        var firstPrevious = input[index - 1];
        if (firstPrevious.Probability < 60)
        {
            return false;
        }

        var firstNext = input[index + 1];
        if (firstNext.Probability < 60)
        {
            return false;
        }
        var secondNext = input[index + 2];
        if (secondNext.Probability < 60)
        {
            return false;
        }

        if (new[] { secondPrevious.Color, firstPrevious.Color, firstNext.Color, secondNext.Color }.Distinct().Count() > 1)
        {
            return false;
        }
        return true;
    }

    public void Update(IList<ColorResult> input, int index)
    {
        input[index].Color = input[index + 1].Color;
    }
}

最后,我使用您的数据创建了一个综合测试:

[Test]
public void Should_convert_all_low_probability_colors()
{
    var colors = new List<ColorResult>
        {
            new ColorResult(1, "Unknown", 5f),
            new ColorResult(2, "Blue", 80f),
            new ColorResult(3, "Blue", 80f),
            new ColorResult(4, "Green", 40f),
            new ColorResult(5, "Blue", 80f),
            new ColorResult(6, "Blue", 80f),
            new ColorResult(7, "Red", 20f),
            new ColorResult(8, "Blue", 80f),
            new ColorResult(9, "Green", 5f)
        };

    ConvertLowProbabilityColors(colors);

    foreach (var colorResult in colors)
    {
        Console.WriteLine(colorResult.Index + " " + colorResult.Color);
    }

    colors[0].Color.ShouldBeEqualTo("Blue");
    colors[1].Color.ShouldBeEqualTo("Blue");
    colors[2].Color.ShouldBeEqualTo("Blue");
    colors[3].Color.ShouldBeEqualTo("Blue");
    colors[4].Color.ShouldBeEqualTo("Blue");
    colors[5].Color.ShouldBeEqualTo("Blue");
    colors[6].Color.ShouldBeEqualTo("Red");
    colors[7].Color.ShouldBeEqualTo("Blue");
    colors[8].Color.ShouldBeEqualTo("Green");
}

以及使用上面创建的方法的实现:

public void ConvertLowProbabilityColors(IList<ColorResult> colors)
{
    ConvertLeadingLowProbabilityColors(colors);
    ConvertSurroundedLowProbabilityColors(colors);
    ConvertTrailingLowProbabilityColors(colors);
}

如果这是我的代码库,我会继续在边缘情况下添加测试,例如:第1和第3部分的概率<60的所有项目; 第一部分,除了最后...... 第3部分除了第一部分外; 等等

您可以使用linq非常巧妙地对顺序项进行操作:

  var colours = new List<string>(new[]{"red", "green", "blue"});
  var added = colours
              .Skip(1)
              .Zip(colours,
              (second, first) => first + second);
  foreach (var colour in added)
  {
    Console.WriteLine(colour);
  } 

请注意,我们跳过序列的第一个元素,然后用自己压缩结果。 这给出了:

redgreen

绿,蓝

首先,我认为规则1和规则3只是规则2的边缘情况。我将从2个扩展方法开始LINQify:

  1. 一种方法,为每个元素返回它周围的邻居。 在开头和结尾添加两个空值以处理边缘情况。 我把这个方法称为WithSurroundingNeigbours
  2. 一种方法,从5个元素的列表中返回是否应排除中间元素。 我把它留作练习:-)

结合起来,这允许编写结果查询,如下所示:

       var results = from x in Colors.WithSurroundingNeighbours()
                     where !x.ItemShouldBeRemoved()
                     select x[2]; 



public static  class Extensions
{
    public static IEnumerable<List<T>> WithSurroundingNeighbours<T>(this IEnumerable<T> input) where T : class
    {
        var q = new List<T>();
        var twoNulls = new T[] {null, null};
        foreach (var item in twoNulls.Concat(input).Concat(twoNulls))
        {
            q.Add(item);
            if (q.Count < 5) continue;
            yield return q;
            q.RemoveAt(0);
        }
    }

    public static bool ItemShouldBeRemoved(this List<ColorResult> items) 
    {
        if (items.Count != 5) throw new ArgumentException("expected list with exactly 5 items");
        // TODO: return true when Item3 of the tuple should be removed
        // for the first item in the list, Item1 and Item2 are null
        // for the last item in the list, Item4 and Item5 are null
        return false;
    } 
}

以下是使用循环的完整解决方案,以防任何人感兴趣:

        bool startComplete = false;
        for (int i = 0; i < Colors.Count; i++)
        {
            if (Colors[i].ResultConfidence > 60)
            {
                // This item does not need to be changed
                startComplete = true;
            }
            if (!startComplete)
            {
                int twoItemsAway = i + 2;
                if (twoItemsAway < Colors.Count)
                {
                    if (Colors[twoItemsAway].Name == Colors[twoItemsAway - 1].Name && Colors[twoItemsAway].ResultConfidence > 60 && Colors[twoItemsAway - 1].ResultConfidence > 60)
                    {
                        // The next item, and the one after that both have the same value and 60+ confidence
                        for (int loopBack = i; loopBack >= 0; loopBack--)
                        {
                            Colors[loopBack].Name = Colors[twoItemsAway].Name;
                        }

                        startComplete = true;
                    }
                }
            }
        }

        bool endComplete = false;
        for (int i = Colors.Count - 1; i >= 0; i--)
        {
            if (Colors[i].ResultConfidence > 60)
            {
                // This item does not need to be changed
                endComplete = true;
            }
            if (!endComplete)
            {
                int twoItemsAway = i - 2;
                if (twoItemsAway >= 0)
                {
                    if (Colors[twoItemsAway].Name == Colors[twoItemsAway + 1].Name && Colors[twoItemsAway].ResultConfidence > 60 && Colors[twoItemsAway + 1].ResultConfidence > 60)
                    {
                        // The next item, and the one after that both have the same value and 60+ confidence
                        for (int loopForward = twoItemsAway; loopForward < Colors.Count; loopForward++)
                        {
                            Colors[loopForward].Name = Colors[twoItemsAway].Name;
                        }

                        endComplete = true;
                    }
                }
            }
        }

        // Fill in the middle values.

        for (int i = 2; i < Colors.Count - 2; i++)
        {
            if (Colors[i].ResultConfidence < 60)
            {
                int twoLeft = i - 2;
                int oneLeft = i - 1;
                int oneRight = i + 1;
                int twoRight = i + 2;

                if (Colors[twoLeft].Name == Colors[oneLeft].Name && Colors[oneLeft].Name == Colors[oneRight].Name && Colors[oneRight].Name == Colors[twoRight].Name
                    &&
                    Colors[twoLeft].ResultConfidence > 60 && Colors[oneLeft].ResultConfidence > 60 && Colors[oneRight].ResultConfidence > 60 && Colors[twoRight].ResultConfidence > 60)
                {
                    Colors[i].Name = Colors[oneRight].Name;
                }

            }
        }

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM