Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic LINQ function - SelectMany with selection Func as a parameter

I've got a class with many string arrays. I'd like to have one generic function which can get me a unique List<string> for a given property. Example:

public class Zoo 
{    
  string Name { get; set;}
  string[] Animals { get; set;}
  string[] Zookeepers { get; set;}
  string[] Vendors { get; set;}
}

I'd like to have a generic function that will get me a distinct List<string> of Animals in List? I want this to be generic, so I can also get a distinct list of Zookeepers and Vendors.

I've been trying this, but it doesn't compile:

public static List<string> GetExtendedList(Func<Zoo, string[]> filter)
{
        var Zoos = QueryZoos(HttpContext.Current);
        return Zoos.Where(z => z.Type == "Active")
            .SelectMany(filter)
            .Distinct()
            .OrderBy(s => s);
    }

Note: this is related to two questions I've asked before, but I'm having trouble merging the information. I previously asked how to query using SelectMany (SO 1229897) and separately asked how to write a generic function which gets a list using Select rather than SelectMany (SO 1278989).

like image 799
Jon Galloway Avatar asked Nov 30 '22 19:11

Jon Galloway


2 Answers

"Each Zoo"

click

Suppose you had a list of zoo's:

List<Zoo> zooList = GetZooList();

Then, if you wanted distinct animals from all the zoos, you would apply SelectMany in this way:

List<string> animalList = zooList
  .SelectMany(zoo => zoo.animals)
  .Distinct()
  .ToList();

And If you commonly did this task and wanted one function to wrap these three calls, you could write such a function this way:

public static List<string> GetDistinctStringList<T>(
  this IEnumerable<T> source,
  Func<T, IEnumerable<string>> childCollectionFunc
)
{
  return source.SelectMany(childCollectionFunc).Distinct().ToList();
}

Which would then be called:

List<string> animals = ZooList.GetDistinctStringList(zoo => zoo.animals);

And for the code sample that doesn't compile (for which you've given no error message), I deduce you need to add a ToList():

.OrderBy(s => s).ToList();

The other problem (why the type argument can't be inferred) is that string[] doesn't implement IEnumerable<string>. Change that type parameter to IEnumerable<string> instead of string[]

like image 90
Amy B Avatar answered Dec 15 '22 14:12

Amy B


The best way would be to create a HashSet<String> for each String[] - this would filter out all duplicates.

Since HashSet<T> has a constructor that accepts an IEnumerable<T> you could simply instantiate a HashSet<T> by passing each of your arrays into the constructor. The resulting HashSet<T> would be the distinct list of Strings. While this is not a List<String> like you requested, HashSet<T> does implement ICollection<T> so many of the methods you would need may be available.

static ICollection<String> GetDistinct(IEnumerable<String> sequence)
{
    return new HashSet<String>(sequence);
}
like image 28
Andrew Hare Avatar answered Dec 15 '22 13:12

Andrew Hare