7

I need to split List<IInterface> to get lists of concrete implementations of IInterface. How can I do it in optimal way?

        public interface IPet { }
        public class Dog :IPet { }
        public class Cat : IPet { }
        public class Parrot : IPet { }

        public void Act()
        {
            var lst = new List<IPet>() {new Dog(),new Cat(),new Parrot()};
            // I need to get three lists that hold each implementation 
            // of IPet: List<Dog>, List<Cat>, List<Parrot>
        }
AsValeO
  • 2,859
  • 3
  • 27
  • 64

5 Answers5

11

You could do a GroupBy by type:

var grouped = lst.GroupBy(i => i.GetType()).Select(g => g.ToList()).ToList()

If you want a dictionary by type you could do:

var grouped = lst.GroupBy(i => i.GetType()).ToDictionary(g => g.Key, g => g.ToList());
var dogList = grouped[typeof(Dog)];

Or as Tim suggested in a comment:

var grouped = lst.ToLookup(i => i.GetType());
Nico
  • 3,542
  • 24
  • 29
4

You can use OfType extension:

var dogs = lst.OfType<Dog>().ToList();
var cats = lst.OfType<Cat>().ToList();
var parrots = lst.OfType<Carrot>().ToList();
Arturo Menchaca
  • 15,783
  • 1
  • 29
  • 53
3

While there are answers here already. Their implementations all use linq and create many new lists and passes of the data, here is a single pass implementation which is more efficient, but not as pretty. Here is some code comparing my approach with all others in here:

ouput first:

Time taken using heinzbeinz: 6533

Time taken using Arturo Menchaca: 6450

Time taken using johnny 5: 5261

Time taken using Matt Clark (no linq): 2072

          Stopwatch sw = new Stopwatch();
        sw.Start();
        for (int k = 0; k < 1000000; k++)
        {
            // heinzbeinz
            var petLists = pets.GroupBy(i => i.GetType()).Select(g => g.ToList()).ToList();
        }
        sw.Stop();
        Console.WriteLine($"Time taken using heinzbeinz: {sw.ElapsedMilliseconds}");

        sw.Reset();
        sw.Start();
        for (int k = 0; k < 1000000; k++)
        {
            // Arturo Menchaca
            var dogs = pets.OfType<Dog>().ToList();
            var cats = pets.OfType<Cat>().ToList();
            var parrots = pets.OfType<Parrot>().ToList();
        }
        sw.Stop();
        Console.WriteLine($"Time taken using Arturo Menchaca: {sw.ElapsedMilliseconds}");

        sw.Reset();
        sw.Start();
        for (int k = 0; k < 1000000; k++)
        {
            // johnny 5
            var dogs = pets.Where(x => x is Dog).ToList();
            var cats = pets.Where(x => x is Cat).ToList();
            var parrots = pets.Where(x => x is Parrot).ToList();
        }
        sw.Stop();
        Console.WriteLine($"Time taken using johnny 5: {sw.ElapsedMilliseconds}");

        sw.Reset();
        sw.Start();
        for (int k = 0; k < 1000000; k++)
        {
            // Matt Clark
            var dogs = new List<Dog>();
            var cats = new List<Cat>();
            var parrot = new List<Parrot>();
            foreach (var pet in pets)
            {
                if (pet is Dog)
                {
                    dogs.Add(pet as Dog);
                }
                if (pet is Cat)
                {
                    cats.Add(pet as Cat);
                }
                if (pet is Parrot)
                {
                    parrot.Add(pet as Parrot);
                }
            }
        }
        sw.Stop();
        Console.WriteLine($"Time taken using Matt Clark (no linq): {sw.ElapsedMilliseconds}");
Matt Clark
  • 1,171
  • 6
  • 12
  • Is this implementation more effective than **heinzbeinz**''s version? – AsValeO Sep 20 '16 at 20:32
  • 1
    Heinz's is probably the best solution if you don't know the types in advance. My solutions DOES require knowing the types in advance. To make Heinz's more efficient you probably need to do cached reflection. However, my solution's run time is lower than Heinz's, his is at least 2 passes, probably 3 – Matt Clark Sep 20 '16 at 20:33
  • 1
    "this...is more efficient" - it would make/strengthen that point if you added actual time comparisons to your answer. – hatchet - done with SOverflow Sep 20 '16 at 20:37
  • 1
    Added, also in general using linq is less efficient in terms of time and memory than for loops. @hatchet – Matt Clark Sep 20 '16 at 20:42
  • @Matt Clark, thank you for your detailed responce with time comparisons. In fact, your way is most effective, but heinzbeinz's solution looks too pretty. =) – AsValeO Sep 20 '16 at 20:44
  • 1
    @AsValeO while its true that mine is the least memory and time costly, if this is not being run on a server where every operation counts (ie the scale of like bing or google or msn or similar) then it is reasonable to use linq. Just understand the tradeoff when the system is under load :) – Matt Clark Sep 20 '16 at 20:48
  • 1
    This is classic premature optimization, which adds technical debt in favor of a *potentially* faster runtime. Always, always, always measure in your code when considering an optimization, and measure the overall app, not what you think might be slow. Technical debt (hardcoded types) is not necessarily a good tradeoff for marginally faster code. – Tim Sep 20 '16 at 20:51
  • @Tim which is why i included the blurb about tradeoff. And its not marginally faster, its over twice as fast. Which, again only shows up at scale. So if the code is not running at scale, use linq. If it is running at scale? Don't. – Matt Clark Sep 20 '16 at 20:52
  • 1
    My point is faster how? Faster when ran repeatedly? Maybe. Faster when running on a large list? Only by 100ms, which is so insignificant in terms of the overall performance that the technical debt you just introduced by having to maintain a static list completely obliterates any benefit. So it all depends on use cases. Measure with the surrounding code, with the intended data sizes, with the intended needs in mind. Never assume based on isolated benchmarks or beforehand optimizations. AKA premature optimizations are the root of all evil. – Tim Sep 20 '16 at 21:13
  • 1
    There is no maybe. At scale linq performance is not nearly as good. Which may not matter on small datasets or low qps servers. But if you work on scale systems linq can quickly become a liability as small reductions in ms add up quickly to millions of dollars in server costs saved. Further my solution could easily group by type name too and still be much faster. My point is not that my solution is inherently better, but that the op has options and should understand the tradeoffs made using linq, ava that linq is not always right. – Matt Clark Sep 20 '16 at 21:24
  • 1
    Fair point about Linq not magically always being the right solution. However I still feel like jumping straight to highly optimized solutions without even asking for scale or use case sends the wrong idea to most developers. Some people need to care about a few ms. Most do not. Optimizing before measuring in the specific app and environment is a sure fire way to unmaintainable code. And I much more often see unmaintainable code than I do under optimized code. – Tim Sep 20 '16 at 21:36
  • @MattClark granted, your code is more flexible and faster, e.g. currently you would sort a `GoldenRetriever : Dog` also into the dogs list (which could be changed if you strictly check the types). This kind of flexibility is not provided by my solution. – Nico Sep 20 '16 at 21:43
1

You can use linq the just filter the ones who are of the type you're looking for, However if you trying to break things out into any lists of all Implementations that could be tricky and you'd have to use reflection

var dogs = lst.Where(x => x is Dog).ToList()
var cats = lst.Where(x => x is Cat).ToList()
var parrots = lst.Where(x => x is Parrot).ToList()
johnny 5
  • 19,893
  • 50
  • 121
  • 195
0

You could better use

var dogs = lst.OfType<Dog>().ToList()
var cats = lst.OfType<Cat>().ToList()
var parrots = lst.OfType<Parrot>().ToList()
Clock
  • 974
  • 3
  • 17
  • 35