选择第n个重复行之后的下一行
本文关键字:一行 之后 选择 | 更新日期: 2023-09-27 17:57:52
我正在做一个概率游戏,以证明翻转硬币没有内存。换言之,如果我连续翻转了两个头部,下一个翻转有同样的概率是尾巴或头部。我正在生成随机数。例如,1表示尾部,0表示头部。我正在将结果记录在列表中。因此,该列表包含1和0。例如,如何执行选择以仅获得3个连续头或尾之后的行。换句话说,如果我有清单:
0,1,0,1,1,0,0,1,0,1,1,1,1
我想得到1,1
因为有三个0重复,下一个数字是1。下一个1是因为连续3个1之后的数字是1
我知道我可以在循环中执行选择迭代,并在里面有一个计数器,每次有重复的计数器增量,但我想知道用linq查询是否可以做到这一点
这个问题与另一个SO问题类似。我可以使用LINQ只检索吗;关于变化";价值观
Thomas Petricek的回答建议在GroupBy扩展方法上创建覆盖。该技术应该为您提供所需的内容,此外,它还可以在关键点中断时执行聚合函数。
Thomas一定非常关注这个问题,因为他写了一篇关于创建一个自定义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创建具有GroupBy覆盖的IAdjacentGroup类型。
我不知道该怎么做。至少解决方案并不明显,例如,如果你这样做,
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;
});
到了编写select谓词的地步,您意识到一次只处理列表中的一项。似乎没有办法确定前三项是什么。如果这个问题能够得到解决,那么这可能是可能的。
然后你会得到这样的东西,
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]);
}
}
}
这不一定是你想要的,而是需要思考的。
任何类型的运行计数算法都需要多个状态变量:
- 电流值
- 以前的值
- 当前运行计数
最接近实现目标的是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
,因为您的需求有点超出了"查询"。