选择第n个重复行后的下一行

时间:2011-05-29 22:07:06

标签: c# linq

我正在制作一个概率游戏,以证明掠夺硬币没有记忆。换句话说,如果我连续翻转了2个头,则下一个翻盖具有相同的成为tailes或head的概率。我正在生成随机数。例如,1代表尾部,0代表头部。我将结果记录在列表中。因此该列表包含1和0。例如,如何执行选择以仅在连续3个头或尾之后获取行。换句话说,如果我有列表:

0,1,0,1,1,0,0,0,1,0,1,1,1,1

我想得到1,1

因为有三个0重复而下一个数字是1.而下一个1是因为连续3个1之后的数字是1


我知道我可以执行选择迭代循环并在内部有一个计数器,每次有重复时计数器递增但我想知道用linq查询是否可行吗

3 个答案:

答案 0 :(得分:3)

此问题类似于其他问题Can I use LINQ to retrieve only "on change" values?

Thomas Petricek的回答建议在GroupBy扩展方法上创建覆盖。该技术应该为您提供您想要的功能,并且它可以创建在您的键断点上执行聚合功能的功能。

托马斯一定非常着迷于这个问题,因为他撰写了一篇关于创建自定义GroupBy的深度文章,用于对相邻键值进行分组。以下是该文章:http://tomasp.net/blog/custom-linq-grouping.aspx

如果您使用本文中建议的技术,那么您可以通过一种很好的方式来提取重复的行。

int[] coinTossResults = {0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1};
var tails = (from x in coinTossResults.WithAdjacentGrouping()
             group x by x into g
             where g.Count() > 1 && g.Key == 1
             select g);

WithAdjacentGrouping创建IAdjacentGroup类型,该类型具有GroupBy的覆盖。

答案 1 :(得分:1)

我不知道怎么做。至少解决方案并不明显,例如,如果你这样做,

    int[] integers = new int[] {0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1};

    var values = integers.Select(x => 
        {
            return x == 1;
        });

您到了编写选择谓词的位置,并且您意识到您一次只处理列表中的一个项目。似乎没有办法确定前三个项目是什么。如果可以解决这个问题,那么就有可能。

然后你最终得到这样的东西,

int[] integers = new int[] {0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1};

List<int> results = new List<int>();

for (int pos = 0; pos < integers.Length; pos++)
{
    if (pos > 2)
    {
        if (integers[pos - 1] == integers[pos - 2] && integers[pos - 2] == integers[pos - 3])
        {
            results.Add(integers[pos]);
        }
    }
}

这不一定是你想要的,但需要考虑的事情。

答案 2 :(得分:1)

任何类型的运行计数算法都需要多个状态变量:

  • 当前值
  • 以前的值
  • 当前运行次数

最接近你的是Enumerable.Aggregate,因为它既为您提供了当前值,也为您提供了一些您选择输出的自定义每项值。在我们的例子中,它可能是以前的值。

使用简单的int[],您必须为current run count设置一些额外的状态。这必须放在Linq查询语句之外。虽然它可以工作,但它与使用for循环并没有太大的不同。

如果您改为将枚举修改为自定义结构,则可以修改该结构以包含运行计数,并且您可以执行(某种程度上)更符合linq的操作:

class Program
{
    class CoinToss
    {
        public int Value;
        public int RunCount;
    }

    static void Main(string[] args)
    {
        int[] values = new int[]
        {
            0, 1, 0, 1, 1, 0, 0, 0, 1, 0,
            1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1
        };

        var coinTosses = values
            .Select(v => new CoinToss() { Value = v, RunCount = 1 })
            .ToList();

        coinTosses.Aggregate(
            (previous, current) =>
            {
                current.RunCount = current.Value == previous.Value
                    ? previous.RunCount + 1
                    : 1;
                return current;
            });

        foreach (var coinToss in coinTosses)
        {
            Console.WriteLine("Value: {0}, Run Count: {1}",
                coinToss.Value,
                coinToss.RunCount);
        }
    }
}

请注意,Linq操作产生副作用有点奇怪,所以这一切都取决于你想要的纯度......

在此之后,您只需选择:

coinTosses.Where(coinToss => coinToss.RunCount >= 3);

不幸的是,您无法链接Aggregate函数,因此您必须构建整个列表才能使其正常工作。如果这是一个问题,您应该只使用一个循环而不是yield return,因为您的要求超出了“查询”。