2009-04-24 12 views
5

Tengo un código como el siguiente:¿Tengo que hacer hash dos veces en C#?

class MyClass 
{ 
    string Name; 
    int NewInfo; 
} 

List<MyClass> newInfo = .... // initialize list with some values 
Dictionary<string, int> myDict = .... // initialize dictionary with some values 

foreach(var item in newInfo) 
{ 
    if(myDict.ContainsKey(item.Name)) // 'A' I hash the first time here 
     myDict[item.Name] += item.NewInfo // 'B' I hash the second (and third?) time here 
    else 
     myDict.Add(item.Name, item.NewInfo); 
} 

¿Hay alguna manera de evitar hacer dos búsquedas en el diccionario - la primera vez para ver si contiene una entrada, y la segunda vez para actualizar el valor? Incluso puede haber dos búsquedas de hash en la línea 'B', una para obtener el valor int y otra para actualizarlo.

Respuesta

14

Sí - use Dictionary.TryGetValue. Toma un parámetro out para recibir el valor y devuelve si se encontró el valor o no. Aquí está el código ajustado:

foreach(var item in newInfo) 
{ 
    int value; 
    if (myDict.TryGetValue(item.Name, out value)) 
    { 
     myDict[item.Name] = value + item.NewInfo; 
    } 
    else 
    { 
     myDict[item.Name] = item.NewInfo; 
    } 
} 

Sin embargo, podemos hacerlo mejor que eso en este caso particular,. Si la clave no está, el parámetro out se establece en 0. Como vamos a establecer el nuevo valor en item.NewInfo o item.NewInfo + value, realmente estamos haciendo lo mismo. Podemos pasar por alto el valor de retorno del método, y sólo tiene que utilizar:

foreach(var item in newInfo) 
{ 
    int value; 
    myDict.TryGetValue(item.Name, out value); 
    myDict[item.Name] = value + item.NewInfo; 
} 

que es bastante inusual, aunque - por lo general se le utilizar el valor de retorno, obviamente.

Digresión

Es sólo porque en realidad está haciendo una operación GetValueOrDefault que funciona. De hecho, eso sería un par válido de los métodos de extensión:

public static TValue GetValueOrDefault<TKey, TValue> 
    (this IDictionary<TKey, TValue> dictionary, TKey key) 
{ 
    TValue value; 
    dictionary.TryGetValue(key, out value); 
    return value; 
} 

public static TValue GetValueOrDefault<TKey, TValue> 
    (this IDictionary<TKey, TValue> dictionary, TKey key, 
    TValue customDefault) 
{ 
    TValue value; 
    if (dictionary.TryGetValue(key, out value)) 
    { 
     return value; 
    } 
    else 
    { 
     return customDefault; 
    } 
} 

En ese momento usted puede hacer su código claro y concisa:

foreach(var item in newInfo) 
{ 
    myDict[item.Name] = myDict.GetValueOrDefault(item.Name) + item.NewInfo; 
} 

(que se podría llamar GetValueOrDefault(item.Name, 0) para potencialmente más claridad .)

Volver al punto ...

Tenga en cuenta que todavía está haciendo dos búsquedas, una para obtener el valor y otra para agregar/reemplazar. Realmente no se puede evitar eso sin hacer que el argumento de tipo TValue sea mutable, lo que podría cambiar en su lugar. Eso sería posible, pero no terriblemente agradable.

En el código original, está realizando potencialmente tres búsquedas - una para ContainsKey, y luego dos (si se encuentra la clave) para reemplazar el valor. Es más fácil ver que si ampliamos la +=:

myDict[item.Name] = myDict[item.Name] + item.NewInfo; 

(. item.Name solamente sería evaluado una vez, pero aparte de eso es lo mismo)

Otra digresión

que habría bueno tener una operación en Dictionary que hizo una "búsqueda y reemplazo" en función de una función para obtener el nuevo valor basado en el anterior, por ejemplo

bool Update(TKey key, Func<TValue, bool, TValue> replacementFunction) 

donde replacementFunction sería una función de tomar el valor actual (o el valor por defecto de TValue si no se ha encontrado la clave) y un booleano para decir si o no la tecla se encontró realmente, y vuelve el nuevo valor El diccionario podría buscar la clave, llamar a la función de reemplazo y actualizar el valor en su lugar. (Esto no se puede implementar como un método de extensión.)

+1

Si bien esto es funcional, acaba de ser conscientes de que el valor sólo es 0 porque el valor predeterminado para el tipo int es 0. Desde una perspectiva de codificación defensiva, esto sacrifica la legibilidad (y potencialmente de mantenimiento) para la concisión. Me quedaría con lo que tienes, para ser sincero. –

+0

Voy a dar una advertencia más grande: ciertamente es un caso especial. –

+0

¿Todavía no has hashing cada elemento dos veces? Una vez en TryGetValue, y de nuevo donde dices 'myDict [item.Name]'? –

1

No, no tiene que hacer hash dos veces la mayor parte del tiempo. El secreto es que almacenas un objeto en el diccionario en lugar de solo un int.

class Program 
{ 
    static void Main(string[] args) 
    { 
     var newInfo = new List<MyClass>(); 
     var myDict = new Dictionary<string, MyClass>(); 

     foreach (var item in newInfo) 
     { 
      MyClass temp; 
      if (!myDict.TryGetValue(item.Name, out temp)) 
      { 
       temp = new MyClass() { Name = item.Name }; 
       myDict.Add(temp.Name,temp); 
      } 

      temp.NewInfo += 1; 

     } 

    } 
} 


class MyClass 
{ 
    public string Name; 
    public int NewInfo; 
} 
+0

Al menos conviértelos en propiedades en lugar de campos públicos ... ick! :) –

Cuestiones relacionadas