Я пытаюсь найти эффективный способ создать метод, который берет словарь, содержащий несколько списков последовательных целых чисел (каждый список должен начинаться выше 0 или выше и заканчиваться на 100 или ниже, но точные числа могут отличаться) и возвращает список словарей, содержащих все перестановки, где сумма всех чисел равна 100.
Например, для 4 категорий: 10 + 20 + 10 + 60 = 100
Каждый словарь в результирующем списке должен храниться с одним целочисленным значением для каждого ключа.
Вот код, который я придумал, чтобы проиллюстрировать свой вопрос:
using System;
using System.Collections.Generic;
using System.Linq;
namespace recursiveTest
{
class Program
{
static void Main(string[] args)
{
Dictionary<string, List<int>> data = new Dictionary<string, List<int>>();
data.Add("A", Enumerable.Range(0, 100).ToList());
data.Add("B", Enumerable.Range(0, 100).ToList());
data.Add("C", Enumerable.Range(0, 100).ToList());
data.Add("D", Enumerable.Range(0, 100).ToList());
// I would like to add a few entries more...
List<Dictionary<string, int>> permutations = new List<Dictionary<string, int>>();
foreach (var a in data["A"])
{
foreach (var b in data["B"])
{
foreach (var c in data["C"])
{
foreach (var d in data["D"])
{
if (a + b + c + d == 100)
{
var current = new Dictionary<string, int>()
{
["A"] = a,
["B"] = b,
["C"] = c,
["D"] = d,
};
permutations.Add(current);
}
}
}
}
}
Console.WriteLine($"Found (foreach): {permutations.Count()}");
Console.ReadKey();
}
}
}
Альтернатива с использованием LINQ:
List<Dictionary<string, int>> permutations2 = (from a in data["A"]
from b in data["B"]
from c in data["C"]
from d in data["D"]
where a + b + c + d == 100
let current = new Dictionary<string, int>()
{
["A"] = a,
["B"] = b,
["C"] = c,
["D"] = d,
}
select current).ToList();
Console.WriteLine($"Found (LINQ): {permutations2.Count()}");
Console.ReadKey();
Это не было очень сложной задачей до того, как количество категорий (словарных ключей) и чисел начало расти... Поскольку количество словарных ключей (категорий) может варьироваться, это кажется потенциальным кандидатом для рекурсии, но я не не в состоянии заставить его работать. Эти две версии имеют несколько очевидных недостатков:
- Как только количество элементов и/или категорий увеличивается, производительность внезапно падает.
- Код в форме стрелки кажется рецептом катастрофы.
- Он пытается пройти все возможные комбинации, хотя на самом деле полезны лишь некоторые (те, которые в сумме дают 100).
Как лучше всего достичь желаемого результата с коротким и читаемым кодом и хорошей производительностью?
Есть ли способ отфильтровать ненужные циклы при попытке найти эти 100 значений суммы?
EDIT: Для пояснения, моя идея состоит в том, чтобы иметь возможность определить метод с такой сигнатурой:
private static List<Dictionary<string, int>> GetValidPermutations(Dictionary<string, List<int>> data)
Затем назовите это так:
List<Dictionary<string, int>> permutations = GetValidPermutations(data);