Pregunta

¿Alguien tiene una manera más ingeniosa de hacer esto? Parece que debería ser más fácil que esto, pero estoy teniendo un bloqueo mental. Básicamente, necesito eliminar elementos de un diccionario y recurrir a los valores de los elementos que también son diccionarios.

private void RemoveNotPermittedItems(ActionDictionary menu)
{
    var keysToRemove = new List<string>();
    foreach (var item in menu)
    {
        if (!GetIsPermitted(item.Value.Call))
        {
            keysToRemove.Add(item.Key);
        }
        else if (item.Value is ActionDictionary)
        {
            RemoveNotPermittedItems((ActionDictionary)item.Value);
            if (((ActionDictionary)item.Value).Count == 0)
            {
                keysToRemove.Add(item.Key);
            }
        }
    }
    foreach (var key in (from item in menu where keysToRemove.Contains(item.Key) select item.Key).ToArray())
    {
        menu.Remove(key);
    }
}

El diccionario de acción es así:

public class ActionDictionary : Dictionary<string, IActionItem>, IActionItem
¿Fue útil?

Solución

Realmente no necesita recolectar las claves e iterarlas nuevamente si itera el diccionario en reversa (desde 'menu.Count - 1' a cero). Iterar en orden directo, por supuesto, generará excepciones de colección mutadas si comienzas a eliminar cosas.

No sé qué es un ActionDictionary, así que no pude probar su escenario exacto, pero aquí hay un ejemplo usando solo Dictionary<string,object>.

    static int counter = 0;
    private static void RemoveNotPermittedItems(Dictionary<string, object> menu)
    {
        for (int c = menu.Count - 1; c >= 0; c--)
        {
            var key = menu.Keys.ElementAt(c);
            var value = menu[key];
            if (value is Dictionary<string, object>)
            {
                RemoveNotPermittedItems((Dictionary<string, object>)value);
                if (((Dictionary<string, object>)value).Count == 0)
                {
                    menu.Remove(key);
                }
            }
            else if (!GetIsPermitted(value))
            {
                menu.Remove(key);
            }
        }
    }

    // This just added to actually cause some elements to be removed...
    private static bool GetIsPermitted(object value)
    {
        if (counter++ % 2 == 0)
            return false;
        return true;
    }

También revertí la declaración 'if', pero eso era solo una suposición de que querría hacer una verificación de tipo antes de llamar a un método para actuar sobre el valor del elemento ... funcionará de cualquier manera suponiendo 'GetIsPermitted' siempre devuelve VERDADERO para ActionDictionary.

Espero que esto ayude.

Otros consejos

Si bien foreach y GetEnumerator fallan, un ciclo for funciona,

var table = new Dictionary<string, int>() {{"first", 1}, {"second", 2}};
for (int i = 0; i < table.Keys.Count; i++)//string key in table.Keys)
{
    string key = table.Keys.ElementAt(i);
    if (key.StartsWith("f"))
    {
        table.Remove(key);
    }
}

Pero ElementAt () es una característica de .NET 3.5.

Para empezar, su bucle foreach es mucho más complicado de lo que debe ser. Solo hazlo:

foreach (var key in keysToRemove)
{
    menu.Remove(key);
}

Me sorprende un poco que Dictionary no tenga un método RemoveAll pero no parece que lo tenga ...

Opción 1: un diccionario sigue siendo una colección. Iterar sobre el menú. Valores.

Puedes iterar sobre el menú. Valora y elimínalos a medida que lo haces. Los valores no aparecerán en ningún orden (lo que debería estar bien para su caso). Es posible que deba usar un bucle for y ajustar el índice en lugar de usar foreach; el enumerador lanzará una excepción si modifica la colección mientras realiza la iteración.

(Intentaré agregar el código cuando esté en mi máquina dev de Mon)

Opción 2: crear un iterador personalizado.

