Creating ILookups

I have an ILookup generated by some kind of complex expression. Let's say this is a search for people by last name. (In our simplified model of the world, surnames are unique by family)

ILookup<string, Person> families; 

Now I have two questions that interest me how to create.

First, how will I filter by name?

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

But here germanFamilies is IEnumerable<IGrouping<string, Person>> ; if I call ToLookup() on it, I would put it better IGrouping<string, IGrouping<string, Person>> . If I try to be smart and call SelectMany , first I would end up with a computer doing a lot of unnecessary work. How could you easily convert this listing into a search?

Secondly, I would like to get only adult viewing.

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

Here I encountered two problems: the Grouping type Grouping not exist (except for the inner inner class Lookup ), and even if this were done, we would encounter the problem described above.

Thus, in addition to implementing the ILookup and IGrouping interfaces completely or forcing the computer to do stupid amounts of work (rearranging what has already been grouped), is there a way to modify existing ILookups to generate new ones that I missed?

+4
source share
2 answers

(I'm going to assume that you really wanted to filter by last name, given your query.)

You cannot change any implementation of ILookup<T> that I know of. Of course, implement ToLookup with constant search , as you clearly know :)

However, you could change the use of Dictionary<string, List<Person>> :

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

This approach also works for your second request:

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

While this still does a little more work than we might think, this is not so bad.

EDIT: A discussion with Ani in the comments is worth reading. Basically, we will iterate over each person anyway, so if we assume that searching and inserting the word O (1), we are actually no better in terms of the complexity of time using the existing search than smoothing:

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

In the first case, we could use an existing grouping, for example:

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

Then it is potentially much more effective (if we have a lot of people in each family), but it means that we use groups "out of context". I believe that everything is actually in order, but for some reason I have a slightly strange taste. Since ToLookup materializes the request, it’s hard to understand how this could happen, though ...

+4
source

For your first request, how to implement your own FilteredLookup , able to take advantage of another ILookup ?
(thanks to Jon Skeet for the tip)

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

With the FilteredLookup class:

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

and grouping:

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

So basically your first request will be:

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

This avoids re-alignment-filtering-ToLookup or creating a new dictionary (and so on hashing).

For the second query, the idea will be similar, you just need to create a similar class, not filtering for all IGrouping , but for IGrouping elements.

Just an idea, maybe it cannot be faster than other methods :)

+2
source

Source: https://habr.com/ru/post/1334980/


All Articles