How to filter a recursive object? - c#

In my current project, a method I don't control sends me an object of this type:
public class SampleClass
{
public SampleClass();
public int ID { get; set; }
public List<SampleClass> Items { get; set; }
public string Name { get; set; }
public SampleType Type { get; set; }
}
public enum SampleType
{
type1,
type2,
type3
}
I display those data in a TreeView, but I would like to display only the path ending with SampleClass objects having their Type property set to type3, no matter the depth of this leaf.
I have absolutely no clue on how to do that, can someone help me ?
Thanks in advance !
Edit
To explain the problem I meet with the solutions proposed by Shahrooz Jefri and dasblinkenlight, here is a picture. The left column is the original data, without filtering, and the right one is the data filtered. Both methods provide the same result.
In red is the problem.

Use this Filter method:
public void Filter(List<SampleClass> items)
{
if (items != null)
{
List<SampleClass> itemsToRemove = new List<SampleClass>();
foreach (SampleClass item in items)
{
Filter(item.Items);
if (item.Items == null || item.Items.Count == 0)
if (item.Type != SampleType.type3)
itemsToRemove.Add(item);
}
foreach (SampleClass item in itemsToRemove)
{
items.Remove(item);
}
}
}

In addition to initially determining which items to show, if the datasize is substantial and you expect users to frequently collapse and expand sections then filtering after every click my result in slow ui response.
Consider the Decorator pattern or some other way of tagging each node with relevant info so that the filtering is not required after every click.

Try this approach:
static bool ShouldKeep(SampleClass item) {
return (item.Type == SampleType.type3 && item.Items.Count == 0)
|| item.Items.Any(ShouldKeep);
}
static SampleClass Filter(SampleClass item) {
if (!ShouldKeep(item)) return null;
return new SampleClass {
Id = item.Id
, Name = item.Name
, Type = item.Type
, Items = item.Items.Where(ShouldKeep).Select(x=>Filter(x)).ToList()
};
}
The above code assumes that Items of leaves are empty lists, rather than nulls.

Related

Find tree item in c#

I have a page in which a user can select a child of a child from a tree hierarchy in Xamarin Forms. After saving, once a user clicks the edit button, I need to loop over all the items to set the user's selected value again
For example:
public class A
{
public string Id {get;set;}
public string Name {get;set;}
public List<A> Items{get;set;}
}
In the VM, I have a method to initialize object A1 of type A. I need to loop over all the children of A to match a value of A to that of the selected Id
private A GetA(string id, List<A> items)
{
foreach (var a in items)
{
if (a.Id == id)
{
return a;
}
else
{
if (a.Items.Count > 0)
{
return GetA(id, a.Items);
}
}
}
return null;
}
So far, I wrote a recursive function that only loops on the first child of every A. Thus, can anyone provide me with a better solution?
The problem is, that you are not further iterating over the other items in the list, when a.Id != id and a.Items.Count > 0. You should instead save the result of the recursive GetA and only if it's not null return it, otherwise keep looping. Otherwise you would just loop until the first branch and then recursively only ever search all the first branches, but not any other ones.
private A GetA(string id, List<A> items)
{
foreach (var a in items)
{
if (a.Id == id)
{
return a;
}
// You could also remove this if and just call GetA directly,
// since GetA(id, a.Items) with an empty list,
// will always return null
if (a.Items.Count > 0)
{
var innerA = GetA(id, a.Items);
if (innerA != null) {
return GetA(id, a.Items);
}
}
}
return null;
}

Why does the "contains" method returns an element that is in the list as if it is not?

