简体   繁体   English

IEnumerable、Where 和 Object.ReferenceEquals 的问题

[英]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.Startremaining的过滤似乎不起作用。

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.

相关问题 object.ReferenceEquals或==运算符? - object.ReferenceEquals or == operator? Object.ReferenceEquals永远不会命中 - Object.ReferenceEquals never hit Assert.ReferenceEquals()在Visual Studio Test中Object.ReferenceEquals()返回&#39;false&#39;的位置 - Assert.ReferenceEquals() Passes where Object.ReferenceEquals() returns 'false' in Visual Studio Test Object.ReferenceEquals对于匹配的字符串返回true - Object.ReferenceEquals returns true for matching strings Object.ReferenceEquals为两个不同的对象输出true - Object.ReferenceEquals prints true for two different objects &#39;Object.ReferenceEquals&#39;始终为false,因为它使用值类型进行调用 - 'Object.ReferenceEquals' is always false because it is called with a value type c#中Object.Equals(object,object)和Object.ReferenceEquals(object,object)之间的区别 - difference between Object.Equals(object,object) and Object.ReferenceEquals(object,object) in c# C#operator ==,StringBuilder.Equals,Object.Equals和Object.ReferenceEquals之间的差异 - C# Differences between operator ==, StringBuilder.Equals, Object.Equals and Object.ReferenceEquals 为什么我要在Equals覆盖中执行object.ReferenceEquals(null,this)? - Why would I ever want to do object.ReferenceEquals(null, this) in Equals override? 参考平等性能差异? ((object)obj1 ==(object)obj2)vs。object.ReferenceEquals(obj1,obj2) - Reference equality performance difference? ((object)obj1 == (object)obj2) vs. object.ReferenceEquals( obj1, obj2 )
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM