LINQ запрос, чтобы найти различные комбинации элементов списка, принимая 2 за один раз c # - PullRequest
3 голосов
/ 13 января 2011
var com = from exam1 in timeTable.Exams
          from exam2 in timeTable.Exams
          where (exam1 != exam2)
          select new List<Exam> { exam1, exam2 };

timeTable - это список экзаменов

Как я могу получить com, чтобы отличаться независимо от порядка экзаменов, который он содержит.Я думаю, что это делает перестановки, теперь мне нужны только различные комбинации.

Ответы [ 5 ]

4 голосов
/ 13 января 2011
var exams = timeTable.Exams.ToList();

var com = exams.Select(x => exams.Where(y => exams.IndexOf(y) > exams.IndexOf(x))
    .Select(z => new List<Exam> {x, z}))
    .SelectMany(x => x);
2 голосов
/ 13 января 2011

Если вы можете реализовать сравнение, такое как IComparable<Exam> (которое может, например, сравнивать на основе ключевого поля, такого как ExamId), вы можете сделать это следующим образом (показывая пример с использованием целых чисел)

int[] array = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };

var query = from item1 in array
            from item2 in array
            where (item1.CompareTo(item2) < 0)
            select new Tuple<int, int>(item1, item2);

foreach (Tuple<int, int> tuple in query)
    Console.WriteLine("{0}\t{1}", tuple.Item1, tuple.Item2);

Без реализации интерфейса та же логика может быть выражена как Func<> встроенная или объявлена ​​отдельно, в зависимости от того, насколько сложной может быть логика.

Func<int, int, int> itemComparer = (x, y) => x.CompareTo(y);
// in your case, perhaps 
// Func<Exam, Exam, int> examComparer = (exam1, exam2) => exam1.Id.CompareTo(exam2.Id);

var query = from item1 in array
            from item2 in array
            where itemComparer(item1, item2) < 0
            select new Tuple<int, int>(item1, item2);

В противном случае вы можете рассмотретьследуя по указателям и просматривая item2, только когда его позиция в последовательности превышает item1.

int[] array = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };

var query = from item1 in array.Select((item, index) => new { item, index })
            from item2 in array.Select((item, index) => new { item, index })
            where (item1.index < item2.index)
            select new Tuple<int, int>(item1.item, item2.item);

foreach (Tuple<int, int> tuple in query)
    Console.WriteLine("{0}\t{1}", tuple.Item1, tuple.Item2);
0 голосов
/ 13 января 2011

Как то так?

var array = timeTable.Exams.ToArray();
var twoTuples = from index1 in Enumerable.Range(0, array.Length)
                from index2 in Enumerable.Range(i, array.Length - i)
                select Tuple.Create(array[index1], array[index2]);

(Конечно, вы можете заменить кортеж анонимным типом или List<Exam>, если это необходимо, но я подумал, что это более понятно).

Если вы не хотите (x, x), одним из способов будет добавление предложения where index1 != index2 или, альтернативно, изменение диапазонов.

Кроме того, если вам нужны только «отдельные» кортежи (последовательность содержит дубликаты), вы можете добавить вызов .Distinct() в конце. Это будет хорошо работать, так как тип Tuple<,> корректно переопределяет Equals и GetHashCode.

Преимущества

  1. Не требует внутренней сопоставимости исходных элементов.
  2. Нет необходимости рассматривать, а затем отклонять комбинации.

Минус

Это просто причудливый способ написания вложенного цикла for с индексами. Ориентирован скорее на индексы, чем на предметы, так что это, к сожалению, не очень LINQy.

0 голосов
/ 13 января 2011

Вы можете попробовать это:

var com = exams
          .Select(i => new {Head = new [] {i}, Tail = exams.SkipWhile(j => j != i).Skip(1)})
          .Select(l => l.Head.Join(l.Tail, x => 0, x => 0, (a,b) => new {a,b}))
          .SelectMany(l => l);

Как это работает: (для последовательности 1,2,3,4)
Шаг 1: Разделите входную последовательность на список заголовков и список хвостов. Список заголовков всегда будет содержать один элемент, а список хвостов будет содержать элементы после элемента заголовка.
(результат: {[1], [2,3,4]}, {[2], [3,4] }, {[3], [4]}, {[], []})
Шаг 2: Соедините списки головы и хвоста.
(результат: [[1,2], [1,3], [1,4]], [[2,3] , [2,4]], [[3,4]])
Шаг 3: Свести коллекцию

0 голосов
/ 13 января 2011

Сначала определите класс, который представляет неупорядоченный кортеж:

class UnorderedTuple<T1, T2> {
    public T1 Item1 { get; set; }
    public T2 Item2 { get; set; }
    public UnorderedTuple(T1 item1, T2 item2) {
        this.Item1 = item1;
        this.Item2 = item2;
    }

    public override bool Equals(object obj) {
        if (Object.ReferenceEquals(this, obj)) {
            return true;
        }
        UnorderedTuple<T1, T2> instance = obj as UnorderedTuple<T1, T2>;
        if (instance == null) {
            return false;
        }
        return (this.Item1.Equals(instance.Item1) &&
                this.Item2.Equals(instance.Item2)) ||
               (this.Item1.Equals(instance.Item2) &&
                this.Item2.Equals(instance.Item1)
               );
    }

    public override int GetHashCode() {
        return this.Item1.GetHashCode() ^ this.Item2.GetHashCode();
    }
}

Тогда, как пример:

int[] array = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };

var pairs = from item1 in array
            from item2 in array
            where item1 != item2
            select new UnorderedTuple<int, int>(item1, item2);

var distinct = pairs.Distinct();
foreach (var item in distinct) {
    Console.WriteLine("{{{0}, {1}}}", item.Item1, item.Item2);
}

Теперь вы увидите пару {1, 2} только один раз. Обратите внимание, что

Console.WriteLine(distinct.Count());

выводит 36, что правильно, поскольку существует 9 * 8 / 2 = 72 / 2 = 36 уникальных комбинаций (9 * 8 представляет количество упорядоченных пар с различными элементами, и мы делим на 2, чтобы получить количество неупорядоченных пар).

Для вашего конкретного случая:

var com = (from exam1 in timeTable.Exams
           from exam2 in timeTable.Exams
           where (exam1 != exam2)
           select new UnorderedTuple<Exam, Exam>(exam1, exam2)
          ).Distinct();
...