简体   繁体   中英

Lambda expression (or other way) to search collection

I have a collection containing planets and their moons as a Children collection.
It is a collection but it really represents a tree-like structure. I am showing only 2 tree levels for simplicity but each planet or moon could further have a collection of chemical elements, so I use 2 level tree only for simplicity.

Mercury

Venus

Mars
  - Deimos
  - Phobos

Jupiter
  - Europa
  - Ganymede
  - Io

I know how to convert this collection to a list, I just use

var myList = myCollection.Values.ToList();

I would like to search this list for each item containing "m" in its name. If parent does not have "m" in its name but any of its children moons has, I would like to include that child (moon) AND its parent (planet). In case of Jupiter, I would include both Jupiter and Ganymede in my list.

My search for "m" would therefore return following list

{Mercury, Mars, Deimos, Jupiter, Ganymede}

Id prefer using lambda for this but it is not required to do so

UPDATE: Structure

BodyNode
-ID      [Guid]
-Name    [string]
-IsChild [bool]
-Parent  [BodyNode]
-Children[BodyList ObservableCollection of BodyNode]

BodyTreeNode : BodyNode
-Expanded   [bool]
-Selected   [bool]
-Enabled    [bool]

If you are really sure that your data is a tree-like structure then you can do something like (without checking for cycles):

 bool GetNodes(Tree root, List<Tree> result, Func<Tree, bool> f) {
     bool add = f(root);
     foreach (var child in root.Children) {
         add ||= GetNodes(child, result, f);
     }
     if (add)
          result.Add(root);
     return add;
}

f is function that let you know whether or not to add a Tree. Eg (t)=>t.Name.Contains("m") .

Edit: Assuming all objects derive from Base and Base has a property public List<Base> GetChildren{get;} the above logic can be implemented like:

 bool GetNodes(Base b, List<Base> result, Func<Base, bool> f) {
     bool add = f(b);
     foreach (var child in b.GetChildren) {
          add ||= GetNodes(child);
     }
     if (add) result.Add(b);
     return add;
}

You would then use it as:

var r = new List<Base>();
myList.Foreach(o => GetNodes(o, r, (b) => b.Name.Contains("m")); 

Suppose you had a sequence of all elements of the collection. You could then use Where , or any other sequence operator, on that sequence. So your best bet is to build that sequence:

static class Extensions {
 public static IEnumerable<Nodes> Flatten(this IEnumerable<Node> nodes)
 {
  foreach(var node in nodes)
  {
    yield return node;
    foreach (var child in node.Children.Flatten())
      yield return child;
  }
 }
}

Easy peasy. And now you can simply say:

var results = from node in myCollection.Values.Flatten()
              where node.Name.ToLower().Contains("m")   
              select node;

That's a list of every element that has an m in the name:

Or using a lambda

var results = myCollection.Values.Flatten()
  .Where(node => ... and so on ... );

Now, what you want is a list of every element that has an m in the name or any child does. So write that; we have all the tools we need!

var results = from node in myCollection.Values.Flatten()
              where node.Name.Contains("m") || node.Children.Flatten().Any(node.Name.Contains("m"))
              select node;

Now, this is rather inefficient -- can you see why? -- but it does the trick. And now you have something working that you can analyze and try to make more efficient if you actually need to.

var MRecords = myList.Where(x=>x.toUpper().Contains("M"));

var result = new HashSet<"yourClass">(); //i didn`t use hashset before but this as stated in [here](http://stackoverflow.com/questions/6391738/what-is-the-difference-between-hashsett-and-listt) eliminates duplicates 

foreach(var record in MRecords)
{
    result.Add(record);
    var ParentLooper = record; 
    while(ParentLooper.parent!=null) //i suppose roots have the parent as null
    {
         result.add(ParentLooper.parent);
         ParentLooper = ParentLooper.parent;
    }
}
return result;

Personally, I recommend using objects that contain IEnumerables of their own custom class when dealing with tree entities like this. Your Planet and your Moon both have the same properties, it seems, so they are essentially both of class Planet . Given that we're just dealing with names, I made a private mockup class as below:

private class Planet
        {
            public string Name { get; set; } = string.Empty;
            public IEnumerable<Planet> Children { get; set; } = new List<Planet>();
        }

This makes the use of Lambdas a bit simpler. I didn't get to test this one, but if it's not correct it's close to it and should get you there:

      IEnumerable<Planet> myCollection = new List<Planet>();
      myCollection = LetThereBeLight(true); //method to populate the list
      var myList = myCollection.ToList().Where(t => t.Name.ToUpper().Contains("M"));
      myList.ToList().AddRange
                            (
                             myCollection.SelectMany(t => t.Children.Where
                                    (
                                      v => v.Name.ToUpper().Contains("M")).Distinct()
                                    )
                            );

You could make it all one Lambda, but it's hard enough to read without trying to squeeze it all into one line. This will give you a single IEnumerable<Planet> If you want to signify a difference in the moons, like adding a "-" to the name string , you can loop through each Planet's Children collection. You may want to make sure you only use myList.Distinct() , as duplication is entirely possible here.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM