2010-09-13 16 views
5

Cómo convertirmarcha atrás diccionario utilizando LINQ en C#

Dictioanry<String,List<String>> into Dictionary<String,String> 

estoy teniendo un diccionario como

Dictioanry<String,List<String>>dictOne=new Dictionary<String,List<String>>(); 

, y que containg

Key(String)   Value(List<String>) 

    A    a1,a2 
    B    b1,b2 
    C    c1 

i necesidad de convertir el "dictOne "en

Dictionary<String,String> dictReverse=new Dictionary<String,String>() 

Así, el resultado será como

Key(String)   Value(String) 

    a1     A 
    a2     A 
    b1     B 
    b2     B 
    c1     C 

¿Hay alguna manera de hacer esto utilizando LINQ

Gracias de antemano

+0

¿Hay alguna razón específica por la que deba hacerse en LINQ? –

+0

@ Michael Shimmins: Ahora estoy usando el bucle foreach. está tomando más tiempo para competir la ejecución (tengo más de 2000 claves y cada valor (Lista) contiene al menos 5 elementos) –

+0

Dudo que LINQ sea más rápido, probablemente sea más lento. –

Respuesta

12

actualización: Como otros han señalado, con el fin de un diccionario para ser verdaderamente "reversible" de esta manera, los valores de en sus List<string> objetos tienen que ser todos únicos; de lo contrario, no puede crear un Dictionary<string, string> con una entrada para cada valor en su diccionario de origen, ya que habría claves duplicadas.

Ejemplo:

var dictOne = new Dictionary<string, List<string>> 
{ 
    { "A", new List<string> { "a1", "a2" } }, 
    { "B", new List<string> { "b1", "b2" } }, 
    { "C", new List<string> { "c1", "a2" } } // duplicate! 
}; 

que tiene (al menos) dos opciones para hacer frente a esto.

Opción 1: El tiro en los duplicados

Es posible que desee asegurarse de que todos los elementos de cada List<string> es, de hecho, única. En este caso, un simple SelectMany con un ToDictionary logrará lo que necesita; la llamada ToDictionary lanzará una ArgumentException al encontrarse con un valor duplicado:

var dictTwo = dictOne 
    .SelectMany(kvp => kvp.Value.Select(s => new { Key = s, Value = kvp.Key })) 
    .ToDictionary(x => x.Key, x => x.Value); 

La forma más genérica (que viene a la mente) para abstraer esta funcionalidad en su propio método sería la de poner en práctica un método de extensión que hace esto para cualquier IDictionary<T, TEnumerable> aplicación donde TEnumerable implementa IEnumerable<TValue>:

// Code uglified to fit within horizonal scroll area 
public static Dictionary<T2, T1> ReverseDictionary<T1, T2, TEnumerable>(
    this IDictionary<T1, TEnumerable> source) where TEnumerable : IEnumerable<T2> 
{ 
    return source 
     .SelectMany(e => e.Value.Select(s => new { Key = s, Value = e.Key })) 
     .ToDictionary(x => x.Key, x => x.Value); 
} 

feo la proliferación de parámetros de tipo genérico en el método anterior es para permitir tipos distintos de estrictamente Dictionary<T, List<T>>: podría aceptar una Dictionary<int, string[]>, por ejemplo, o SortedList<string, Queue<DateTime>> - solo un par de ejemplos arbitrarios para demostrar su flexibilidad.

(Un programa de pruebas que ilustran este método es en la parte inferior de esta respuesta.)

Opción 2: Skip duplica

Si los elementos duplicados en sus List<string> valores es un escenario realista que usted quiere ser capaz para manejar sin lanzar una excepción, le sugiero que eche un vistazo a Gabe's excellent answer para un enfoque que utiliza GroupBy (en realidad, Gabe también proporciona un enfoque flexible que puede cubrir cualquiera de estos dos casos en función de un selector; sin embargo, si definitivamente quiero tirar en un duplicado, aún así sugeriría el abov e enfoque, ya que debe ser algo más barato que usar GroupBy).

programa Ejemplo

Aquí está un pequeño programa de prueba que ilustra Opción 1 anteriormente en un Dictionary<string, List<string>> con no hay elementos duplicados en sus List<string> valores:

var dictOne = new Dictionary<string, List<string>> 
{ 
    { "A", new List<string> { "a1", "a2" } }, 
    { "B", new List<string> { "b1", "b2" } }, 
    { "C", new List<string> { "c1" } } 
}; 

