Итерировать каждую комбинацию двух элементов в HashSet

Как я могу один раз перебрать каждую комбинацию двух элементов в HashSet?

foreach (var elt1 in hashSet) {
  foreach (var elt2 in hashSet) {
    ...
  }
}

Это будет повторять комбинации из двух, но будет повторять каждую комбинацию ДВАЖДЫ. Я хотел бы сделать это один раз.

Я думаю, что это легко сделать в Python. Есть ли способ сделать это на С#?

Образец:

ввод hashSet: {1, 2, 3, 4}

перебрать: (1,2), (1,3), (1,4), (2,3), (2,4), (3,4)


person Rok Povsic    schedule 23.04.2014    source источник
comment
ToArray() hashSet, а затем использовать вложенные циклы в массиве?   -  person Medinoc    schedule 23.04.2014
comment
Это можно сделать, да. Есть ли более аккуратный способ?   -  person Rok Povsic    schedule 23.04.2014
comment
если hashSet содержит n элементов, должно быть n * n комбинаций; самый простой способ их генерировать - это вложенные циклы, INHO   -  person Dmitry Bychenko    schedule 23.04.2014
comment
Не могли бы вы уточнить это немного подробнее ... означает, как выглядит ваш вход и что будет на выходе, а также что вы хотите выполнить между ними, чтобы у нас была лучшая картина всего этого.   -  person    schedule 23.04.2014
comment
@yper у меня на голове, я так не думаю. Просто ToArray(), For i 0 → size, For j i → size   -  person Medinoc    schedule 23.04.2014
comment
@yper, что не так с решением Medinoc?   -  person Eldritch Conundrum    schedule 23.04.2014
comment
@Eldritch Conundrum: я думаю, что читабельность не самая лучшая. Мне нравится MakeAllPairs() от dasblinkenlight.   -  person Rok Povsic    schedule 23.04.2014


Ответы (4)


В C# нет встроенного метода для этого. Поскольку HashSet<T> не индексируется*, вы также не можете сделать это с двумя циклами.

Если это разовая сделка, самое простое решение — сделать два вложенных цикла по результатам ToList() или ToArray(), например так:

var items = hashSet.ToList();
for (var i = 0 ; i != items.Count ; i++) {
    var a = items[i];
    for (var j = i+1 ; j != items.Count ; j++) {
        var b = items[i];
    }
}

Если вы ищете что-то повторно используемое, создайте метод расширения для IEnumerable<T>, который создает все пары:

static IEnumerable<Tuple<T,T>> MakeAllPairs<T>(this IEnumerable<T> data) {
    var items = data.ToList();
    for (var i = 0 ; i != items.Count ; i++) {
        var a = items[i];
        for (var j = i+1 ; j != items.Count ; j++) {
            var b = items[i];
            yield return Tuple.Create(a, b);
        }
    }
}

Теперь вы можете перебирать свои пары в одном цикле:

foreach (var pair in hashSet.MakeAllPairs()) {
    Console.WriteLine("{0} {1}", pair.Item1, pair.Item2);
}

* Технически вы можете использовать расширение ElementAt<T>(int) из Enumerable, но это будет очень медленно на больших наборах.

person Sergey Kalinichenko    schedule 23.04.2014

Изначально я неправильно понял вопрос. Это новый ответ

Это то, что вам нужно (если возможен вариант работы на основе индекса). Объяснение ниже

string[] myArray = GetArray();

for (int i = 0; i < myArray.Length - 1; i++)
{
    var element1 = myArray[i];

    for(int j = i + 1; j < myArray.Length; j++)
    {
        var element2 = myArray[j];
        Console.WriteLine("{0} {1}", element1, element2);
    }
}

Объяснение: Предположим, что используется следующий массив:

Apple, Banana, Coconut, Zucchini

Когда i = 0 (яблоко), j будет 1 (банан), затем 2 (кокос), затем 3 (цуккини).

Когда i = 1 (банан), j будет 2 (кокос), затем 3 (цуккини).

И так далее...

По сути, вы следите за тем, чтобы элемент j всегда был впереди элемента i. Это означает, что вы эффективно удалили половину возможностей (где j было бы перед i), чего вы и хотели.

Примечание: если вы хотите использовать наборы одинаковых элементов (Apple + Apple), второй цикл for должен измениться на:

    for(int j = i; j < myArray.Length; j++) //notice j = i instead of i + 1
person Flater    schedule 23.04.2014
comment
Каждая комбинация означала бы не только соседние элементы. - person Rok Povsic; 23.04.2014
comment
Я изменил свой ответ. Я совершенно неправильно прочитал это. Этот правильный :) - person Flater; 23.04.2014

Вы можете работать с индексами непосредственно в HashSet. Попробуй это:

int int1, int2;
HashSet<int> hs = new HashSet<int>();
hs.Add(1);
hs.Add(2);
hs.Add(3);
for (int i = 0; i < hs.Count-1; i++) {
    int1 = hs.ElementAt<int>(i);
    for (int j = i + 1; j < hs.Count; j++)
    {
        int2 = hs.ElementAt<int>(j);
    }
}
person Zohar Peled    schedule 23.04.2014

Чтобы вернуть все перестановки (а именно (1,2) и (2,1)), вы можете перекрестно соединить набор с самим собой, используя SelectMany:

 var hashSet = new HashSet<int>{1,2,3,4,5,6,7,8};
 foreach (var elt in hashSet.SelectMany(
                     x => hashSet.Select(y => new Tuple<int, int>(x, y))))
 {
    Debug.WriteLine("{0}-{1}", elt.Item1, elt.Item2);
 }

Изменить: если вам нужны уникальные комбинации (а именно (1,2), но не (2,1)), просто добавьте фильтр только для больших значений во время перекрестного соединения:

 var hashSet = new HashSet<int> { 1, 2, 3, 4, 5, 6, 7, 8 };
 foreach (var elt in hashSet.SelectMany(
                     x => hashSet.Where(y => y >= x)
                                 .Select(y => new Tuple<int, int>(x, y))))
 {
    Debug.WriteLine("{0}-{1}", elt.Item1, elt.Item2);
 }
person StuartLC    schedule 23.04.2014
comment
Это не предотвращает повторение одной и той же комбинации дважды, например. 1+4 и 4+1. Чего и хотел ОП, если я правильно понимаю. - person Flater; 23.04.2014
comment
Исходный вопрос OP повторяется дважды, начиная с элемента 0, что дает все перестановки. - person StuartLC; 23.04.2014
comment
Цитата из OP: ...but would iterate each combination TWICE. I'd like to do it once. Это означает, что порядок элементов не имеет значения, 1 + 4 равно 4 + 1 и не должен выполняться для каждой перестановки, а только для комбинации. - person Flater; 23.04.2014
comment
Я думаю, что ОП ищет комбинацию C (n, 2). - person Fung; 23.04.2014
comment
@Fung: я так думаю. Не уверен в этих обозначениях больше :) - person Flater; 23.04.2014
comment
Обновлено - в том числе и для комбинаций. - person StuartLC; 23.04.2014