C# Linq изменяет внешний ключ на внутренний ключ для словаря со словарем в качестве значения

#c# #linq

#c# #linq

Вопрос:

Я пытаюсь использовать Linq для группировки словаря типов Dictionarylt;string, Dictionarylt;string, Listgt;gt; таким образом, чтобы внешний ключ становился внутренним ключом и наоборот.

Это самое близкое, что у меня было

 Dictionarylt;IEnumerablelt;stringgt;, Dictionarylt;string, Listgt;gt; reversed =   nodeTypedContainer  .GroupBy(kv =gt;  {  IEnumerablelt;stringgt; keys = kv.Value.Keys.Select(x =gt; x);   return keys;  }, kv =gt;  {  return new  {  Values = kv.Value.Values.SelectMany(x =gt; x.ToList()),  Node = kv.Key  };  })  .ToDictionary(  group =gt;  {  return group.Key;  },  group =gt;  {  return group  .Select(x =gt; (x.Containers, x.Node))  .GroupBy(x =gt; x.Node, x =gt; x.Containers)  .ToDictionary(x =gt; x.Key, x =gt; x.SelectMany(q =gt; q));  });  

где nodeTypedContainer-это

 Dictionarylt;string, Dictionarylt;string, IEnumerablelt;V1Containergt;gt;gt;  

Таким образом, внешний ключ-это IEnumerable, что имеет смысл, потому что, если у меня изначально есть такой словарь

 [  {  key: node1,  value: [{  key: k1  value: [1, 2, 3]  },{  key: k2  value: [2]  ]  },  {  key: node2,  value: [{  key: k1  value: [3]  }]  } ]  

перевернутое должно быть

 [  {  key: k1,  value: [{  key: node1  value: [1, 2, 3]  },{  key: node2  value: [3]  }]  },  {  key: k2,  value: [{  key: node1  value: [2]  }]  }, ]  

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

1. Мне нравится закрытое голосование без каких-либо объяснений.

2. Вы там не очень ясно представляете себе свои типы. Что такое List ? Что такое V1Container ? Не могли бы вы добавить в вопрос исходный и целевой типы без каких-либо псевдонимов?

3. Вы говорите , что внешний ключ (я предполагаю, что это ключ внешнего словаря) — это an IEnumerable , но в выходном примере внешние key свойства являются просто k1 и k2 являются простыми string s вместо IEnumerablelt;stringgt; s.

4. Кроме того, что вы ожидаете увидеть, когда используете IEnumerable ключ в качестве словаря? Словарь не будет проверять содержащиеся в нем элементы при сравнении ключей. Он просто сравнит адреса памяти IEnumerable s, которые, скорее всего, всегда будут разными, даже если их значения равны.

Ответ №1:

Интересная штука, подумал я.. Я бы использовал SelectMany для расширения гнезда до a { key1, key2, value } , а затем агрегировал, чтобы собрать его обратно, а не GroupBy/ToDictionary

 var r = nodeTypedContainer  .SelectMany(  kvpO =gt; kvpO.Value,   (kvpO, kvpI) =gt; new { KeyO = kvpO.Key, KeyI = kvpI.Key, kvpI.Value }  )  .Aggregate(  new Dictionarylt;string, Dictionarylt;string, Listlt;intgt;gt;gt;(),  (seed, val) =gt; {  if (seed.TryGetValue(val.KeyI, out var dictI))  dictI.Add(val.KeyO, val.Value);  else  seed[val.KeyI] = new() { { val.KeyO, val.Value } };  return seed;  }  );  

Агрегат и, в меньшей степени, СелектМаны, я думаю, используются не часто, поэтому это может потребовать объяснения.

Выбор любого с одним аргументом довольно прост: он преобразует T[][] в T [], поэтому некоторый вложенный список списков (думайте как список людей, и у каждого человека есть список домашних животных) становится прямым списком вложенных элементов (10 человек, каждый из которых имеет 20 домашних животных в списке, становится 1 списком из 200 домашних животных).

SelectMany с двумя аргументами позволяет нам получить доступ к исходному человеку, а также к списку домашних животных, что означает, что мы можем получить доступ как к более высокому уровню гнезда, так и к более низкому. Это означает, что мы можем составить список из 200 домашних животных, причем каждый человек повторяется тоже 20 раз

В этом случае он преобразует данные:

 [  {  key: node1,  value: [{  key: k1  value: [1, 2, 3]  },{  key: k2  value: [2]  ]  },  {  key: node2,  value: [{  key: k1  value: [3]  }]  } ]  

Во что-то вроде:

 { node1, k1, [1, 2, 3] } { node1, k2, [2] } { node2, k1, [3] }  

Теперь иерархии нет; вместо этого повторяется узел 1.

Затем мы снова собрали его вместе с помощью Aggregate

  • Первый аргумент для агрегирования-это новый Dictionarylt;string, Dictionarylt;string, Listlt;intgt;gt;gt; , который мы выведем. Он начинается как пустой словарь, и мы будем создавать его по мере того, как будем перебирать каждый ненужный элемент.
  • Вторым аргументом для агрегирования должен быть некоторый код, который изменяет текущее значение накопления и возвращает его. На самом деле нам не нужна версия, которая возвращает ее, потому что мы всегда изменяем содержимое исходного кода, который мы создали, вместо того, чтобы иметь неизменяемый стиль «возьмите текущую итерацию, подготовьте новую версию на ее основе и верните ее в следующий раз». В какой-то степени это противоречит принципу «LINQ не должен иметь побочных эффектов», но в Aggregate принято, что у него может быть этот побочный эффект, и это безопасно в том смысле, что мы изменяем экземпляр, созданный в исходном коде. Будьте осторожны с использованием Aggregate для чего-то изменяемого, что было создано в другом месте, кроме как в первом аргументе

Таким образом, лямбда для второго arg получает новый Dictlt;Dictgt;; он должен посмотреть, содержит ли этот внешний словарь внутренний ключ lt;Dictgt;(например, k1, k2).. Если это так, то он должен добавить внешний ключ и внутренний список в качестве новой записи. Если это не так, то он должен создать новый внутренний Dictionarylt;string, Listgt; , инициализированный внешним ключом и внутренним списком

Ответ №2:

Это самый простой способ, который я мог придумать:

 Dictionarylt;string, Dictionarylt;string, Listlt;intgt;gt;gt; reversed =  (  from kv1 in nodeTypedContainer  from kv2 in kv1.Value  select new { k1 = kv1.Key, k2 = kv2.Key, v = kv2.Value} )  .ToLookup(x =gt; x.k2)  .ToDictionary(x =gt; x.Key, x =gt; x.ToDictionary(y =gt; y.k1, y =gt; y.v));