Динамический для циклов над списками (C #)

#c# #for-loop #cartesian-product

#c# #for-loop #Декартово произведение

Вопрос:

У меня есть 3 списка, содержащих строки

   GenderList = ["M", "F"] 
  AgeList    = ["10-15", "16-20", "21-26"]
  CityList   = ["CityA", "CityB"]
  

Я хочу создать перекрестный продукт над списками, но катастрофически:
если пользователь запрашивает gender_city :
Мне нужно следующее:

  foreach(var g in GenderList)
    foreach(var c in CityList
     .....
  

Если пользователь запрашивает gender_city_age , цикл должен быть:

 foreach(var g in GenderList)
  foreach(var c in CityList)
    foreach(var a in AgeList)
       ....
  

если пользователь запрашивает city_age

 foreach(var c in CityList)
   foreach(var a in AgeList)
      ...
  

Как я могу это сделать? У меня есть о 20 списках, мне нужно динамически умножать элементы списков
спасибо

Комментарии:

1. Что вы хотите сделать, объединить строки, создать объект со всеми свойствами, …?

2. Мне любопытно, может ли быть лучший подход, чем использование набора циклов for. Я не совсем уверен, какие данные вам нужны, но это звучит так, что Sql Server или какая-либо СУБД могут быть более подходящими для того, что вам нужно, поскольку у вас могут быть условные соединения СЛЕВА, которые могут дать вам то, что вам нужно.

3. Да, у меня есть 3 таблицы из базы данных (пол, город, возраст), и я заполняю их в списки. Я хочу предоставить пользователю подсчет (пол в городе), (пол по возрасту), (пол по городу и возрасту) Я имею в виду, я хочу, чтобы пользователь заполнял лист Excel, содержащий динамический col: gender city coutn или gender city age count, или city age count …. и т. Д

4. связаны ли ваши данные внешними ключами в таблицах вашей базы данных?

5. Нет, отдельные таблицы, я хочу генерировать Excel динамически

Ответ №1:

Давайте начнем с обобщенного Cartesian :

 private static IEnumerable<T[]> Cartesian<T>(IEnumerable<IEnumerable<T>> source) {
  T[][] lists = source
    .Select(line => line.ToArray())
    .ToArray();

  if (source.Any(line => !line.Any()))
    yield break;

  int[] indexes = new int[lists.Length];

  do {
    yield return lists.Select((line, index) => line[indexes[index]]).ToArray();

    for (int i = 0; i < indexes.Length;   i)
      if (  indexes[i] < lists[i].Length) 
        break;
      else
        indexes[i] = 0;
  }
  while (!indexes.All(index => index == 0));
}
  

Тогда мы можем попробовать его использовать. Давайте организуем все коллекции, которые мы хотим запросить, в единый словарь:

 // we allow to use any IEnumerable<string>, not necessary List<string>
Dictionary<string, IEnumerable<string>> data = 
  new Dictionary<string, IEnumerable<string>>(StringComparer.OrdinalIgnoreCase) {
    { "Gender", new [] { "M", "F" } },
    { "Age",    new List<string> { "10-15", "16-20", "21-26" } },
    { "City",   new [] { "CityA", "CityB" } },
};
  

И запрашивать его:

  string userChoice = "gender_city_age";

 var result = Cartesian(userChoice.Split('_').Select(name => data[name]));

 // Have a look at the result:
 string report = string.Join(Environment.NewLine, result
   .Select(line => string.Join(", ", line)));

 Console.Write(report);
  

Результат:

 M, CityA, 10-15
F, CityA, 10-15
M, CityB, 10-15
F, CityB, 10-15
M, CityA, 16-20
F, CityA, 16-20
M, CityB, 16-20
F, CityB, 16-20
M, CityA, 21-26
F, CityA, 21-26
M, CityB, 21-26
F, CityB, 21-26
  

Комментарии:

1. Я постараюсь, кажется отличным ответом, большое вам спасибо