Как получить совпадения в ConcurrentQueue / Dictionary и сгруппировать их?

Имеется словарь, в котором записаны ключ и набор некоторых строковых данных.

        IEnumerable<string> test1 = new List<string>() 
        { 
            "текст 1",
            "текст 2",
            "текст 3",
            "текст 4",
            "текст 5"
        };

        IEnumerable<string> test2 = new List<string>()
        {
            "текст 01",
            "текст 02",
            "текст 03",
            "текст 3",
            "текст 4",
            "текст 5"
        };

        IEnumerable<string> test3 = new List<string>()
        {
            "текст 1",
            "текст 2",
            "текст 3",
            "текст 49870",
            "текст 687"
        };

        IEnumerable<string> test4 = new List<string>()
        {
            "текст 01",
            "текст 02",
            "текст 03",
            "текст 411",
            "текст 611"
        };

        ConcurrentQueue<Dictionary<string, IEnumerable<string>>> pairs = new ConcurrentQueue<Dictionary<string, IEnumerable<string>>>();
        pairs.Enqueue(new Dictionary<string, IEnumerable<string>> { { "ключ 1", test1 }});
        pairs.Enqueue(new Dictionary<string, IEnumerable<string>> { { "ключ 2", test2 } });
        pairs.Enqueue(new Dictionary<string, IEnumerable<string>> { { "ключ 3", test3 } });
        pairs.Enqueue(new Dictionary<string, IEnumerable<string>> { { "ключ 4", test4 } });

Как получить из pairs следующий результат:

Объединение №1 Объединены: 1, 3

Объединение №2 Объединены: 2, 4

Основное правило объединения это наличие в одном объединении только тех ключей, в которых имеются общие значения и минимальное количество таких общих значений равно 3.

То есть в urls1 и urls3 совпадает 2 значение: текст 1, текст 2, текст 3. В это объединение не входит urls2, так как у него есть общие значения только с urls1, но не с urls3.

Пробовал разные варианты, но так и не получилось что-то стоящее.

test1 = new List()
{
"текст 1",
"текст 2",
"текст 3",
"текст 4",
"текст 5"
};

test2 = new List()
{
"текст 01",
"текст 02",
"текст 03",
"текст 3",
"текст 4",
"текст 5"
};

test3 = new List()
{
"текст 1",
"текст 2",
"текст 3",
"текст 49870",
"текст 687"
};

test4 = new List()
{
"текст 01",
"текст 02",
"текст 03",
"текст 411",
"текст 611"
};

Если обходить с начала в конец, то берется test1 и считается что он маркерный и все остальное сравнить с ним и будет в результате набор объединений, внутри которых не обязательно все элементы содержат одинаковые наборы данных, т.е. минимум 3 совпадения по значениям.

Если так делать, то на первой итерации будет группа

test1 = new List()
{
"текст 1",
"текст 2",
"текст 3",
"текст 4",
"текст 5"
};

test2 = new List()
{
"текст 01",
"текст 02",
"текст 03",
"текст 3",
"текст 4",
"текст 5"
};

test3 = new List()
{
"текст 1",
"текст 2",
"текст 3",
"текст 49870",
"текст 687"
};

Так как test2 и test3 имеют что-то общее с test1. Теперь сравнение внутри группы и получается, что тут может быть 2 вариант

Лишний

test3 = new List()
{
"текст 1",
"текст 2",
"текст 3",
"текст 49870",
"текст 687"
};

Или лишний

test3 = new List()
{
"текст 1",
"текст 2",
"текст 3",
"текст 49870",
"текст 687"
};

Какой именно убрать помогает понять (тут не знаю точно как это сделать) последующие подобные рассуждения, когда становится видно, что удастся сформировать еще пару только в случае, когда лишний в первой группе будет

test2 = new List()
{
"текст 01",
"текст 02",
"текст 03",
"текст 3",
"текст 4",
"текст 5"
};

Так как его можно совместить с

test4 = new List()
{
"текст 01",
"текст 02",
"текст 03",
"текст 411",
"текст 611"
};

И не потерять объединение.

Итого получится

1 пара

test1 = new List()
{
"текст 1",
"текст 2",
"текст 3",
"текст 4",
"текст 5"
};

test3 = new List()
{
"текст 1",
"текст 2",
"текст 3",
"текст 49870",
"текст 687"
};

И вторая пара

test2 = new List()
{
"текст 01",
"текст 02",
"текст 03",
"текст 3",
"текст 4",
"текст 5"
};

test4 = new List()
{
"текст 01",
"текст 02",
"текст 03",
"текст 411",
"текст 611"
};

И вводных таких наборов данных в плане test может быть любое количество, четное или нечетное как угодно, но на выходе никаких дублей и прочего быть не должно, а что не удается объединить, то будет само по себе по типу объединение содержит 1 элемент ну и пусть.


Ответы (1 шт):

Автор решения: motpfofs

Если использование непосредственного ConcurrentQueue можно избежать, то попробуйте этот код:

var pairsAsArray = pairs.ToArray();
var unions = new List<Dictionary<string, IEnumerable<string>>[]>();

for (int i = 0; i < pairsAsArray.Length; i++)
{
    for (int j = i + 1; j < pairsAsArray.Length; j++)
    {
        int matches = 0;
        foreach (var value in pairsAsArray[j].Values.FirstOrDefault())
        {
            if (pairsAsArray[i].Values.FirstOrDefault().Contains(value))
            {
                matches++;
                if (matches >= 3)
                {
                    unions.Add(new Dictionary<string, IEnumerable<string>>[] { pairsAsArray[i], pairsAsArray[j] });
                    break;
                }
            }
        }
    }
}
→ Ссылка