Algunas colecciones devueltas de ListBox SelectedItems en Winforms realmente no contienen la colección, proporcionan un envoltorio alrededor de la colección subyacente. Algo así como CollectionViewSource en WPF. ReadOnlyCollection también hace algo similar.

Cree una clase que pueda "aplanar" sus diccionarios anidados en algo que puede enumerarse sobre ellos como si fueran una sola colección. Implemente una función de eliminación que parezca que elimina un elemento de la colección, pero que realmente elimina del diccionario actual.

En mi opinión, puede definir su propia clase genérica derivada de KeyValuePair < ... > tanto TKey como TValue serán List < T > y usted puede usar el RemoveAll o el RemoveRange de la List < T > en un nuevo RemoveRange () o Método RemoveAll () en su clase derivada para eliminar los elementos que desee.

Sé que es probable que ya hayas encontrado una buena solución, pero solo por el motivo de la "astucia" si pudieras modificar las firmas de tu método para (sé que puede que no sea apropiado en tu escenario):

private ActionDictionary RemoveNotPermittedItems(ActionDictionary menu)
{
 return new ActionDictionary(from item in menu where GetIsPermitted(item.Value.Call) select item)
.ToDictionary(d=>d.Key, d=>d.Value is ActionDictionary?RemoveNotPermittedItems(d.Value as ActionDictionary) : d.Value));
}

Y puedo ver varias formas en las que puedes usar tu diccionario con elementos filtrados sin modificaciones y materializando nuevos diccionarios.

No es mucho menos complicado, pero algunos cambios idiomáticos lo hacen un poco más corto y más fácil para la vista:

    private static void RemoveNotPermittedItems(IDictionary<string, IActionItem> menu)
    {
        var keysToRemove = new List<string>();

        foreach (var item in menu)
        {
            if (GetIsPermitted(item.Value.Call))
            {
                var value = item.Value as ActionDictionary;

                if (value != null)
                {
                    RemoveNotPermittedItems(value);
                    if (!value.Any())
                    {
                        keysToRemove.Add(item.Key);
                    }
                }
            }
            else
            {
                keysToRemove.Add(item.Key);
            }
        }

        foreach (var key in keysToRemove)
        {
            menu.Remove(key);
        }
    }

    private static bool GetIsPermitted(object call)
    {
        return ...;
    }

Cambie el tipo de keysToRemove a HashSet < string > y obtendrá un método O (1) Contiene . Con List < string > es O (n), que es más lento como puede suponer.

sin probar hasta que esté en mi máquina VS mañana: o

private void RemoveNotPermittedItems(ActionDictionary menu)
{
    foreach(var _checked in (from m in menu
                             select new
                             {
                                 gip = !GetIsPermitted(m.Value.Call),
                                 recur = m.Value is ActionDictionary,
                                 item = m
                             }).ToArray())
    {
        ActionDictionary tmp = _checked.item.Value as ActionDictionary;
        if (_checked.recur)
        {
            RemoveNotPermittedItems(tmp);
        }
        if (_checked.gip || (tmp != null && tmp.Count == 0) {
            menu.Remove(_checked.item.Key);
        }
    }
}

Creo

public class ActionSet : HashSet<IActionItem>, IActionItem

Y

bool Clean(ActionSet nodes)
    {
        if (nodes != null)
        {
            var removed = nodes.Where(n => this.IsNullOrNotPermitted(n) || !this.IsNotSetOrNotEmpty(n) || !this.Clean(n as ActionSet));

            removed.ToList().ForEach(n => nodes.Remove(n));

            return nodes.Any();
        }

        return true;
    }

    bool IsNullOrNotPermitted(IActionItem node)
    {
        return node == null || *YourTest*(node.Call);
    }

    bool IsNotSetOrNotEmpty(IActionItem node)
    {
        var hset = node as ActionSet;
        return hset == null || hset.Any();
    }

Debería trabajar rápido

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top