C# Traverse Hierarchy without Recursion - c#

I need a hand to transform my recursive function into a loop as I'm stuck trying to do this for hours. The reason is that I kept running into StackOverflow exception.
Please check the following code:
private List<int> GetManagers(Employee employee, List<Employee> employeeList)
{
List<int> collection = new List<int>();
if (employee.DirectManagers.Any())
{
var managers = employeeList.Where(x => employee.DirectManagers.Any(y => y.Equals(x.Id)));
foreach (var manager in managers)
{
if (!collection.Any(x => x.Equals(manager.Id)))
collection.Add(manager.Id);
if (manager.DirectManagers.Any())
collection.AddRange(GetManagers(manager, employeeList));
}
}
return collection;
}
Edit: More codes here
foreach (var employee in employeeList)
{
List<int> allManagers = new List<int>();
allManagers = GetManagers(employee, employeeList);
// Do something with allManagers found here that does not affect the collection
}
public class Employee
{
public int Id { get; set; }
public int? DepartmentId { get; set; }
public List<int> DirectManagers { get; set; }
public List<int> DirectSubordinates { get; set; }
public int Counter { get; set; }
}
var employeeList = context.AdministratorProfiles
.Where(x => !x.dateResigned.HasValue && x.departmentID.HasValue)
.Select(x => new Employee {
Id = x.id,
DepartmentId = x.departmentID,
Counter = 0,
DirectManagers = x.Managers.Select(y => y.managerID).ToList(),
DirectSubordinates = x.Subordinates.Select(y => y.adminID).ToList()
}).ToList(); // TODO: Add active account here
Basically, what this does is that I'm trying to get all the managers of an Employee. Due to the huge number of staff, I often run into StackOverflow exception. I need a hand, appreciate if anyone out there could lend a hand. Thank you.
Edit: Now, I have listed all the codes. So perhaps you can have a better understanding. Basically, what I'm trying to do is to loop through every single employee to perform work, first I must have a work list. This work list would exclude all the managers or managers' managers to form the final list.

Your problem is not recursion but cyclic references. You can use pattern visitor to work with this problem. (In this pattern you mark all entities that were visited with your recursion method and if you visit this entity again, you just return)

You could do something like:
...
Dictionary<int, bool> processed = new Dictionary<int, bool>();
Queue<Employee> managersQueue = new Queue<Employee>();
managersQueue.Enqueue(employee);
while (managersQueue.Any())
{
var currentEmployee = stack.Dequeue();
var managers = employeeList.Where(x => currentEmployee.DirectManagers.Any(y => y.Equals(x.Id)));
foreach (var manager in managers)
{
if (processed.ContainsKey(manager.Id)) continue;
processed.Add(manager.Id, true);
managersQueue.Enqueue(manager);
}
}
return processed.Select(x => x.Key).ToList();
This is just a basic outline of how you could do this iteratively, obviously I don't know your code base or exactly how certain calls would be made.

Related

Get a unique list (by key) from a list-in-list

