Я пытаюсь найти элементы двух массивов, где они появляются в одном, но не в другом. Прочитал эту статью и думаю, пока разбираюсь, что-то делаю не так.
Предположим, следующий простой код:
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
(т.е. весь proposedLocation)
Вопрос: как извлечь значения, которые появляются только в proposedLocation? Было бы полезно, если бы я мог каким-то образом сохранить int[][], а не нечетную часть string, но это бонус.
@MySkullCaveIsADarkPlace - спасибо, но, надо признать, это намного выше моего уровня знаний. :) [чтобы написать собственный компаратор равенства, который есть].
Обратите внимание, если значения внутреннего массива идут вместе, например, 11,10 отличаются от 11,11, например координаты, используйте структуру со значениями x/y вместо внутреннего массива.





Согласен с -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);
Проголосовал за него, так как он работает, но пока не помечая его как «одобренный ответ», поскольку это обходной путь. Спасибо :) -- (если больше никто не ответит, я позже отмечу это как АА.)
Использование массива object крайне не рекомендуется в C#. Вы можете добиться того же, используя ValueTuple вместо анонимных типов BTW.
Использование int[] { 11, 10} для местоположения недостаточно по нескольким причинам.
Во-первых, все массивы имеют семантику ссылок, что означает, что проверка на равенство проверяет не значения, а ссылочное равенство (если они являются одним и тем же объектом в памяти). Это означает, что следующая проверка дает false.
int[] a = new[] { 1,2 };
int[] b = new[] { 1,2 };
if ( a == b || a.Equals(b) )
{
// this will never happen
}
Во-вторых, существует принудительное использование двух координат для каждого местоположения. Каждая строка зубчатого массива может иметь разное количество столбцов. Это делает неудобным кодирование, которое должно быть тщательным в вашем коде.
В-третьих, значения массива могут быть изменены в любое время, и у вас нет способа предотвратить это. Рассмотрим код
int[] a = new[] { 1, 2};
a[0] = 10;
Это может вызвать ошибки, поскольку местоположения, которые, по вашему мнению, имеют определенные значения из-за ошибки программирования, могут легко измениться.
Наконец, если вы хотите отобразить значения местоположения, вы не можете просто вызвать Console.WriteLine(array), потому что он не будет отображать значения, и поэтому вам нужен цикл или используйте string.Join() для создания отображаемого массива.
Все вышеперечисленное в совокупности требует другого подхода. Я предлагаю использовать структуру, которая может хранить два значения (так же, как массив), только два значения, не более и не менее, могут проверяться на равенство с другими местоположениями и, наконец, имеют встроенный способ преобразования в строку для отображения.
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
}
Некоторыми ключевыми особенностями этой структуры являются
X, Y свойства сохраняют значения и не изменяются. Местоположение определяется его значениями (x, y), и они идут парами вместе. Значения устанавливаются в конструкторе и доступны только для чтения.
Equals(Location other) проверка на равенство значений (и x, и y).
ToString() автоматически преобразует структуру в строку, когда это необходимо. Это позволяет, например, таким вызовам, как Console.WriteLine(location), давать результаты (11,14).
Операции LINQ автоматически используют преимущества функции Equals(), поскольку мы пересылаем общую Equals(object) конкретной функции Equals(Location).
Функцию GetHashCode() пока можно игнорировать. Он существует, потому что он требуется для определенных коллекций, и он гарантирует, что если два местоположения будут разными, то они будут иметь разные хэш-коды.
Теперь, чтобы проверить вариант использования, описанный в вопросе
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);
}
}
}
с выводом, как и ожидалось:
(11,14)
(11,15)
Обратите внимание, что из-за действия Except() и предоставленного определения вам нужно вычесть первый список из второго списка, и именно так я вызываю .Except(proposedLocation, currentLocation), а не наоборот.
Вы можете добиться того же результата, используя Tuple из двух значений int. На самом деле ValueTuple<int,int> будет вести себя точно так же, как наша пользовательская структура Location выше.
Приведенный ниже код дает тот же результат, что и раньше, не требует пользовательской структуры и сохраняет безопасность типов, в отличие от решения, которое преобразует местоположения в 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)
}
}
Я бы предпочел синтаксический сахар (int X, int Y)ValueTuple<int, int>.
Обратите внимание на официальную документацию по Except. У него есть перегрузка, которая принимает
IEqualityComparer<T>. Вы можете создать собственный компаратор на равенство, который сравнивает на равенство содержимое массивов int (например, с помощью цикла или метода Linq SequenceEquals). Затем передайте свой компаратор равенства методу Except, и Боб должен быть вашим дядей. Если компаратор равенства работает к вашему удовлетворению, не стесняйтесь самостоятельно отвечать на вопрос... ;-)