Pregunta

Tengo una iLookup generada por alguna expresión complicada. Digamos que es una búsqueda de las personas por el apellido. (En nuestro modelo del mundo simplista, apellidos son únicos por la familia)

ILookup<string, Person> families;

Ahora tengo dos consultas Estoy interesado en la forma de construir.

En primer lugar, ¿cómo iba a filtrar por el apellido?

var germanFamilies = families.Where(family => IsNameGerman(family.Key));

Pero aquí, es un germanFamilies IEnumerable<IGrouping<string, Person>>; si llamo ToLookup() en ella, estaría mejor opción sería tener una IGrouping<string, IGrouping<string, Person>>. Si trato de ser inteligente y SelectMany llamada primero que iba a terminar con el ordenador haciendo un montón de trabajo innecesario. ¿Cómo se puede convertir esta enumeración en una búsqueda con facilidad?

En segundo lugar, me gustaría llegar a las búsquedas de los adultos solamente.

var adults = families.Select(family =>
         new Grouping(family.Key, family.Select(person =>
               person.IsAdult())));

Aquí estoy frente a dos problemas:. No existe el tipo Grouping (excepto como una clase interna interna de Lookup), e incluso si lo hiciera tendríamos el problema discutido anteriormente

Por lo tanto, además de la aplicación de la iLookup y IGrouping las interfaces completamente, o hacer que el ordenador haga cantidades tontas de trabajo (reagrupar lo que ya se han agrupado), hay una manera de alterar ILookups existentes para generar otros nuevos que me perdí?

¿Fue útil?

Solución

(voy a suponer que en realidad quería filtro por el apellido, dada su consulta.)

No se puede modificar cualquier aplicación de ILookup<T> que yo sepa. Es ciertamente posible poner en práctica ToLookup con un inmutable de búsqueda , ya que estás claramente consciente:)

Lo que podría do, sin embargo, es a cambio de usar un Dictionary<string, List<Person>>:

var germanFamilies = families.Where(family => IsNameGerman(family.Key))
                             .ToDictionary(family => family.Key,
                                           family.ToList());

Ese enfoque también funciona para su segunda consulta:

var adults = families.ToDictionary(family => family.Key,
                                   family.Where(person => persion.IsAdult)
                                         .ToList());

Mientras que todavía está haciendo un bits más trabajo de lo que podríamos pensar es necesario, no es demasiado malo.

EDIT: La discusión con Ani en los comentarios es vale la pena leer. Básicamente, que ya vamos a estar interactuando sobre todas las personas de todos modos - así que si suponemos O (1) diccionario de búsqueda e inserción, estamos en realidad no es mejor en términos de tiempo-complejidad usando el vigente las operaciones de búsqueda de aplanamiento:

var adults = families.SelectMany(x => x)
                     .Where(person => person.IsAdult)
                     .ToLookup(x => x.LastName);

En el primer caso, se podría usar potencialmente la agrupación existente, así:

// We'll have an IDictionary<string, IGrouping<string, Person>>
var germanFamilies = families.Where(family => IsNameGerman(family.Key))
                             .ToDictionary(family => family.Key);

Eso es entonces potencialmente mucho más eficiente (si tenemos mucha gente en cada familia), pero significa que estamos utilizando agrupaciones "fuera de contexto". Creo que es realmente bueno, pero deja un ligero sabor extraño en la boca, por alguna razón. Como ToLookup materializa la consulta, es difícil ver cómo funciona en realidad podría ir mal, aunque ...

Otros consejos

En su primera consulta, ¿qué pasa con la implementación de su propia FilteredLookup capaz de tomar ventaja de venir de otro ILookup?
(Gracias a Jon Skeet por la pista)

public static ILookup<TKey, TElement> ToFilteredLookup<TKey, TElement>(this ILookup<TKey, TElement> lookup, Func<IGrouping<TKey, TElement>, bool> filter)
{
    return new FilteredLookup<TKey, TElement>(lookup, filter);
}

Con ser de clase FilteredLookup:

internal sealed class FilteredLookup<TKey, TElement> : ILookup<TKey, TElement>
{
    int count = -1;
    Func<IGrouping<TKey, TElement>, bool> filter;
    ILookup<TKey, TElement> lookup;

    public FilteredLookup(ILookup<TKey, TElement> lookup, Func<IGrouping<TKey, TElement>, bool> filter)
    {
        this.filter = filter;
        this.lookup = lookup;
    }

    public bool Contains(TKey key)
    {
        if (this.lookup.Contains(key))
            return this.filter(this.GetGrouping(key));
        return false;
    }

    public int Count
    {
        get
        {
            if (count >= 0)
                return count;
            count = this.lookup.Where(filter).Count();
            return count;
        }
    }

    public IEnumerable<TElement> this[TKey key]
    {
        get
        {
            var grp = this.GetGrouping(key);
            if (!filter(grp))
                throw new KeyNotFoundException();
            return grp;
        }
    }

    public IEnumerator<IGrouping<TKey, TElement>> GetEnumerator()
    {
        return this.lookup.Where(filter).GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    private IGrouping<TKey, TElement> GetGrouping(TKey key)
    {
        return new Grouping<TKey, TElement>(key, this.lookup[key]);
    }
}

y agrupar:

internal sealed class Grouping<TKey, TElement> : IGrouping<TKey, TElement>
{
    private readonly TKey key;
    private readonly IEnumerable<TElement> elements;

    internal Grouping(TKey key, IEnumerable<TElement> elements)
    {
        this.key = key;
        this.elements = elements;
    }

    public TKey Key { get { return key; } }

    public IEnumerator<TElement> GetEnumerator()
    {
        return elements.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

Así que, básicamente, su primera consulta será:

var germanFamilies = families.ToFilteredLookup(family => IsNameGerman(family.Key));

Esto le permite evitar la re-aplanamiento de filtrado-ToLookup, o la creación de un nuevo diccionario (y así hash teclas de nuevo).

En la segunda consulta la idea será similar, simplemente deberá crear una clase similar no filtrado para toda la IGrouping pero para los elementos de la IGrouping.

Es sólo una idea, tal vez podría no ser más rápido que otros métodos:)

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