I have a list of requests. Each request has many approvers. I want to go through all the requests and their approvers and get a list of unique approvers and their requests.
Here are sample models:
var requestsToProcess = await GetBatchOfApprovedRequestsAsyn(); // new List<RequestModel>();
public class RequestModel
{
public RequestModel()
{
ApproversList = new List<RequestApproverModel>();
}
public long Id { get; set; } // Key
public string Brief { get; set; }
public string Description { get; set; }
public List<RequestApproverModel> ApproversList { get; set; }
}
public class RequestApproverModel
{
public string Email { get; set; } // Key
public string FullName { get; set; }
}
I know how to get unique tuple from a list but don't understand if the target list is on an element of another list.
Basically the premise, is flatten and project, then groupby, then optionally project again.
Given
var requests= new List<RequestModel>()
{
new()
{
Id = 1,
ApproversList = new List<RequestApproverModel>()
{
new(){Email = "bob"},
new(){Email = "dole"}
}
},
new()
{
Id = 2,
ApproversList = new List<RequestApproverModel>()
{
new(){Email = "bob"},
new(){Email = "blerg"}
}
}
};
Example
var results =requests.SelectMany(request =>
request.ApproversList,
(request, approver) => new {request, approver})
.GroupBy(x => x.approver.Email )
.Select(x => new { Approver = x.Key, Requests = x.Select(y => y.request).ToList() });
foreach (var item in results)
{
Console.WriteLine(item.Approver);
foreach (var request in item.Requests)
Console.WriteLine(" " + request.Id);
}
Output
bob
1
2
dole
1
blerg
2
The two complementary methods you need from LINQ are SelectMany, which unpacks a list-of-lists to a list, and GroupBy, which packs a list to a list-of-lists (you need to go from a-of-b to b-of-a)
var result = someRequestModels
.SelectMany(rm => rm.ApproversList, (rm, ram) => new { RM = rm, RamEmail = ram.Email })
.GroupBy(at => at.RamEmail, at => at.RM);
The SelectMany is like a nested pair of foreach
foreach(var rm in someRequestModels)
foreach(var ram in rm.ApproversList)
flatlist.Add(new { rm, ram});
This has turned the list of lists into a single list, repeating the RequestModel over and over per RequestApproverModel. You can then run a GroupBy of approver Email which takes every unique email in the flattened list and puts together a list of list of RequestModels. In non LINQ pseudocode it'd look something like:
foreach(var rmRamPair In flatlist)
grouping[rmRamPair.Email].Add(rmRamPair.Rm);
This produces an IGrouping which is something like a list of lists, where each entry has a Key, a string of the approver's email and is an enumerable of all the requestmodels they have, so eg
foreach(var x in result){
Console.WriteLine($"approver with email of {x.Key} has cases:";
foreach(var rm in x)
Console.WriteLine($"id is '{rm.Id}' and Brief is '{rm.Brief}'");
}
If it makes you more comfortable, you can call ToDictionary(x => x.Key, x => x.ToList()) on the result and you'll get a Dictionary<string, List<RequestModel>> out, the email being the key and and list of requestmodels being the value
If you want the whole RequestApproverModel, not just the email it might be a bit more tricky. It's easy if you've reused instances of RAM so if there is literally only one object in memory that is "bob#mail.com" and that object is present on a couple of different requests:
var ram = new RequestApproverModel{ Email = "bob#mail.com" };
var r1 = new RequestModel();
r1.ApproversList.Add(ram);
var r2 = new RequestModel();
r2.ApproversList.Add(ram);
Here the instance is the same one; you can just group by it instead of the email.
If you've ended up with objects that look the same but are at different memory addresses:
var r1 = new RequestModel();
r1.ApproversList.Add(new RequestApproverModel{ Email = "bob#mail.com" });
var r2 = new RequestModel();
r2.ApproversList.Add(new RequestApproverModel{ Email = "bob#mail.com" });
Then the standard implementation of Equals and GetHashcode(inherited from object) is useless because it's based on the memory stress where the instances live.
Your RequestModel class will instead need to implement Equals and GetHashcode that report equality based on Email, otherwise grouping by the whole RequestModel won't work out

Create Intersect of HashSet and remove Items from class structure with extracted ID's

lets assume I have the following classes:
public class ServiceStatistics
{
public string LocalId { get; set; }
public string OrganizationId { get; set; }
public List<StatisticElements> Elements { get; } = new List<StatisticElements>();
}
public class StatisticElements
{
public string StatisticId { get; set; }
public string Type { get; set; }
public string ServiceName { get; set; }
}
I retrieve such ServiceStatistics by a soap service and I use serialization/deserialization.
Each ServiceStatistics contains a set of StatisticElements. I also have a static list of StatisticElements-ID's which are relevant for calculation. All other incoming StatisticElements-ID's can be dropped. I need to do this on my side
because the SOAP Service does not support selecting specific StatisticElements-ID's
So I have generated a static Class with a HashSet:
public static class RelevantDutyPlans
{
private static HashSet<int> relevantDutyPlans;
static RelevantDutyPlans()
{
// only a subset of the original ID's
relevantDutyPlans = new HashSet<int>()
{
530,
1150,
1095,
};
}
public static HashSet<int> GetRelevantDutyPlans()
{
return relevantDutyPlans;
}
public static bool Contains(int planId)
{
return relevantDutyPlans.Contains(planId);
}
// Extracts all DutyPlans which are relevant (HashSet) for validation from
// the incoming data
public static List<int> ExtractRelevantDutyPlans(List<int> planIds)
{
var relevantPlans = new HashSet<int>(planIds);
relevantPlans.IntersectWith(relevantDutyPlans);
return relevantDutyPlans.ToList();
}
}
So my thought was, to create an Intersect like this:
List<ServiceStatistics> statistics = SoapService.GetStatistics(Now);
List<int> incomingIds = new List<int>();
foreach(var item in statistics)
{
foreach(var element in item.Statistic)
{
incomingIds.Add(int.Parse(element.StatisticId));
}
}
List<int> extract = RelevantDutyPlans.ExtractRelevantDutyPlans(incomingIds);
So now I have a List of ID's which are relevant for further processing. What I want to achieve is to remove all class elements "StatisticElements" with "StatisticId" not contained in the the extract list generated above.
Any ideas?
Any help is very appreciated
How about a little bit different approach. Simply remove irrelevant plans right away!
List<ServiceStatistics> statistics = SoapService.GetStatistics(Now);
foreach(var item in statistics)
{
item.Elements.RemoveAll(x => !RelevantDutyPlans.Contains(int.Parse(x.StatisticId)));
}
Now you are only left with the relevant once.
Hope you can use selectMany to flatten the collection and proceed the filter.
var filteredItems = statistics.SelectMany(s => s.Elements)
.Where(s => extract.Contains(Convert.ToInt32(s.StatisticId)))
.ToList();
You could also use LINQ to create a new List<> if you need to keep the original statistcs intact - e.g. if you might run multiple plans against it.
var relevantStatistics = statistics.Select(s => new {
LocalId = s.LocalId,
OrganizationId = s.OrganizationId,
Elements = s.Elements.Where(e => !RelevantDutyPlans.Contains(Convert.ToInt32(e.StatisticId))).ToList()
});
Since ServiceStatistics doesn't provide for construction, I return an anonymous object instead, but you could create an appropriate DTO class.

How to set flag in one list for the Id's which match with the Id's in another list using Lambda expression c#

I have two lists classes
public class class1{
public Int Id { get; set; }
public Bool Flag{ get; set; }
}
public class class2{
public Int Id { get; set; }
}
Now i have List<class1> and List<class2>,
Now i have to update Flag property to true in List<class1> for only those Ids which match with the Id's present in List<class2> using lambda expression c#.Don't want to use foreach.
using lambda expression. Don't want to use foreach.
That's usually a silly requirement and a hallmark that you're not really familiar with C#, Linq or performance analysis. You have a collection whose elements you want to modify, so you should use foreach().
If you're trying out functional programming, then you should treat the list elements as immutable and project into a new collection.
The first part of your problem, looking up which list elements to modify based on a presence of one of their properties in another collection's elements' properties, is trivial:
var elementsToModify = list1.Where(l1 => list2.Any(l2 => l2.Id == l1.Id));
Now with a foreach(), this'll be simple:
foreach (var l1 in elementsToModify)
{
l1.Flag = true;
}
Or, even denser (not that less code equals more performance):
foreach (var l1 in list1.Where(l1 => list2.Any(l2 => l2.Id == l1.Id)))
{
l1.Flag = true;
}
So, there's your code. But you didn't want to use foreach(). Then you need to project into a new collection:
var newList1 = list1.Where(l1 => list2.Any(l2 => l2.Id == l1.Id))
.Select(l1 => new Class1
{
Id = l1.Id,
Flag = true,
})
.ToList();
There you have it, a List<Class1> with only flagged items. Optionally you could use this list in a foreach() to update the original list1. Oh, wait.
The below solution does not use the classical "for each", but is compiled to one under the hood. If that's not what you meant, then please explain what you are trying to achieve. Using for each in this example is a good approach. One could also use while or for loops, but is it really what's being asked here?
Object definition:
public class MyObject
{
public int Id { get; set; }
public bool Flag { get; set; }
}
List initialization:
var list = new List<MyObject>()
{
new MyObject() { Id= 1 },
new MyObject() { Id= 2 },
new MyObject() { Id= 3 },
new MyObject() { Id= 4 }
};
var list2 = new List<MyObject>()
{
new MyObject() { Id= 2 },
new MyObject() { Id= 4 }
};
Code:
list.ForEach(el => el.Flag = list2.Any(el2 => el2.Id == el.Id));
EDIT:
An example with a while loop (a bit nasty to do it this way):
int i = -1;
int numberOfElements = list.Count;
while (++i < numberOfElements)
{
list[i].Flag = list2.Any(el => el.Id == list[i].Id);
}
I guess you can write a for loop yourself...

How to clone object with a different Primary Key

I have this class Cart_Record, shown below. I want to update the PrimaryKey. To do that I am trying to clone the object into a new object to copy CartLines and update ID. I haven't found much in the issue queue or the documentation to help me.
public class Cart_Record : RealmObject
{
public Cart_Record() { }
public Cart_Record(IList<Cart_Line> cartLines, int id)
{
ID = id;
foreach (var cartLine in cartLines)
CartLines.Add(App.RealmDB.Find<Cart_Line>(cartLine.ProductId));
}
[PrimaryKey]
public int ID { get; set; }
public IList<Cart_Line> CartLines { get; }
}
I am trying this
var appCart = App.RealmDB.All<Cart_Record>().First();
App.RealmDB.Write(() =>
{
var cartLines = new List<Cart_Line>(appCart.CartLines);
App.RealmDB.Remove(App.RealmDB.Find<Cart_Record>(appCart.ID));
App.RealmDB.Add<Cart_Record>(new Cart_Record(cartLines, serverCart.ID));
});
However I keep getting exceptions, specifically RealmObjectManagedByAnotherRealmException. I don't understand how as I am not readding the Cart_Line objects to Realm, just to the CartLine list in the new object.
What am I doing wrong?
Thanks ahead of time.
Edit: I found something that works but I would like to see if someone else have a better method. This is what works for me.
var appCart = App.RealmDB.All<Cart_Record>().First();
App.RealmDB.Write(() =>
{
var cartLines = new List<Cart_Line>(appCart.CartLines);
App.RealmDB.Remove(App.RealmDB.Find<Cart_Record>(appCart.ID));
var newAppCart = App.RealmDB.Add<Cart_Record>(new Cart_Record() { ID = serverCart.ID });
foreach (var cartLine in cartLines)
newAppCart.CartLines.Add(cartLine);
});
I'm not sure what App.RealmDB does under the hood, but using the out-of-the-box Realm API, what you want to achieve can be done by simply adding the CartLines from the original to the updated object:
// Assume want to change Id from 1 to 2
var realm = Realm.GetInstance();
var original = realm.Find<Cart_Record>(1);
var updated = new Cart_Record { ID = 2 }; // other properties must be copied here
foreach (var cart in original.CartLines)
{
updated.CartLines.Add(cart);
}
realm.Write(() =>
{
realm.Remove(original);
realm.Add(updated);
});
// updated now has all the original's CartLines

Element Metrics with Custom collection in C#

I am trying to figure out the best way to organise a bunch of my data classes, given I need to be able to access some metrics on them all at some point.
Here's a snippet of my OR class:
public enum status { CLOSED, OPEN }
public class OR
{
public string reference { get; set; }
public string title { get; set; }
public status status { get; set; }
}
Not every OR I initialise will have values for all properties. I want to be able to 'collect' thousands of these together in such a way that I can easily obtain a count of how many OR objects had a value set. For example:
OR a = new OR() { reference = "a" }
OR b = new OR() { reference = "b", title = "test" }
OR c = new OR() { reference = "c", title = "test", status = status.CLOSED }
Now these are somehow collected in such a way I can do (pseudo):
int titleCount = ORCollection.titleCount;
titleCount = 2
I would also want to be able gather metrics for the enum type properties, for example retrieve a Dictionary from the collection that looks like:
Dictionary<string, int> statusCounts = { "CLOSED", 1 }
The reason for wanting access to these metrics is that I am building two collections of ORs and comparing them side-by-side for any differences (they should be identical). I want to be able to compare their metrics at this higher level first, then break-down where precisely they differ.
Thanks for any light that can be shed on how to accomplish this. :-)
... to 'collect' thousands of these
Thousands is not a huge number. Just use a List<OR> and you can get all your metrics with Linq queries.
For example:
List<OR> orList = ...;
int titleCount = orList
.Where(o => ! string.IsNullOrEmpty(o.title))
.Count();
Dictionary<status, int> statusCounts = orList
.GroupBy(o => o.status)
.ToDictionary(g => g.Key, g => g.Count());
The existing answers using Linq are absolutely great and really elegant, so the idea presented below is just for posterity.
Here is a (very rough) reflection-based program that will alow you to count the "valid" properties in any collection of objects.
The validators are defined by you in the Validators dictionary so that you can easily change what is a valid/invalid value for each property. You may find it useful as a concept if you end up with objects having tons of properties and don't want to have to write inline linq metrics on the actual collection itself for every single property.
You could weaponise this as a function and then run it against both collections, giving you a basis to report on the exact differences between both since it records the references to the individual objects in the final dictionary.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
namespace reftest1
{
public enum status { CLOSED, OPEN }
public class OR
{
public string reference { get; set; }
public string title { get; set; }
public status status { get; set; }
public int foo { get; set; }
}
//creates a dictionary by property of objects whereby that property is a valid value
class Program
{
//create dictionary containing what constitues an invalid value here
static Dictionary<string,Func<object,bool>> Validators = new Dictionary<string, Func<object,bool>>
{
{"reference",
(r)=> { if (r ==null) return false;
return !String.IsNullOrEmpty(r.ToString());}
},
{"title",
(t)=> { if (t ==null) return false;
return !String.IsNullOrEmpty(t.ToString());}
},
{"status", (s) =>
{
if (s == null) return false;
return !String.IsNullOrEmpty(s.ToString());
}},
{"foo",
(f) =>{if (f == null) return false;
return !(Convert.ToInt32(f.ToString()) == 0);}
}
};
static void Main(string[] args)
{
var collection = new List<OR>();
collection.Add(new OR() {reference = "a",foo=1,});
collection.Add(new OR(){reference = "b", title = "test"});
collection.Add(new OR(){reference = "c", title = "test", status = status.CLOSED});
Type T = typeof (OR);
var PropertyMetrics = new Dictionary<string, List<OR>>();
foreach (var pi in GetProperties(T))
{
PropertyMetrics.Add(pi.Name,new List<OR>());
foreach (var item in collection)
{
//execute validator if defined
if (Validators.ContainsKey(pi.Name))
{
//get actual property value and compare to valid value
var value = pi.GetValue(item, null);
//if the value is valid, record the object into the dictionary
if (Validators[pi.Name](value))
{
var lookup = PropertyMetrics[pi.Name];
lookup.Add(item);
}
}//end trygetvalue
}
}//end foreach pi
foreach (var metric in PropertyMetrics)
{
Console.WriteLine("Property '{0}' is set in {1} objects in collection",metric.Key,metric.Value.Count);
}
Console.ReadLine();
}
private static List<PropertyInfo> GetProperties(Type T)
{
return T.GetProperties(BindingFlags.Public | BindingFlags.Instance).ToList();
}
}
}
You can get the title count using this linq query:
int titleCount = ORCollection
.Where(x => !string.IsNullOrWhiteSpace(x.title))
.Count();
You could get the count of closed like this:
int closedCount = ORCollection
.Where(x => x.status == status.CLOSED)
.Count();
If you were going to have larger collections or you access the values a lot it might be worth creating a custom collection implementation that stores the field counts, it could then increment/decrement these values as you add and remove items. You could also store a dictionary of status counts in this custom collection that gets updated as you add and remove items.

Categories