I have a Product table in my DB. Also, I have Brand and Category tables in my DB which are not related to each other. I want to relate these. In the form UI when I click the one of the Categories, should come the Brands which they have products in the related category.
I tried this way to do this. First, I get my products by categoryID with GetList method then I get these products' brands and I added these brands to pblist list(Brand type). However, some products have the same brands and pblist have repeated brand names. I tried to fix this with contains method but it does not work. Also, I have the same problem in the other part which I try to remove brands not included in pblist from blist(all brands' list). I tried removing item from blist by taking its index with this code: blist.RemoveAt(blist.IndexOf(item)); but this one also not working.It returns -1. But item is in the blist.
public class BrandVM : BaseVM
{
public int ProductCount { get; set; }
}
public class BaseVM
{
public int ID { get; set; }
public string Name { get; set; }
public override string ToString()
{
return this.Name;
}
public class BrandService : ServiceBase, IBrandService
{
public List<BrandVM> GetList(int Count)
{
try
{
var result = GetQuery();
result = Count > 0 ? result.Take(Count) : result;
return result.ToList();
}
catch (Exception ex)
{
return null;
}
}
public List<BrandVM> GetListByCatID(int pCatID)
{
var plist = productService.GetListByCatID(pCatID);
List<BrandVM> pblist = new List<BrandVM>();
foreach (var item in plist)
{
if (!pblist.Contains(item.Brand))
{
pblist.Add(item.Brand);
}
};
var blist = GetList(0);
var blistBackup = GetList(0);
foreach (BrandVM item in blistBackup)
{
if (!pblist.Contains(item))
{
blist.Remove(item);
}
};
return blist;
}
These are my classes related to Brand. In BrandService I shared the filled methods there are more methods to fill.
This is method is in my ProductService:
I use that method to pull product list by CategoryID (plist)
public List<ProductVM> GetListByCatID(int EntityID)
{
try
{
var result = GetQuery().Where(x => x.Category.ID==EntityID);
return result.ToList();
}
catch (Exception ex)
{
return null;
}
}
This GetQuery method for ProductService, in other services there are some differences but there are similar
private IQueryable<ProductVM> GetQuery()
{
return from p in DB.Products
select new ProductVM
{
ID = p.ProductID,
Name = p.ProductName,
UnitPrice = (decimal)p.UnitPrice,
Category =p.CategoryID==null?null:new CategoryVM()
{
ID = (int)p.CategoryID,
Name = p.Category.CategoryName
},
Brand = p.BrandID == null ? null :
new BrandVM
{
ID=(int)p.BrandID,
Name=p.Brand.BrandName,
}
};
}
Entity framework will translate Linq queries into SQL statements, which means that Equals (and GetHashCode) will not be used for comparison of database objects. However, if you're comparing local instances of these objects, then these methods will be used for comparisons.
The default Equals does a reference comparison to determine equality, which literally means that two instances of a type are only considered equal if they both refer to the exact same object in memory.
Instead, we want to use the ID property for equality comparison, which means we need to override the Equals (and GetHashCode) methods for the class.
Here's an example of how you could do this:
public class BaseVM
{
public int ID { get; set; }
public string Name { get; set; }
public override string ToString()
{
return Name;
}
public override bool Equals(object obj)
{
return obj is BaseVM &&
((BaseVM) obj).ID == ID;
}
public override int GetHashCode()
{
return ID;
}
}
Alternatively, if you don't want to modify the class (which I would recommend since it solves this problem everywhere), you can modify your code to filter out any brands that have the same id (or name):
foreach (var item in plist)
{
// Note: you could potentially use 'Name' instead of 'Id'
if (!pblist.Any(productBrand => productBrand.Id == item.Brand.Id))
{
pblist.Add(item.Brand);
}
}
Since you don't ensure that two different instances for a same brand are not equal,
in the sense that ´.Equals(object other)´ returns true,
the ´.Contains´ method as no way to identify them.
I think you'ĺl solve you issue by overriding .Equals in you Brand class.

Recursive list contains property

I have a class as follows:
public class Feature
{
public string Name { get; set; }
public string DisplayName { get; set; }
public List<Feature> SubFeatures { get; set; } = new List<Feature>();
}
I then have a List<Feature> features = new List<Feature>; where I store all my features.
Now, I'd like to know if a particular Feature (by name) exists in my features variable.
However, it can exist at any level (SubFeature of the SubFeature of the SubFeature for example).
The closest I've gotten was this:
public bool FeatureExists(Feature feature, string name)
{
return feature.Name == name || feature.SubFeatures.Select(subFeature => FeatureExists(subFeature, name)).Any(result => result);
}
But it involves having to use a for on the caller of FeatureExists() to pass one top level feature at a time to it.
I'm sure there's an easier way to do this, how can I do this properly?
Define a recursive method like this:
public IEnumerable<Feature> FeatureAndSubFeatures(Feature feature)
{
yield return feature;
foreach (var subFeature in feature.SubFeatures)
{
foreach (var child in FeatureAndSubFeatures(subFeature))
{
yield return child;
}
}
}
Then use it:
FeatureAndSubFeatures(feature).Any(x => x.Name == name);
Another option is to put this method on Feature itself, called something like SelfAndSubFeaturesRecursive().
This approach - writing a method to recursively flatten the tree, rather than writing a specific method to search for a Feature with the given name - is flexible, as you can use it to search the tree for any node based on any criterion, or any subset of nodes, rather than being specialised for just finding nodes with a particular name.
You can also write it to take a collection of features to start with. Something like:
public IEnumerable<Feature> FeaturesAndSubFeatures(IEnumerable<Feature> features)
{
foreach (var feature in features)
{
yield return feature;
foreach (var child in FeaturesAndSubFeatures(feature.SubFeatures))
{
yield return child;
}
}
}
This is only useful if you're always starting with a collection of features, but saves a SelectMany in the event that you do.
I try to avoid recursion whenever possible. This is a version without:
public bool FeatureExists(Feature feature, string name)
{
var featureQueue = new Queue<Feature>();
featureQueue.Enqueue(feature);
while (featureQueue.Count > 0)
{
Feature current = featureQueue.Dequeue();
if (feature.Name == name)
return true;
foreach (Feature f in current.SubFeatures)
featureQueue.Enqueue(f);
}
return false;
}
If you find this less readable as commented you can use a generic extension method and use it whenever you need recursive checks, for example:
public static class Extensions
{
public static bool RecursiveCheck<T>(this T rootItem, Func<T, IEnumerable<T>> getChildrenFunc, Func<T, bool> predicate)
{
var queue = new Queue<T>();
queue.Enqueue(rootItem);
while (queue.Count > 0)
{
T current = queue.Dequeue();
if (predicate(current))
return true;
foreach (T child in getChildrenFunc(current))
queue.Enqueue(child);
}
return false;
}
}
Test-Feature:
Feature f1 = new Feature
{
Name = "1", SubFeatures = new List<Feature> { new Feature {Name="1.1", SubFeatures = new List<Feature> {new Feature {Name= "thename" } } }}
};
This simple one-liner remains:
bool containsName = f1.RecursiveCheck<Feature>(f => f.SubFeatures, f => f.Name == "thename");
You want to make a separate recursive method that does this for you.'
Try this:
public bool FeatureExists(Feature feature, string name) {
if(feature.Name == name) {
return true;
}
if(!feature.SubFeatures.isEmpty) {
foreach(Feature subFeature in feature.SubFeatures){
FeatureExists(subFeature, name)
}
}
return false;
}
the following should recurse through all the lists and populate "result" with all the possible features contained in the entire hierarchy
public class Feature
{
public string Name { get; set; }
public string DisplayName { get; set; }
public List<Feature> SubFeatures { get; set; } = new List<Feature>();
}
other class
List<feature> result = new List<feature>();
public void FindItems(Feature yourFeature)
{
result.add(yourFeature);
foreach(Feature feature in yourFeature)
{
if(feature.SubFeatures.count != 0)
{
foreach(Feature subfeature in feature)
{
FindItems(subfeature);
}
}
else
{
result.add(feature);
}
}
}
Well I'm not sure if this is what you are asking but if you want cleaner code you may write your function as an extension method and use LINQ instead of for loops to get cleaner code.
public static bool FeatureExists(this Feature feature, string name)
{
return feature.Name == name || feature.SubFeatures.Select(subFeature => FeatureExists(subFeature, name)).Any(result => result);
}
And then
List<Feature> mainFeatures = new List<Feature>();
mainFeatures.Any(obj => obj.FeatureExists("abc"));
If you want event shorter and cleaner code you might consider having a feature as a parent of all features like a mother feature and then call your recursive method over that.
But anyway consider making your method an extension method.

Check all properties in List

I have List of class as :-
public class Requirement
{
public string Id { get; set; }
public string desc { get; set; }
}
List lstRequirement
I have 3 records in this list for Id and desc.
I wanted to check if any of item is not remaining null.
For that I used below :-
bool IsHavingValidTags = lstRequirement.All(_=> _.Id!=null && _.desc!=null);
This condition is working fine with above Linq.
But I wanted to make it as Generic.
Eg. In future there may get added 5 more properties in Requirement class.
After addition of properties I also have to make changes in Linq.
How can I make this Linq condition generic for all properties?
I want to check any of the property is not remaining null in List.
Please help..!!!
I tried With =>
bool IsHavingValidTags = lstRequirement.All(_ => _ != null);
But not giving desired result.
EDIT 1 :
You can write an extension method that uses reflection like the following:
public static class Extensions
{
public static bool AreAllPropertiesNotNullForAllItems<T>(this IEnumerable<T> items)
{
var properties = typeof(T).GetProperties();
return items.All(x => properties.All(p => p.GetValue(x) != null));
}
}
then use like this:
bool IsHavingValidTags = lstRequirement.AreAllPropertiesNotNullForAllItems();
EDIT:
PropertyInfo.GetValue(object obj) method overload was introduced in .NET Framework 4.5. If you are using .NET Framework 4.0 you need to call p.GetValue(x, null)
Instead of this you should make those field not null. this will never allow those field inserted null. keep validations. like bellow.
[Required(ErrorMessage = "First name is required")]
public string first_name { get; set; }
[Required(ErrorMessage = "Last name is required")]
public string last_name { get; set; }
You can use foreach loop to loop through all the object in the list. Then use reflection to get all the properties in each item in the list, then you can loop through those properties to perform your null check.
Foreach (var x in lstRequirement){
List prop = x.GetType().GetProperties();
Foreach (var y in prop){
If (y == null){
IsHavingValidTag = true;
//Then you can return you method here or throw an Exception
}
}
Hope this helps.
You should add an static method to check the Properties of the Class. I will show you the following example.
Instead of your code :
bool IsHavingValidTags = lstRequirement.All(_ => _ != null);
use the following codes:
bool flg = list.All(m => CheckProperties(m));
public static bool CheckProperties<T>(T source)
{
bool rtnFlg = true;
Type t = typeof(T);
var properties = t.GetProperties();
foreach (var prop in properties)
{
var value = prop.GetValue(source, null);
if (value == null)
{
return false;
}
}
return rtnFlg;
}

Variable initalisation in while loop

I have a function that reads a file in chunks.
public static DataObject ReadNextFile(){ ...}
And dataobject looks like this:
public DataObject
{
public string Category { get; set; }
// And other members ...
}
What I want to do is the following basically
List<DataObject> dataObjects = new List<DataObject>();
while(ReadNextFile().Category == "category")
{
dataObjects.Add(^^^^^ the thingy in the while);
}
I know it's probably not how it's done, because how do I access the object I've just read.
I think what you're looking for is:
List<DataObject> dataObjects = new List<DataObject>();
DataObject nextObject;
while((nextObject = ReadNextFile()).Category == "category")
{
dataObjects.Add(nextObject);
}
But I wouldn't do that. I'd write:
List<DataObject> dataObject = source.ReadItems()
.TakeWhile(x => x.Category == "Category")
.ToList();
where ReadItems() was a method returning an IEnumerable<DataObject>, reading and yielding one item at a time. You may well want to implement it with an iterator block (yield return etc).
This is assuming you really want to stop reading as soon as you find the first object which has a different category. If you actually want to include all the matching DataObjects,
change TakeWhile to Where in the above LINQ query.
(EDIT: Saeed has since deleted his objections to the answer, but I guess I might as well leave the example up...)
EDIT: Proof that this will work, as Saeed doesn't seem to believe me:
using System;
using System.Collections.Generic;
public class DataObject
{
public string Category { get; set; }
public int Id { get; set; }
}
class Test
{
static int count = 0;
static DataObject ReadNextFile()
{
count++;
return new DataObject
{
Category = count <= 5 ? "yes" : "no",
Id = count
};
}
static void Main()
{
List<DataObject> dataObjects = new List<DataObject>();
DataObject nextObject;
while((nextObject = ReadNextFile()).Category == "yes")
{
dataObjects.Add(nextObject);
}
foreach (DataObject x in dataObjects)
{
Console.WriteLine("{0}: {1}", x.Id, x.Category);
}
}
}
Output:
1: yes
2: yes
3: yes
4: yes
5: yes
In other words, the list has retained references to the 5 distinct objects which have been returned from ReadNextFile.
This is subjective, but I hate this pattern (and I fully recognize that I am in the very small minority here). Here is how I do it when I need something like this.
var dataObjects = new List<DataObject>();
while(true) {
DataObject obj = ReadNextFile();
if(obj.Category != "category") {
break;
}
dataObjects.Add(obj);
}
But these days, it is better to say
List<DataObject> dataObjects = GetItemsFromFile(path)
.TakeWhile(x => x.Category == "category")
.ToList();
Here, of course, GetItemsFromFile reads the items from the file pointed to by path and returns an IEnumerable<DataObject>.
List<DataObject> dataObjects = new List<DataObject>();
string category = "";
while((category=ReadNextFile().Category) == "category")
{
dataObjects.Add(new DataObject{Category = category});
}
And if you have more complicated object you can do this (like jon):
List<DataObject> dataObjects = new List<DataObject>();
var category = new DataObject();
while((category=ReadNextFile()).Category == "category")
{
dataObjects.Add(category);
}
You should look into implementing IEnumerator on the class container the call to ReadNextFile(). Then you would always have reference to the current object with IEnumerator.Current, and MoveNext() will return the bool you are looking for to check for advancement. Something like this:
public class ObjectReader : IEnumerator<DataObject>
{
public bool MoveNext()
{
// try to read next file, return false if you can't
// if you can, set the Current to the returned DataObject
}
public DataObject Current
{
get;
private set;
}
}

Categories

Resources