[英]Issue with IEnumerable, Where, and Object.ReferenceEquals
Trying to solve an Exercism problem (Dominoes).试图解决锻炼问题(多米诺骨牌)。 In this problem I am attempting to create a valid chain of dominoes such that numbers are touching each other and the first and last number are the same.
在这个问题中,我试图创建一个有效的多米诺骨牌链,使数字相互接触,并且第一个和最后一个数字相同。
Example: [1|2], [3,1], [3,2] -> [1|2][2|3][3|1]
示例:
[1|2], [3,1], [3,2] -> [1|2][2|3][3|1]
I am using brute force to try all combinations.我正在使用蛮力尝试所有组合。 To do so I start a chain with all possible combinations using the following code:
为此,我使用以下代码启动了一个包含所有可能组合的链:
using System;
using System.Collections.Generic;
using System.Linq;
public static class Dominoes
{
private class Domino
{
public readonly int First;
public readonly int Last;
public Domino(int first, int last)
{
First = first;
Last = last;
}
public Domino Flip() => new Domino(Last, First);
}
private class Chain
{
private readonly List<Domino> chained = new List<Domino>();
private readonly List<Domino> remaining = new List<Domino>();
private int First => chained[0].First;
private int Last => chained[chained.Count - 1].Last;
public bool Complete => remaining.Count == 0 && First == Last;
private Chain(Domino domino, IEnumerable<Domino> remaining, IEnumerable<Domino> chained = null)
{
if (chained != null)
{
this.chained.AddRange(chained);
}
this.chained.Add(domino);
this.remaining.AddRange(remaining);
}
public static IEnumerable<Chain> Start(IEnumerable<Domino> dominoes)
{
var chains = new List<Chain>();
foreach (var domino in dominoes)
{
var remaining = dominoes.Where(d => d != domino);
chains.Add(new Chain(domino, remaining));
chains.Add(new Chain(domino.Flip(), remaining));
}
return chains;
}
public IEnumerable<Chain> Extend()
{
var chains = new List<Chain>();
foreach (var domino in this.remaining)
{
var remaining = this.remaining.Where(d => d != domino);
if (domino.First == Last)
{
chains.Add(new Chain(domino, remaining, chained));
}
if (domino.Last == Last)
{
chains.Add(new Chain(domino.Flip(), remaining, chained));
}
}
return chains;
}
}
public static bool CanChain(IEnumerable<(int, int)> dominoes)
{
var chains = Chain.Start(dominoes.Select(d => new Domino(d.Item1, d.Item2)));
return chains.Any() ? Iterate(chains) : true;
}
private static bool Iterate(IEnumerable<Chain> chains)
{
var newChains = new List<Chain>();
foreach (var chain in chains)
{
if (chain.Complete) return true;
newChains.AddRange(chain.Extend());
}
if (newChains.Count == 0) return false;
return Iterate(newChains);
}
}
Test Code测试代码
using System;
using Xunit;
public class DominoesTests
{
[Fact]
public void Singleton_that_cant_be_chained()
{
var dominoes = new[] { (1, 2) };
Assert.False(Dominoes.CanChain(dominoes));
}
}
Run using dotnet test
使用
dotnet test
运行
The filtering for remaining
in Chain.Start
does not appear to work. Chain.Start
中remaining
的过滤似乎不起作用。
Example (using new [] { (1, 2) }
as input for CanChain
)示例(使用
new [] { (1, 2) }
作为CanChain
的输入)
# Expected: `Chain: [1|2] Remaining:`
# Actual: `Chain: [1|2] Remaining: [1|2]`
If I call .ToList()
on the dominoes.Select(d => new Domino(d))
in CanChain
it starts working.如果我在 CanChain 中的
dominoes.Select(d => new Domino(d))
上调用.ToList()
, CanChain
就会开始工作。
EDIT: added more information编辑:添加了更多信息
The problem is that the enumeration of Select
is not executed right away, but instead is delayed.问题是
Select
的枚举没有立即执行,而是延迟执行。 This means that each time you are accessing/using the IEnumerable<>
returned by the Select()
method you will get a new fresh list.这意味着每次您访问/使用
Select()
方法返回的IEnumerable<>
时,您都会得到一个新的列表。 But this also means you are creating new Domino
objects all the time.但这也意味着您一直在创建新的
Domino
对象。 And as you are not overwriting the Equals()
method each of these Domino
object will be different, even if they have the same values.并且由于您没有覆盖
Equals()
方法,因此每个Domino
object 都会有所不同,即使它们具有相同的值。
To show the problem check the following code:要显示问题,请检查以下代码:
private class Foobar {
private readonly int value;
public Foobar(int v) {
Console.WriteLine("## CONSTRUCTOR ## Foobar object created with value: "+v);
this.value = v;
}
public override string ToString() {
return "Foobar("+this.value+")";
}
}
public static void Main(string[] args) {
int[] numbers = new [] { 1, 5};
IEnumerable<Foobar> range = numbers.Select(i => new Foobar(i));
Console.WriteLine(range.Count());
foreach (Foobar entry in range) {
Console.WriteLine("Build an enumerable without "+entry);
IEnumerable<Foobar> remaining = range.Where(it => it != entry);
Console.WriteLine("The length of the remaining: "+remaining.Count());
foreach (Foobar remainingEntry in remaining) {
Console.WriteLine("Entry of remaining: "+remainingEntry);
}
}
}
When you run this code you will get the following output:当您运行此代码时,您将获得以下 output:
## CONSTRUCTOR ## Foobar object created with value: 1
## CONSTRUCTOR ## Foobar object created with value: 5
2
## CONSTRUCTOR ## Foobar object created with value: 1
Build an enumerable without Foobar(1)
## CONSTRUCTOR ## Foobar object created with value: 1
## CONSTRUCTOR ## Foobar object created with value: 5
The length of the remaining: 2
## CONSTRUCTOR ## Foobar object created with value: 1
Entry of remaining: Foobar(1)
## CONSTRUCTOR ## Foobar object created with value: 5
Entry of remaining: Foobar(5)
## CONSTRUCTOR ## Foobar object created with value: 5
Build an enumerable without Foobar(5)
## CONSTRUCTOR ## Foobar object created with value: 1
## CONSTRUCTOR ## Foobar object created with value: 5
The length of the remaining: 2
## CONSTRUCTOR ## Foobar object created with value: 1
Entry of remaining: Foobar(1)
## CONSTRUCTOR ## Foobar object created with value: 5
Entry of remaining: Foobar(5)
As you see the constructor is called way too often .如您所见,构造函数的调用过于频繁。 Each time someone use the
IEnumerable
from the Select()
call it will generate the enumerable list again so it doesn't interfere with other parallel running iterations on the same data.每次有人使用
Select()
调用中的IEnumerable
时,它都会再次生成可枚举列表,因此它不会干扰对同一数据进行的其他并行运行迭代。 However, since you create new objects inside your Select()
statement you will get new objects for each creation of the IEnumerable
of Select()
.但是,由于您在
Select()
语句中创建了新对象,因此每次创建Select()
的IEnumerable
都会获得新对象。
As you have already noticed, to solve the problem you use something like ToList()
and to evaluate/create the Domino
objects once.正如您已经注意到的那样,要解决问题,您可以使用
ToList()
之类的东西并评估/创建一次Domino
对象。 With one ToList()
call the output changes as follow:使用一个
ToList()
调用 output 更改如下:
## CONSTRUCTOR ## Foobar object created with value: 1
## CONSTRUCTOR ## Foobar object created with value: 5
2
Build an enumerable without Foobar(1)
The length of the remaining: 1
Entry of remaining: Foobar(5)
Build an enumerable without Foobar(5)
The length of the remaining: 1
Entry of remaining: Foobar(1)
You see, only two objects are created and the !=
will work as expected, since there are only these two objects.您会看到,只创建了两个对象,并且
!=
将按预期工作,因为只有这两个对象。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.