linq 在C#中比较两个不规则数组的“except”[duplicate]

oogrdqng  于 2022-12-06  发布在  C#
关注(0)|答案(2)|浏览(100)

此问题在此处已有答案

How to get a distinct result for list of array?(4个答案)
上个月关门了。
我试图找到两个数组中的成员,它们出现在一个数组中,但在另一个数组中却没有。我读过this article,我想虽然我理解了它,但我做错了一些事情。
假设以下简单代码:

using System;
using System.Linq;
using System.Collections.Generic;

public class Program
{
    public static void Main()
    {
        int[][] currentLocation = new int[][]
                {
                    new int[] { 11, 10 },
                    new int[] { 11, 11 },
                    new int[] { 11, 12 },
                    new int[] { 11, 13 },
                };
        int[][] proposedLocation = new int[][]
                {
                    new int[] { 11, 12 },
                    new int[] { 11, 13 },
                    new int[] { 11, 14 },
                    new int[] { 11, 15 },
                };
        
        IEnumerable<int[]> onlyInFirstSet = proposedLocation.Except(currentLocation);
        foreach (int[] number in onlyInFirstSet)
            Console.WriteLine(number[0].ToString() + ","+number[1].ToString());
    }
}

现在我希望/想到/期望看到的是:

11,14
11,15

......因为在proposedLocation中,这些是唯一没有出现在currentLocation中的,但是我实际上得到了:

11,12
11,13
11,14
11,15

(i.e.整个proposedLocation

**问题:**如何提取只出现在proposedLocation中的值?如果我能以某种方式保留int[][]而不是string的奇数位,我将是有益的,但这是一个额外的好处。

pb3s4cty

pb3s4cty1#

Using an int[] { 11, 10} for a location isn't sufficient for a few reasons.

  • First, all arrays have reference semantics, which means an equality check does not check values but for reference equality (if they are the same object in memory). This means the following check produces false.
int[] a = new[] { 1,2 };
int[] b = new[] { 1,2 };
if( a == b || a.Equals(b) ) 
{
    // this will never happen
}
  • Second, there is enforcement of two coordinates for each location. Each row in a jagged array can have different number of columns. This makes for some awkward coding to be thorough in your code.
  • Third, the values of an array can be changed at any time, without you having a way to prevent this. Consider the code
int[] a = new[] { 1, 2};
a[0] = 10;

This can cause bugs as locations you think have specific values, due to a programming error, can easily change locations.

  • Lastly, if you want to display the values of a location, you can't just call Console.WriteLine(array) because it won't display the values, and thus you need a loop, or use string.Join() to create a displayable array.

Location as a struct (X,Y)

All of the above, combined, call for a different approach. I suggest using a structure that can store two values (just as the array), only two values, no more or less, can check for equality with other locations, and finally has a built-in way to convert to a string for display.

public readonly struct Location : IEquatable<Location>
{
    public Location(int x, int y) : this()
    {
        X = x;
        Y = y;
    }
    public int X { get; }
    public int Y { get; }

    public override string ToString() => $"({X},{Y})";

    #region IEquatable Members

    /// <summary>
    /// Equality overrides from <see cref="System.Object"/>
    /// </summary>
    /// <param name="obj">The object to compare this with</param>
    /// <returns>False if object is a different type, otherwise it calls <code>Equals(Location)</code></returns>
    public override bool Equals(object obj)
    {
        if (obj is Location item)
        {
            return Equals(item);
        }
        return false;
    }

    /// <summary>
    /// Checks for equality among <see cref="Location"/> classes
    /// </summary>
    /// <returns>True if equal</returns>
    public bool Equals(Location other)
    {
        return this.X == other.X && this.Y == other.Y;
    }
    /// <summary>
    /// Calculates the hash code for the <see cref="Location"/>
    /// </summary>
    /// <returns>The int hash value</returns>
    public override int GetHashCode()
    {
            unchecked
            {
                int hc = -1817952719;
                hc = (-1521134295) * hc + this.X.GetHashCode();
                hc = (-1521134295) * hc + this.Y.GetHashCode();
                return hc;
            }                
    }
    public static bool operator ==(Location target, Location other) { return target.Equals(other); }
    public static bool operator !=(Location target, Location other) { return !target.Equals(other); }

    #endregion
}

Some key features of this structure are

  • X , Y properties hold the values and are unchanging. A location is defined by its (x,y) values and they go in pairs together. The values are set at the constructor and are readonly.
  • Equals(Location other) check for value equality (both x and y).
  • ToString() automatically converts the structure to a string when needed. This allows calls like Console.WriteLine(location) to produce the results (11,14) for example.
  • LINQ operations automatically take advantage of Equals() function as we forward the generic Equals(object) to the specific Equals(Location) function.
  • The GetHashCode() function you can ignore for now. It is there because it is required by certain collections, and it guarantees that if two locations are different, that they will have different hash-codes.

Program

Now to test for the use case described in the question

class Program
{
    static void Main(string[] args)
    {
        Location[] currentLocation = new Location[]
        {
            new Location( 11, 10 ),
            new Location( 11, 11 ),
            new Location( 11, 12 ),
            new Location( 11, 13 ),
        };
        Location[] proposedLocation = new Location[]
        {
            new Location( 11, 12 ),
            new Location( 11, 13 ),
            new Location( 11, 14 ),
            new Location( 11, 15 ),
        };

        var list = Enumerable.Except(proposedLocation, currentLocation).ToArray();
        foreach (var item in list)
        {
            Console.WriteLine(item);
        }
    }
}

with output as expected:

(11,14)
(11,15)

Note that due to the operation of Except() and the definition provided, you need to subtract the fist list from the second list, and that is way I am calling .Except(proposedLocation, currentLocation) and not the other way around.

Location as a tuple (int,int)

You can achieve the same result, by using a Tuple of two int values. Actually a ValueTuple<int,int> would have the exact same behavior as our custom structure Location above.
The code below has the exact same result as before, does not require a custom structure, and keeps type safety , as opposed to a solution that casts locations as object .

class Program
{
    static void Main(string[] args)
    {
        var currentLocation = new List<(int X, int Y)>()            
        {
            ( 11, 10 ),
            ( 11, 11 ),
            ( 11, 12 ),
            ( 11, 13 ),
        };
        var proposedLocation = new List<(int X, int Y)>()
        {
            ( 11, 12 ),
            ( 11, 13 ),
            ( 11, 14 ),
            ( 11, 15 ),
        };

        var list = Enumerable.Except(proposedLocation, currentLocation).ToArray();
        foreach (var item in list)
        {
            Console.WriteLine(item);
        }
        // Output:
        // (11,14)
        // (11,15)
    }
}
ecfsfe2w

ecfsfe2w2#

同意-MySkullCaveIsADarkPlace。但是,如果您想使用解决方法来达成此目的,请参阅下列。
请使用对象,而不要使用不规则数组。

var currentLocation = new object[]
                        {
                        new  { a = 11, b = 10 },
                        new  { a = 11, b = 11 },
                        new  { a = 11, b = 12 },
                        new  { a = 11, b = 13 }
                        };
    
                var proposedLocation = new object[]
                        {
                        new  { a = 11, b = 12 },
                        new  { a = 11, b = 13 },
                        new  { a = 11, b = 14 },
                        new  { a = 11, b = 15 }
                        };
    
                var a = proposedLocation.Except(currentLocation);

相关问题