如何使用 LINQ 比较顺序“邻居”的值在列表<>内?

发布于 2024-11-05 18:02:08 字数 2138 浏览 3 评论 0 原文

看一下这段代码:

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

2) 替换任何概率低于 60 且被四个邻居包围的项目都具有相同的值(“绿色”变为“蓝色”,因为 #2、#3、#5 和 #6 是蓝色且概率为 60+)

3)按顺序工作,替换列表末尾的任何项目<>前面有两个具有相同值的项目(与第一部分相同,但相反)。在样本数据中,#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 专家都可以分享最有效的实现吗?

Take a look at this code:

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

Using LINQ, how would you accomplish the following:

1) Working sequentially, replace all items at the start of the List<> that have a Probability lower than 60 when the first TWO items that follow with a probability higher than 60 have the same value ("Unknown" becomes "Blue" because #2 and #3 are Blue and have Probability of 60+)

2) Replace any item with a probability lower than 60 that's surrounded with FOUR neighbors have the same value ("Green" becomes "Blue" because #2, #3, #5 and #6 are Blue and have Probability of 60+)

3) Working sequentially, replace any items at the end of the List<> that are preceded by TWO items with the same value (same as the first part, but in reverse). In the sample data, nothing would happen to #9 since #7 would need to be "Blue" and would need 60+ Probability.

This is pretty easy with loops, but I am absolutely stumped on how to compare sequential "neighbors" in LINQ.

This was my original solution for part 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;
                    }
                }
            }
        }

Can any LINQ experts please share the most efficient implementation?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

墨洒年华 2024-11-12 18:02:08

这是第一个示例,可以帮助您继续。关键是使用 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];


        }
    }
}

Here's an example of the first one, to get you going. The key is using Enumerable.Range so you have the index. As has been said, it'd be much more readable with loops.

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


        }
    }
}
嘴硬脾气大 2024-11-12 18:02:08

我从第 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;第 1 部分,最后……第 3 部分除第一部分外的所有内容; ETC。

I started with a test for part 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");
}

and implementation

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

then added a test for part 3, since it is a variation of part 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");
}

and implementation:

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

then, I tackled part 2. Again I started with a test:

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

and this implementation:

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

This time it seemed cleaner to pull out a helper class:

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

Finally, I created a consolidated test with your data:

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

and an implementation that uses the methods created above:

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

Were this my code base I would go on to add tests around edge cases like: all items having probability < 60 for parts 1 and 3; all but last ... for part 1; all but first for part 3; etc.

樱娆 2024-11-12 18:02:08

这是一个基于收益的解决方案,我猜它不是严格意义上的 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);
      }
    }
  }
}

Here's a yield-based solution, which isn't strictly LINQ I guess but it's LINQ-like. Implemented quickly; I'm sure there's better ways to do this. Apologies (and +1) to George for stealing his test framework.

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);
      }
    }
  }
}
記柔刀 2024-11-12 18:02:08

首先,我认为规则 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;
    } 
}

First, I believe rules 1 and 3 are simply edge cases for rule 2. I would LINQify this starting with 2 extension methods:

  1. a method that returns, for each element, it's surrounding neighbours. Add two null values at the beginning and the end to handle the edge cases. I called this method WithSurroundingNeigbours
  2. a method that, from a list of 5 elements, returns whether the middle element should be excluded or not. I left this as an excercise :-)

Combined, this allows to write the resulting query like this:

       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;
    } 
}
柠栀 2024-11-12 18:02:08

如果有人感兴趣的话,这是一个使用循环的完整解决方案:

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

            }
        }

Here is a complete solution using loops, in case anyone is interested:

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

            }
        }
撩起发的微风 2024-11-12 18:02:08

您可以使用 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);
  } 

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

红绿

绿蓝

You can use linq to operate on sequential items quite neatly:

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

Notice that we skip the first element of the sequence, then zip the result with itself.
This gives:

redgreen

greenblue

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文