#c# #linq
#c# #linq
Вопрос:
У меня есть следующий класс:
public class Relation {
public Int32 SourceId { get; set; }
public Int32 TargetId { get; set; }
}
И следующий список:
List<Relation> relations = service.GetRelations();
Мне нужно выбрать идентификаторы источников, которые связаны со ВСЕМИ целями.
Итак, учитывая следующий пример с парами (SourceId, TargetId)
:
(1, 1), (1, 2), (2, 1), (3, 2)
В этом случае TargetId
может быть 1 или 2.
И единственное, SourceId
что связано со всеми TargetIds (1, 2)
, это SourceId 1
.
SourceId 2
связано только с TargetId 1
и SourceId 3
связано только с TargetId 2
.
Как я могу это сделать?
Комментарии:
1. GroupBy ?
2. Но как я могу использовать GroupBy для получения идентификаторов источников, которые связаны со всеми TargetIds? Моей первой идеей было использовать GroupBy, но теперь я знаю, как это сделать… Возможно, я чего-то не хватает.
3. Вы говорите, что вам нужен ответ LINQ из-за использования EF, но
relations
это неIQueryable
, этоList<Relation>
так что EF не задействован.
Ответ №1:
Вам нужно собрать все возможные идентификаторы целей:
var input = new []
{
new Relation(1, 1),
new Relation(1, 2),
new Relation(2, 1),
new Relation(3, 2),
};
var allTargetId = input.Select(x => x.TargetId).Distinct().ToArray();
Затем сгруппируйте по идентификатору источника и в каждой группе убедитесь, что все члены группы, представленные в allTargetId
:
var result = input.GroupBy(x => x.SourceId, x => x.TargetId)
.Where(g => allTargetId.All(x => g.Contains(x)))
.Select(g => g.Key)
.ToArray();
Примечание: чтобы заставить этот код работать, я добавил конструктор в ваш Relation
класс, и он выглядит следующим образом
public class Relation
{
public Relation(int sourceId, int targetId)
{
SourceId = sourceId;
TargetId = targetId;
}
public Int32 SourceId { get; set; }
public Int32 TargetId { get; set; }
}
Редактировать
Для получения Relation
‘s вы можете использовать этот запрос:
var result = input.GroupBy(x => x.SourceId)
.Where(g => allTargetId.All(x => g.Select(y => y.TargetId).Contains(x)))
.SelectMany(g => g)
.ToArray();
Пожалуйста, обратите внимание, что я тестировал это только с linq2objects, поэтому я не уверен, как это будет переведено на SQL
Комментарии:
1. Возможно ли в конце получить отношение вместо SourceID?
2. @MiguelMoura для
Relation
этого совпадения есть —(1, 1), (1, 2)
. Какие из них вы хотите получить? Все?3. Я хотел бы получить оба, если это возможно, потому что SourceID = 1 связан со всеми TargetIds.
4. Я знаю, что пары (SourceID, TargetID) уникальны. Знание этого является необходимой частью. GroupBy(x => x.SourceID, x => x.TargetID)?
5. @MiguelMoura Я обновил свой ответ. Код в разделе «Редактировать» должен возвращать вам все отношения, которые удовлетворяют условию
Ответ №2:
Следующий код выполняет то, что вы просили. Это имеет форму модульного теста, поэтому вы можете проверять различные сценарии
[Fact]
public void FindSourcesThatTargetAll()
{
var list = new List<Relation>
{
new Relation(1, 1), new Relation(1, 2), new Relation(2, 1), new Relation(3, 2)
};
var allTargets = list.Select(x => x.TargetId).Distinct().OrderBy(x=>x).ToList();
var dict = list.GroupBy(x => x.SourceId).ToDictionary(x => x.Key,
grouping => grouping.Select(y => y.TargetId).Distinct().OrderBy(x=>x).ToList());
var sourcesThatTargetAll = dict.Where(x => x.Value.Count == allTargets.Count).Select(x => x.Key).ToList();
Assert.Single(sourcesThatTargetAll);
Assert.Equal(1, sourcesThatTargetAll.First());
}
В принципе, я сделал:
- Найдите все целевые объекты.
- Для каждого источника найдите все целевые объекты (важно различать) и сгруппируйте их по источнику в словаре (
dict
переменная) - Из приведенного выше словаря выберите все источники, которые соответствуют всем целевым объектам (количество в примере достаточно, но вы можете провести более сложное сравнение)
Ответ №3:
Простым способом добиться этого было бы сгруппировать записи по TargetId
, а затем найти пересечение всех SourceId
var groups = relations.GroupBy(r => r.TargetId).ToArray();
if (groups.Length > 0) {
var set = new HashSet<int>(groups[0]);
for (int i = 1; i < groups.Length; i)
set.IntersectWith(groups[i].Select(r => r.SourceId));
}
В конце этого set
будут содержаться все SourceId
элементы, которые связаны со всеми TargetId
элементами
Комментарии:
1. Мне нужно использовать только Linq в качестве запроса для entity framework
2. @MiguelMoura Тогда вам следует обновить вопрос и теги, чтобы отразить это.
Ответ №4:
public class Relation
{
public Int32 SourceId { get; set; }
public Int32 TargetId { get; set; }
}
public Int32?[] FindRelation(Relation[] relations)
{
List<Int32?> sourceIds = new List<int?>;
var countOfTargets = relations.Select(x => x.TargetId).Distinct().Count();
var relationsGroupedBySource = relations.GroupBy(x => x.SourceId);
foreach (var group in relationsGroupedBySource)
{
var distinctGroup = group.Distinct();
if (distinctGroup.Count() == countOfTargets)
{
sourceIds.Add(distinctGroup.Select(x => x.SourceId).First());
}
}
return sourceIds.ToArray();
}
public void Test()
{
Relation[] relations = {
new Relation() { SourceId = 1, TargetId = 1 },
new Relation() { SourceId = 1, TargetId = 2 },
new Relation() { SourceId = 2, TargetId = 1 },
new Relation() { SourceId = 3, TargetId = 2 }
};
var sourceIds = FindRelation(relations);
}
Комментарии:
1. Мне нужно использовать только Linq, а не циклы, поскольку я использую Entity Framework
2. Объяснение вашего кода помогло бы OP и другим пользователям лучше понять ваш ответ. Простое размещение вашего кода сбивает с толку других.