Domanda

Ho un iLookup generato da qualche espressione complicata. Diciamo che si tratta di una ricerca di persone in base al cognome. (Nel nostro modello mondo semplicistico, ultimi nomi sono unici dalla famiglia)

ILookup<string, Person> families;

Ora ho due domande mi interessa come costruire.

In primo luogo, come faccio a filtrare in base al cognome?

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

Ma qui, germanFamilies è un IEnumerable<IGrouping<string, Person>>; se chiamo ToLookup() su di esso, mi piacerebbe soluzione migliore sarebbe ottenere un IGrouping<string, IGrouping<string, Person>>. Se cerco di essere intelligente e SelectMany chiamata prima sarei andato a finire con il computer facendo un sacco di lavoro inutile. Come si convertire questo enumerazione in una ricerca facile?

In secondo luogo, mi piacerebbe ottenere un lookup di soli adulti.

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

Qui sto di fronte a due problemi:. Il tipo Grouping non esiste (se non come una classe interna interna del Lookup), e anche se lo facesse avremmo avere il problema discusso in precedenza

Quindi, a parte l'attuazione della iLookup e IGrouping completamente le interfacce, o rendere il computer non quantità sciocche di lavoro (raggruppamento quanto già raggruppate), c'è un modo per alterare ILookups esistenti per generare quelli nuovi che ho perso?

È stato utile?

Soluzione

(ho intenzione di assumere che in realtà voleva filtro per cognome, data la query).

Non è possibile modificare qualsiasi implementazione di ILookup<T> che io sappia. E 'certamente possibile implementare ToLookup con una immutabile ricerca , come si è chiaramente consapevole:)

Quello che potrebbe do, però, è quello di cambiare per usare un Dictionary<string, List<Person>>:

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

Questo approccio funziona anche per la vostra seconda query:

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

Mentre questo è ancora facendo un po ' più lavoro di quanto si possa pensare necessario, non è troppo male.

EDIT: La discussione con Ani nei commenti è una lettura vale la pena. In sostanza, stiamo già andando essere l'iterazione più di ogni persona in ogni caso - quindi se assumiamo O (1) dizionario di ricerca e inserimento, siamo in realtà non è migliore in termini di tempo-complessità con il già esistente ricerca di appiattimento:

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

Nel primo caso, si potrebbe potenzialmente usare l'esistente raggruppamento, in questo modo:

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

Questo è quindi potenzialmente molto più efficiente (se abbiamo molte persone in ogni famiglia), ma significa che stiamo utilizzando raggruppamenti "fuori contesto". Credo che in realtà va bene, ma lascia un sapore un po 'strano in bocca, per qualche motivo. Come ToLookup materializza la query, è difficile vedere come si potrebbe effettivamente andare male anche se ...

Altri suggerimenti

Per la vostra prima query, per quanto riguarda l'implementazione del proprio FilteredLookup in grado di approfittare di venire da un altro ILookup?
(Grazie a Jon Skeet per il suggerimento)

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 l'essere di classe 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]);
    }
}

e raggruppamento:

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();
    }
}

Quindi, in pratica la vostra prima domanda sarà:

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

Questo consente di evitare di ri-appiattimento-filtraggio-ToLookup, o la creazione di un nuovo dizionario (e così hashing di nuovo le chiavi).

Per la seconda query l'idea sarà simile, si dovrebbe creare solo una classe simile non filtraggio per l'intero IGrouping ma per gli elementi del IGrouping.

Solo un'idea, forse potrebbe non essere più veloce rispetto ad altri metodi:)

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top