C# 列表<;IEnumerator>;。All(e=>;e.MoveNext())不';别把我的统计员挪动
我正在努力追踪代码中的一个bug。我把它归结为下面的片段。在下面的示例中,我有一个整数网格(行列表),但我想找到具有1的列的索引。其实现是通过使枚举数保持同步,为每一行和每一列依次创建一个枚举数C# 列表<;IEnumerator>;。All(e=>;e.MoveNext())不';别把我的统计员挪动,c#,linq,enumerator,C#,Linq,Enumerator,我正在努力追踪代码中的一个bug。我把它归结为下面的片段。在下面的示例中,我有一个整数网格(行列表),但我想找到具有1的列的索引。其实现是通过使枚举数保持同步,为每一行和每一列依次创建一个枚举数 class Program { static void Main(string[] args) { var ints = new List<List<int>> { new List<int> {0, 0, 1}
class Program
{
static void Main(string[] args)
{
var ints = new List<List<int>> {
new List<int> {0, 0, 1}, // This row has a 1 at index 2
new List<int> {0, 1, 0}, // This row has a 1 at index 1
new List<int> {0, 0, 1} // This row also has a 1 at index 2
};
var result = IndexesWhereThereIsOneInTheColumn(ints);
Console.WriteLine(string.Join(", ", result)); // Expected: "1, 2"
Console.ReadKey();
}
private static IEnumerable<int> IndexesWhereThereIsOneInTheColumn(
IEnumerable<List<int>> myIntsGrid)
{
var enumerators = myIntsGrid.Select(c => c.GetEnumerator()).ToList();
short i = 0;
while (enumerators.All(e => e.MoveNext())) {
if (enumerators.Any(e => e.Current == 1))
yield return i;
i++;
if (i > 1000)
throw new Exception("You have gone too far!!!");
}
}
}
所以这只是列表的问题吗?或者,您可以使用lambda扩展来完成
var ids = Enumerable.Range(0,ints.Max (row => row.Count)).
Where(col => ints.Any(row => (row.Count>col)? row[col] == (1) : false));
或
这是因为
列表
的枚举数是结构
,而数组
的枚举数是类
因此,当您使用结构调用Enumerable.All
时,将创建枚举数的副本,并将其作为参数传递给Func
,因为结构是按值复制的。因此,对副本而不是原始文件调用e.MoveNext
试试这个:
Console.WriteLine(new List<int>().GetEnumerator().GetType().IsValueType);
Console.WriteLine(new int[]{}.GetEnumerator().GetType().IsValueType);
如前所述,问题是由于缺少装箱,并且意外地列表枚举器实现是一个结构
,而不是引用类型。通常,不会期望枚举数的值类型行为,因为大多数是由IEnumerator
/IEnumerator
接口公开的,或者是引用类型本身。一个快速的方法是改变这条线
var enumerators = myIntsGrid.Select(c => c.GetEnumerator()).ToList();
到
相反
上面的代码将构造一个已经装箱的枚举数列表,由于接口转换,这些枚举数将被视为引用类型实例。从那一刻起,它们的行为应该与您在以后的代码中所期望的一样
如果需要泛型枚举器(以避免后者使用
枚举器.Current
属性时强制转换),可以强制转换到相应的泛型IEnumerator
界面:
c => (IEnumerator<int>) c.GetEnumerator()
c=>(IEnumerator)c.GetEnumerator()
甚至更好
c => c.GetEnumerator() as IEnumerator<int>
c=>c.GetEnumerator()作为IEnumerator
据说,
as
关键字的性能比直接强制转换好得多,在循环的情况下,它可以带来基本的性能优势。请注意,如果根据以下注释进行的强制转换失败,as
将返回null
。在OP的情况下,可以保证枚举器实现IEnumerator
,因此可以安全地进行as
转换。为什么不能像这样获取这些索引:
var result = ints.Select (i => i.IndexOf(1)).Distinct().OrderBy(i => i);
似乎要容易得多…下面是一个使用循环和
yield
的简单实现:
private static IEnumerable<int> IndexesWhereThereIsOneInTheColumn(
IEnumerable<List<int>> myIntsGrid)
{
for (int i=0; myIntsGrid.Max(l=>l.Count) > i;i++)
{
foreach(var row in myIntsGrid)
{
if (row.Count > i && row[i] == 1)
{
yield return i;
break;
}
}
}
}
为了好玩,这里有一个简洁的LINQ查询,它不会在代码中造成难以跟踪的副作用:
IEnumerable<int> IndexesWhereThereIsOneInTheColumn(IEnumerable<IEnumerable<int>> myIntsGrid)
{
return myIntsGrid
// Collapse the rows into a single row of the maximum value of all rows
.Aggregate((acc, x) => acc.Zip(x, Math.Max))
// Enumerate the row
.Select((Value,Index) => new { Value, Index })
.Where(x => x.Value == 1)
.Select(x => x.Index);
}
IEnumerable Indexes(IEnumerable myIntsGrid)不在列中
{
返回myIntsGrid
//将行折叠为所有行的最大值的单行
.Aggregate((acc,x)=>acc.Zip(x,数学最大值))
//列举该行
.Select((值,索引)=>new{Value,Index})
.其中(x=>x.值==1)
.选择(x=>x.Index);
}
为什么不简单地使用foreach
循环?@Rik使用foreach
我找不到一个好看的实现。我的第一次尝试中有两个break
s,我认为Linq实现更干净。传递给Linq的函数不应该有副作用。令人发狂!我只是花了一个小时在这个问题上,试图说服自己我不是疯了…这对这个例子来说是可行的,但我想减少我发布的代码量。我的实际错误更复杂,但我发布的代码说明了问题的症结所在。它返回包含1的行的索引,我需要列。啊哈,我误解了。编辑过。挑剔:使用(IEnumerator)
而不是(IEnumerator)
,这样你就不必在循环时在中施放e.Current
。@DominicKexel,很好,我会把它作为一个编辑。谢谢,这不是因为拳击。OP的代码变异了一个副本,但不是盒装副本。@IvayloSlavov:可能需要注意的是,foo as Bar
在强制转换失败时返回null,而(Bar)foo
抛出异常。它们在各自的方面都很有用,这取决于你想如何处理错误的铸造:)@IvayloSlavov:是的,我认为这与本案无关。但是有人可以从你的帖子中得出结论,fooasbar总是更好。我提到的是那些人:)这不是因为拳击。OP的代码变异了一个副本,但不是一个盒装副本。@CodesIn混乱很好,修改了我的答案。看看这是否有意义:)这是我读这个问题时的第一个想法,但我无法想象枚举数有时会被实现为值类型。很容易出错。@Mene:想象一下;这是真的!它容易出错,但请记住,99.9999%的时间只有为foreach
循环生成的代码才能看到枚举数;创建自己的未绑定枚举数是一种罕见的情况。设计团队认为,避免收集压力的性能胜利是值得的,这种情况非常罕见,代码混乱。解决了发布(减少)问题,但我并没有在网格中实际使用ints来解决真正的问题。但是,我可能可以创建一个聚合器,将我想要的值冒泡到顶部。当然,您只需要将Math.Max
更改为一个函数,该函数可以拾取您选择的值,然后显然还需要Where
操作符
c => c.GetEnumerator() as IEnumerator<int>
var result = ints.Select (i => i.IndexOf(1)).Distinct().OrderBy(i => i);
private static IEnumerable<int> IndexesWhereThereIsOneInTheColumn(
IEnumerable<List<int>> myIntsGrid)
{
for (int i=0; myIntsGrid.Max(l=>l.Count) > i;i++)
{
foreach(var row in myIntsGrid)
{
if (row.Count > i && row[i] == 1)
{
yield return i;
break;
}
}
}
}
if (myIntsGrid.Any(row => row.Count > i && row[i] == 1)) yield return i;
IEnumerable<int> IndexesWhereThereIsOneInTheColumn(IEnumerable<IEnumerable<int>> myIntsGrid)
{
return myIntsGrid
// Collapse the rows into a single row of the maximum value of all rows
.Aggregate((acc, x) => acc.Zip(x, Math.Max))
// Enumerate the row
.Select((Value,Index) => new { Value, Index })
.Where(x => x.Value == 1)
.Select(x => x.Index);
}