Поиск элементов, связанных со всеми другими элементами

#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());

        }
  

В принципе, я сделал:

  1. Найдите все целевые объекты.
  2. Для каждого источника найдите все целевые объекты (важно различать) и сгруппируйте их по источнику в словаре ( dict переменная)
  3. Из приведенного выше словаря выберите все источники, которые соответствуют всем целевым объектам (количество в примере достаточно, но вы можете провести более сложное сравнение)

Ответ №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 и другим пользователям лучше понять ваш ответ. Простое размещение вашего кода сбивает с толку других.