// Using ReverseDictionary implementation described above: 
var dictTwo = dictOne.ReverseDictionary<string, string, List<string>>(); 

foreach (var entry in dictTwo) 
{ 
    Console.WriteLine("{0}: {1}", entry.Key, entry.Value); 
} 

de salida:

 
a1: A 
a2: A 
b1: B 
b2: B 
c1: C 
+0

+1 - ¡Acaba de escribir exactamente el mismo código! – Will

+0

@Will: Nice, great minds think like;) –

+0

+1 También prefiero este código de una línea con 'SelectMany' en lugar de dos' from'. –

9
// Associates each key with each of its values. Produces a sequence like: 
// {A, a1}, {A, a2}, {B, b1}, {B, b2}, {C, c1}    
var kvps = from kvp in dictOne 
      from value in kvp.Value 
      select new { Key = kvp.Key, Value = value };  

// Turns the sequence into a dictionary, with the old 'Value' as the new 'Key' 
var dictReverse = kvps.ToDictionary(kvp => kvp.Value, kvp => kvp.Key); 

Por supuesto, cada tecla en el diccionario original debe ser asociado con un conjunto único de valores, y ninguna clave debe estar asociada con valores que también estén asociados con otras claves.

También tenga en cuenta que Dictionary<K, V> no define ningún tipo de orden de enumeración. Puede usar el método Enumerable.OrderBy para enumerar el diccionario resultante en el orden apropiado.

+0

+1 por la valiosa información adicional que ha señalado, y por utilizar realmente la sintaxis de consulta LINQ, a diferencia de mí (no sé por qué nunca lo escribo de esa manera; es claramente más legible). –

+0

¿Por qué tendrías que ordenar? Vas de un conjunto desordenado a otro conjunto desordenado, por lo que la clasificación no tendría sentido. – Gabe

+0

@Dan Tao: Gracias. Estoy mucho más cómodo con la sintaxis del método de extensión; Me he forzado a comenzar a escribir la sintaxis de la consulta para tratar de acostumbrarme. – Ani

6

En caso de que lo hiciera terminan con llaves duplicadas en su diccionario de resultados, tendrían que elegir una sola de esas teclas. He aquí una aplicación que acaba recoge el primero que ve (usando First):

var dictReverse = (from kvp in dictOne 
        from value in kvp.Value 
        group kvp.Key by value) 
        .ToDictionary(grp => grp.Key, grp => grp.First()); 

Dado este diccionario de entrada:

var dictOne = new Dictionary<string, IEnumerable<string>> { 
    { "C", new List<string> { "c1", "a2" } }, 
    { "B", new List<string> { "b1", "b2" } }, 
    { "A", new List<string> { "a1", "a2" } } }; 

el resultado sería:

c1: C 
a2: C 
b1: B 
b2: B 
a1: A

Como puntos Dan a cabo, es posible que desee un comportamiento diferente en el caso de las claves duplicadas. Puede crear esta función:

public static Dictionary<V, K> Transpose<K, V>(
    this Dictionary<K, IEnumerable<V>> dictOne, 
    Func<IEnumerable<K>, K> selector) 
{ 
    return (from kvp in dictOne 
      from V value in kvp.Value 
      group kvp.Key by value) 
       .ToDictionary(grp => grp.Key, grp => selector(grp)); 
} 

entonces se podría llamar así dictOne.Transpose(Enumerable.First) para obtener el comportamiento anterior, dictOne.Transpose(Enumerable.Single) para obtener una excepción cuando hay una copia de la llave (el comportamiento de otros mensajes), dictOne.Transpose(Enumerable.Min) para recoger el primero lexicográficamente, o pase en su propia función haga lo que necesite.

+0

+1: Una buena idea, suponiendo que OP quiere poder omitir claves duplicadas. Si quiere garantizar la exclusividad de los elementos en cada 'Lista ' (no está claro a partir de la pregunta), puede * querer * 'ToDictionary' arrojar una excepción en el caso de un duplicado. –

+0

Dan: buen punto. Ver mi edición – Gabe

+0

Me gusta la idea, aunque desafortunadamente no creo que tomar un 'Diccionario >' como argumento sea tan flexible como lo desearía: un 'Diccionario >' (como en la pregunta del OP) wouldn no funciona, por ejemplo. Por supuesto, la solución simple es tomar un 'IDictionary ' con una restricción 'where TEnumerable: IEnumerable ' (ver mi actualización). –

Cuestiones relacionadas