#c# #linq #data-structures
Вопрос:
мой вопрос: Как я могу сравнить два массива и выполнить действие над элементами, которые находятся в обоих ? Я использую C# / LINQ
Что я пытаюсь сделать: зацикливаюсь на множестве пользователей. Другой массив, содержащий правила для некоторых / определенных пользователей. Поэтому для каждого пользователя, у которого есть правило в массиве правил, увеличьте поле в объекте пользователя.
Я уже пробовал использовать Linq:
var array1 = context.SomeSecret.ToArray();
var array2 = anotherContext.AnotherSecret.ToArray();
(from rule in array2
from user in array1
where user.ID = rule.ID
select user).ToObserveable().Subscribe<User>(x => x.MaxRules );
Что я пытаюсь сделать: зацикливаюсь на множестве пользователей. Другой массив, содержащий правила для некоторых / определенных пользователей. Поэтому для каждого пользователя, у которого есть правило в массиве правил, обновите поле в объекте пользователя.
Это был исходный код:
var userDic = context.SomeSecret.ToDictionary(u => u.ID);
var rules = anotherContext.AnotherSecret.ToList();
foreach(var rule in rules)
{
if(userDic.ContainsKey(rule.UserID))
{
userDic[rule.UserID] ;
}
}
user.ID
и rule.UserID
они одинаковы.
Примечание:
Это «бессмысленный» код
Есть ли какой-нибудь «элегантный» способ решить эту проблему ?
Заранее спасибо.
Комментарии:
1. Не могли бы вы привести пример, чего вы пытаетесь достичь?
2. Когда вы говорите, что хотите сравнить 2 массива и выполнить действие, вы имеете в виду, что просто хотите извлечь элементы из 1 массива, соответствующие идентификаторам в другом массиве?
3.
ToObservable
это не ЛИНК. Вы можете найти общие элементы между любыми двумя перечисляемыми с помощью Перечисляемого. Пересекаются , напримерsomeSecrets.Intersect(otherSecrets)
. Что такоеcontext
иanotherContext
хотя? Вы не можете просто использоватьIntersect
с SQL4. Если бы у вас действительно было два массива с объектами, реализующими равенство, вы могли бы просто написать
var common=someArray.Intersect(otherArray);
5. @PanagiotisKanavos
context
иanotherContext
являются 2-мя БД-текстами
Ответ №1:
Вы пытаетесь сделать слишком много в нескольких утверждениях. Это делает ваш код трудным для чтения, трудным для повторного использования, трудным для изменения и трудным для модульного тестирования. Подумайте о том, чтобы взять за привычку создавать небольшие многоразовые методы.
IEnumerable<Secret> GetSecrets() {...}
IEnumerable<Secret> GetOtherSecrets() {...}
Как я могу сравнить два массива и выполнить действие над элементами, которые находятся в обоих?
LINQ может извлекать данные только из ваших исходных данных. LINQ не может изменить исходные данные. Чтобы изменить исходные данные, вам следует перечислить данные, извлеченные с помощью LINQ. Обычно это делается с помощью foreach
.
Итак , у вас есть две последовательности Secrets
, и вы хотите извлечь все Secrets
, что есть в обеих последовательностях.
Определить равенство
Прежде всего, вам нужно указать: когда является секретом в обеих последовательностях:
Secret a = new Secret();
Secret b = a;
Secret c = (Secret)a.Clone();
Ясно, что a и b относятся к одному и тому же объекту. Хотя значения всех свойств и полей в секретах a и c одинаковы, это разные экземпляры.
The effect is, that if you change the value of one of the properties of Secret a, then the value is also changed in Secret b. However, Secret C remains unchanged.
Secret d = new Secret();
Secret e = new Secret();
IEnumerable<Secret> array1 = new Secret[] {a, d};
IEnumerable<Secret> array2 = new Secret[] {a, b, c, e};
It is clear that you want a
in your end result. You also want b
, because a and b refer to the same object. It is also clear that you don’t want d, nor e in your end result. But are in your opinion a
and c
equal?
Another ambiguity in your requirements:
IEnumerable<Secret> array1 = new Secret[] {a};
IEnumerable<Secret> array2 = new Secret[] {a, a, a, a, a};
How many times do you want a in your end result?
Сопоставители равенства
По умолчанию a и c являются разными объектами, a == c
выходами false
.
Однако, если вы хотите определить их равными, вам нужно сказать в своем LINQ: не используйте стандартное определение равенства, используйте мое определение равенства.
Для этого нам нужно написать Компаратор равенства. Или, если быть более точным: создайте объект класса, который реализует IEqualityComparer<Secret>
.
К счастью, обычно это довольно просто.
Определение: Два объекта типа Secret равны, если все свойства возвращают одно и то же значение.
class SecretComparer : EqualityComparer<Secret>
{
public static IEqualityComparer<Secret> ByValue {get;} = new SecretComparer();
public override bool Equals (Secret x, Secret y)
{
... // TODO: implement
}
public override int GetHashCode (Secret x)
{
... // TODO: implement
}
Реализация ниже
Причина , по которой я производен от класса EqualityComparer<Secret>
, а не просто реализую IEqualityComparer<Secret>
, заключается в том, что класс EqualityComparer также предоставляет мне свойство Default
, которое может быть полезно, если вы хотите использовать определение по умолчанию при сравнении двух секретов.
LINQ: получить объекты, которые находятся в двух последовательностях
Как только у вас появится компаратор равенства, LINQ будет простым. Чтобы извлечь Секреты, которые находятся как в x, так и в y, я использую перегрузку Перечисляемого.Пересечение, в котором используется компаратор равенства:
IEnumerable<Secret> ExtractDuplicateSecrets(IEnumerable<Secret> x, IEnumerable<Secret> y)
{
return x.Intersect(y, SecretComparer.ByValue);
}
Это все. Чтобы выполнить действие над каждым оставшимся секретом, используйте foreach:
void PerformSecretAction(IEnumerable<Secret> secrets)
{
foreach (Secret secret in secrets)
{
secret.Process();
}
}
Итак, ваш полный код:
IEnumerable<Secret> x = GetSecrets();
IEnumerable<Secret> y = GetOtherSecrets();
IEnumerable<Secret> secretsInXandY = ExtractDuplicateSecrets(x, y);
PerformSecretAction(secretsInXandY);
Или, если вы хотите сделать это в одном заявлении. Не уверен, что это улучшает читабельность:
PerformSecretAction(ExtractDuplicateSecrets(GetSecrets(), GetOtherSecrets());
Хорошая вещь в создании небольших методов: создание x и y, разделителя секретов, извлечение общих секретов и выполнение действий со всеми оставшимися секретами, заключается в том, что большинство процедур будут довольно небольшими, поэтому их легко прочитать. Кроме того, все процедуры могут быть повторно использованы для других целей. Вы можете легко изменить их (другое определение равенства: просто напишите другой компаратор!) и легко провести модульный тест.
Реализовать Секретное Равенство
public override bool Equals (Secret x, Secret y)
{
// almost all equality comparers start with the following lines:
if (x == null) return y == null; // True if x and y both null
if (y == null) return false; // because x not null
if (Object.ReferenceEquals(x, y) return true; // same object
Большую часть времени мы часто не хотим, чтобы разные производные классы были равны: поэтому a TopSecret
(производный от Secret) не равен a Secret
.
if (x.GetType() != y.GetType()) return false;
The rest depends on your definition of when two Secrets are equal. Most of the time you check all properties. Sometimes you only check a subsection.
return x.Id == y.Id
amp;amp; x.Description == y.Description
amp;amp; x.Date == y.Date
amp;amp; ...
Here you can see that the code depends on your definition of equality. Maybe the Description check is case insensitive:
private static IEqualityComparer<string> descriptionComparer {get;}
= StringComparer.CurrentCultureIgnoreCase;
return x.Id == y.Id
amp;amp; descriptionComparer.Equals(x.Description, y.Description)
amp;amp; ...
Реализовать GetHashCode
Этот метод в основном используется для быстрого определения того, что два объекта не равны. Хороший хэш-код работает быстро и отбрасывает большинство неравных объектов.
Существует только одно требование: если x и y считаются равными, они должны возвращать один и тот же хэш-код. Не наоборот: разные объекты могут иметь один и тот же хэш-код, хотя было бы лучше, если бы они имели разные хэш-коды.
Как насчет этого:
public override int GetHashCode (Secret x)
{
if (x == null)
return 8744523; // just a number;
else
return x.Id.GetHashCode(); // only check Id
}
В приведенном выше коде я предполагаю, что идентификатор секрета довольно уникален. Вероятно, только при обновлении секрета вы найдете два неравных секрета с одинаковым идентификатором:
Secret existingSecret = this.FindSecretById(42);
Secret secretToEdit = (Secret)existingSecret.Clone();
secretToEdit.Description = this.ReadNewDescription();
Теперь существующие Secret и secretToEdit имеют одинаковое значение Id
, но другое описание. Следовательно, они не равны. Тем не менее, у них один и тот же хэш-код.
Тем не менее, безусловно, большинство секретов будут иметь уникальный идентификатор, GetHashCode будет очень быстрым методом определения того, что два секрета разные.