Swap list item according to multiple parameters - c#

I have a list like the one below. I want to sort this list according to the rule I specified. If there is an element to which the list element is attached, it must be in the front row.
For example, since the RequiredCourse field of the record with Id 2 is 3, the element with Id 3 must come before the element with Id 2. To do this, I created a swap method as follows. But the record with Id 3 depends on 4 and I couldn't create the sorting properly. Additionally, it depends on 10 with Id 8 and below. But 9 is not connected to anything. Since I changed the place of the records with id 8 and 10, 8 goes after 9.
The sample output should be as follows:
var list = new List<Course>();
list.Add(new Course { Id = 1 });
list.Add(new Course { Id = 2, RequiredCourse = "3" });
list.Add(new Course { Id = 3, RequiredCourse = "4" });
list.Add(new Course { Id = 4 });
list.Add(new Course { Id = 5 });
list.Add(new Course { Id = 6 });
list.Add(new Course { Id = 7 });
list.Add(new Course { Id = 8, RequiredCourse = "10" });
list.Add(new Course { Id = 9 });
list.Add(new Course { Id = 10 });
list = list.OrderBy(i => i.Id).ThenBy(i => i.RequiredCourse).ToList();
var copy = new List<Course>(list);
for (int i = 0; i < copy.Count; i++)
{
if (!string.IsNullOrEmpty(copy[i].RequiredCourse) && Int32.Parse(copy[i].RequiredCourse) > copy[i].Id)
{
var index = list.FindIndex(k => k.Id == Int32.Parse(copy[i].RequiredCourse));
if (index > -1)
{
var temp = list[i];
list[i] = list[index];
list[index] = temp;
}
}
}
Defective Output using code above
1
3 4
4
2 3
5
6
7
10
9
8 10
Expected Output
1
4
3 4
2 3
5
6
7
10
8 10
9

I think this is easy enough to understand.
var list = new List<Course>();
list.Add(new Course { Id = 1 });
list.Add(new Course { Id = 2, RequiredCourse = "3" });
list.Add(new Course { Id = 3, RequiredCourse = "4" });
list.Add(new Course { Id = 4 });
list.Add(new Course { Id = 5 });
list.Add(new Course { Id = 6 });
list.Add(new Course { Id = 7 });
list.Add(new Course { Id = 8, RequiredCourse = "10" });
list.Add(new Course { Id = 9 });
list.Add(new Course { Id = 10 });
var output = new List<Course>();
var toProcess = new SortedDictionary<int, Course>(list.ToDictionary(c => c.Id));
var pendingAdd = new Stack<Course>();
while (toProcess.Count > 0)
{
Course currentCourse = toProcess.First().Value;
if (string.IsNullOrEmpty(currentCourse.RequiredCourse))
{
// Course has no dependency, process it.
output.Add(currentCourse);
toProcess.Remove(currentCourse.Id);
}
else
{
int courseId = currentCourse.Id;
// Course has dependency. Trace down linked courses.
while (toProcess.ContainsKey(courseId) && !string.IsNullOrEmpty(toProcess[courseId].RequiredCourse))
{
pendingAdd.Push(toProcess[courseId]);
courseId = int.Parse(toProcess[courseId].RequiredCourse);
}
// dont forget to add the "top-level" course for the dependency chain
pendingAdd.Push(toProcess[courseId]);
// Add in reverse depdency order using Stack
while (pendingAdd.Count > 0)
{
var course = pendingAdd.Pop();
output.Add(course);
toProcess.Remove(course.Id);
}
}
}
Your wanted list is in output.

The problem is that you want the item "2 - 3" below the "4 - ''". I'm sure there's a way to improve what I've done, but here's version 1.0:
var list = new List<Course>();
list.Add(new Course { Id = 1 });
list.Add(new Course { Id = 2, RequiredCourse = "3" });
list.Add(new Course { Id = 3, RequiredCourse = "4" });
list.Add(new Course { Id = 4 });
list.Add(new Course { Id = 5 });
list.Add(new Course { Id = 6 });
list.Add(new Course { Id = 7 });
list.Add(new Course { Id = 8, RequiredCourse = "10" });
list.Add(new Course { Id = 9 });
list.Add(new Course { Id = 10 });
// isolating items with RequiredCourse
var withRequired = (
from o in list
where !string.IsNullOrWhiteSpace(o.RequiredCourse)
select o
).ToList();
list = list.Except(withRequired).ToList();
// First I arrange the items without "RequiredCourse" with their respective ones.
for (var i = 0; i < list.Count; i++)
{
var primary = list[i];
var parents =
withRequired
.Where(o => o.RequiredCourse == primary.Id.ToString())
.OrderBy(o => o.Id)
.ToList();
var children = (
from o in withRequired.Except(parents)
join o2 in parents
on o.Id.ToString() equals o2.RequiredCourse
into grp
from o3 in grp
orderby o3.Id descending
select o3
).ToList();
if (!parents.Any())
{
continue;
}
i++;
foreach (var p in parents)
{
list.Insert(i++, p);
foreach (var c in children.Where(o => p.Id.ToString() == o.RequiredCourse))
{
list.Insert(i++, c);
}
}
}
// retrieving items that only link to items that have RequiredCourse
var childOfChildren = withRequired.Except(list).ToList();
// Then I arrange items with RequiredCourses that are linked to other items with RequiredCourses.
for (var i = 0; i < list.Count; i++)
{
var primary = list[i];
var children = (
from o in childOfChildren
where primary.Id.ToString() == o.RequiredCourse
orderby o.Id descending
select o
).ToList();
if (!children.Any())
{
continue;
}
i++;
list.InsertRange(i, children);
}
// CONSOLE APPLICATION PART
foreach (var o in list)
{
if (string.IsNullOrEmpty(o.RequiredCourse))
{
o.RequiredCourse = "-";
}
Console.WriteLine($"Course: {o.Id} - {o.RequiredCourse}");
}

Can be done with regression , sample code not tested for other case . only tested for given data .
public static void Main()
{
var list = new List<Course>();
list.Add(new Course { Id = 1 });
list.Add(new Course { Id = 2, RequiredCourse = "3" });
list.Add(new Course { Id = 3, RequiredCourse = "4" });
list.Add(new Course { Id = 4 });
list.Add(new Course { Id = 5 });
list.Add(new Course { Id = 6 });
list.Add(new Course { Id = 7 });
list.Add(new Course { Id = 8, RequiredCourse = "10" });
list.Add(new Course { Id = 9 });
list.Add(new Course { Id = 10 });
list = list.OrderBy(i => i.Id).ThenBy(i => i.RequiredCourse).ToList();
var copy = new List<Course>();
foreach(var tt in list)
{
//Console.WriteLine(tt.Id + " " + tt.RequiredCourse);
populateParent(list,ref copy, tt);
if(!copy.Any(p => p.Id == tt.Id))
copy.Add(tt);
}
foreach(var tt in copy)
{
Console.WriteLine(tt.Id + " " + tt.RequiredCourse);
}
}
public static Course populateParent(List<Course> inputList,ref List<Course> outputList, Course current)
{
if(string.IsNullOrWhiteSpace(current.RequiredCourse))
{
if(!outputList.Any(p => p.Id == current.Id))
outputList.Add(current);
return current;
}
else
{
var tt = inputList.First(p => current.RequiredCourse != null && p.Id == int.Parse(current.RequiredCourse) );
var parent = populateParent(inputList,ref outputList,tt);
if(!outputList.Any(p => p.Id == parent.Id))
outputList.Add(parent);
if(!outputList.Any(p => p.Id == tt.Id))
outputList.Add(tt);
return tt;
}
}
code in dotnetfiddle

Related

Accessing a List multiple times in same code block - Any better approach?

I need to set the values of multiple labels from a single list based on different conditions. Here is my code:
List<RFPModel> lst = RFPModel.GetAll(); //ALL
if(lst.Count>0)
{
lblActive.InnerText = lst.Count(a => a.StatusID == (int)ProjectStatus.Project_Active).ToString();
lblCompleted.InnerText = lst.Count(a => a.StatusID == (int)ProjectStatus.Project_Completed).ToString();
lblProposal.InnerText = lst.Count(a => a.StatusID == (int)ProjectStatus.Proposal_UnderProcess).ToString();
lblProposalsRej.InnerText = lst.Count(a => a.StatusID == (int)ProjectStatus.Proposal_Rejected).ToString();
lblRFPRec.InnerText= lst.Count(a => a.StatusID == (int)ProjectStatus.RFP_Submitted).ToString();
lblRFPRef.InnerText= lst.Count(a => a.StatusID == (int)ProjectStatus.RFP_Rejected).ToString();
lblRFPApp.InnerText = lst.Count(a => a.StatusID == (int)ProjectStatus.RFP_Approved).ToString();
}
I am afraid that this approach may affect the performance as everytime you need a value you traverse the list. Any suggestion for the better approach will be highly appreciated.
Use a GroupBy (preferably at the db IQueryable level), then for extra flavor you can to ToDictionary to make the look up more efficient
var dict = lst.GroupBy(x => x.StatusID)
.Select(x => new { x.Key, count = x.Count() })
.ToDictionary(x => (ProjectStatus)x.Key, x => x.count.ToString());
lblActive.InnerText = dict[ProjectStatus.Project_Active];
lblCompleted.InnerText = dict[ProjectStatus.Project_Completed];
...
The assumption is the StatusID maps one to one with ProjectStatus
Note the additional Select, is superfluous however is added for in-case you just wanted a list
Additional Resources
Enumerable.GroupBy Method
Groups the elements of a sequence.
Enumerable.Select Method
Projects each element of a sequence into a new form.
Enumerable.ToDictionary Method
Creates a Dictionary from an IEnumerable.
You can do something like this, only looping through the list once (here I only show 4 counts. You can easily add the rest yourself):
int activeCount = 0;
int completedCount = 0;
int proposalCount = 0;
int proposalRejCount = 0;
foreach (var item in lst) {
if (item.StatusID == (int)ProjectStatus.Project_Active)
activeCount++;
else if (item.StatusID == (int)ProjectStatus.Project_Completed)
completedCount++;
else if (item.StatusID == (int)ProjectStatus.Project_UnderProcess)
proposalCount++;
else if (item.StatusID == (int)ProjectStatus.Project_Proposal_Rejected)
proposalRejCount++;
}
lblActive.InnerText = activeCount.ToString();
lblCompleted.InnerText = completedCount.ToString();
lblProposal.InnerText = proposalCount.ToString();
lblProposalRej.InnerText = proposalRejCount.ToString();
But only do this if this particular code is actually causing a performance problem. Don't optimise prematurely.
You could easily use ToLookup like this:
List<RFPModel> lst = new List<RFPModel>();
lst.Add(new RFPModel { StatusID = 1 });
lst.Add(new RFPModel { StatusID = 1 });
lst.Add(new RFPModel { StatusID = 1 });
lst.Add(new RFPModel { StatusID = 2 });
lst.Add(new RFPModel { StatusID = 2 });
lst.Add(new RFPModel { StatusID = 3 });
lst.Add(new RFPModel { StatusID = 4 });
lst.Add(new RFPModel { StatusID = 4 });
lst.Add(new RFPModel { StatusID = 4 });
lst.Add(new RFPModel { StatusID = 4 });
lst.Add(new RFPModel { StatusID = 5 });
var lookup = lst.ToLookup(p => p.StatusID);
var status1Count = lookup[1].Count(); // 3
var status2Count = lookup[2].Count(); // 2
var status3Count = lookup[3].Count(); // 1
var status4Count = lookup[4].Count(); // 4
var status5Count = lookup[5].Count(); // 1
Replace the x in the lookup[x] part with the appropriate enums.
Lookup would be a good solution to have.
List<RFPModel> lst = RFPModel.GetAll(); //ALL
var lookup = lst.ToLookup(x=>x.StatusID);
lblActive.InnerText = lookup[(int)ProjectStatus.Project_Active].Count();
lblCompleted.InnerText = lookup[(int)ProjectStatus.Project_Completed].Count();
and so on ......
You can read more on Lookup here

Convert Objects into Hierarchy based on Number Sequence

I have following data Structure and i want to put covert it in a Hierarchy based on RelationId. This is sorted on RelationId.
Id = 2 has relationId =2 and following two rows has realtionId =0 . That represent the Id=3 and Id=4 are child of Id = 2
Id Name RelationId SortOrder
1 A 1 1
2 B 2 2
3 C 0 3
4 D 0 4
5 E 3 5
6 F 0 6
7 G 0 7
8 H 4 8
End Result would be like following
Id = 1
|
Id = 2
|___ Id = 3 , Id = 4
Id = 5
|___ Id= 6 , Id=7
Id = 8
The desired result is as following (for simplicity representing it as List). This would be a List<Something> in C#
Result =
[
{ Id = 1, Name = A, Children = Null },
{ Id = 2, Name = B, Children = [{ Id = 3, Name = C }, {Id = 4, Name = D }] },
{ Id = 5, Name = E, Children = [{ Id = 6, Name = F }, {Id = 7, Name = G }] },
{ Id = 8, Name = H}
]
My unsuccessful attempt is as following
var finalResult = new List<sampleDataClass>();
var sampleData = GetMeSampleData();
var count = sampleData.Count();
foreach (var item in sampleData)
{
var alreadyExist = finalResult.Any(x => x.Id == item.Id);
var newObject = new sampleDataClass();
if (!alreadyExist && item.RelationId!= 0)
{
newObject = item;
}
for (int i = item.SortOrder; i < count; i++)
{
if (sampleData[i].RelationId== 0)
{
newObject.Children.Add(sampleData[i]);
}
}
finalResult.Add(newObject );
}
Since your RelationId decides whether it is a root or nested element, you could form a group based on these relation and do this. I would suggest using Linq
List<SomeData> somedata = ... // your data.
int index=0;
var results = somedata
.Select(x=> new {gid = x.RelationId ==0? index: ++index, item=x})
.GroupBy(x=> x.gid)
.Select(x=> {
var first = x.FirstOrDefault();
return new
{
Id = first.item.Id,
Name = first.item.Name,
Children = x.Skip(1).Select(s=> new {
Id = s.item.Id,
Name = s.item.Name,
})
};
})
.ToList();
Output :
Id=1, Name=A
Id=2, Name=B
Id=3, Name=C
Id=4, Name=D
Id=5, Name=E
Id=6, Name=F
Id=7, Name=G
Id=8, Name=H
Check this Working Code
I have done it like this. Don't know if there can be some more elegant solution
var data = new List<MyDataObject>();
var SampleData = GetMeSampleData;
var count = SampleData.Count();
for (int i=0;i<count;i++)
{
var rootAdded = false;
var relationId = SampleData[i].relationId;
var alreadyExist = data.Any(x => x.Id == SampleData[i].Id);
var mydataObject = new MyDataObject();
if (!alreadyExist && SampleData[i].RelationId != 0)
{
mydataObject = SampleData[i];
rootAdded = true;
}
for(int j=i+1;j<count;j++)
{
if ((SampleData[j].RelationId == 0 && rootAdded))
{
mydataObject.Children.Add(SampleData[j]);
}
if (SampleData[j].SubjectId != 0)
break;
}
if (rootAdded)
{
data.Add(mydataObject);
}

Remove item from List C#

I have two list of Objects and I have to delete to the 'original' list the values deleted in the other list; but the item is identified by two properties.
I was able to do so, when the object was only identified by one property, but now I need to check on two properties
// library: object with deleted data
// library = new List<Widget>() { new Widget() { Id = "1", Nbr = 1 }, new Widget() { Id = "3", Nbr = 2 } };
var allData = GetData();
// allData = new List<Widget>() { new Widget() { Id = "1", Nbr = 1 }, new Widget() { Id = "2", Nbr = 1 }, new Widget() { Id = "3", Nbr = 2 } };
// var itemsToDelete = allData.Where(w => library.All(p => p.Id != w.Id)).ToList(); // I would do this, if the identifier would be only Id
var itemsToDelete = allData.Where(w => library.All(p => p.Id != w.Id && p.Nbr != w.Nbr)).ToList(); // I need to check for two properties and I'm getting zero coincidences
var itemsToDelete = allData.Where(w => !library.Any(p => p.Id == w.Id && p.Nbr == w.Nbr)).ToList();
Should be right, but not optimal. (O^2)

Using LINQ, how would you filter out all but one item of a particular criteria from a list?

I realize my title probably isn't very clear so here's an example:
I have a list of objects with two properties, A and B.
public class Item
{
public int A { get; set; }
public int B { get; set; }
}
var list = new List<Item>
{
new Item() { A = 0, B = 0 },
new Item() { A = 0, B = 1 },
new Item() { A = 1, B = 0 },
new Item() { A = 2, B = 0 },
new Item() { A = 2, B = 1 },
new Item() { A = 2, B = 2 },
new Item() { A = 3, B = 0 },
new Item() { A = 3, B = 1 },
}
Using LINQ, what's the most elegant way to collapse all the A = 2 items into the first A = 2 item and return along with all the other items? This would be the expected result.
var list = new List<Item>
{
new Item() { A = 0, B = 0 },
new Item() { A = 0, B = 1 },
new Item() { A = 1, B = 0 },
new Item() { A = 2, B = 0 },
new Item() { A = 3, B = 0 },
new Item() { A = 3, B = 1 },
}
I'm not a LINQ expert and already have a "manual" solution but I really like the expressiveness of LINQ and was curious to see if it could be done better.
How about:
var collapsed = list.GroupBy(i => i.A)
.SelectMany(g => g.Key == 2 ? g.Take(1) : g);
The idea is to first group them by A and then select those again (flattening it with .SelectMany) but in the case of the Key being the one we want to collapse, we just take the first entry with Take(1).
One way you can accomplish this is with GroupBy. Group the items by A, and use a SelectMany to project each group into a flat list again. In the SelectMany, check if A is 2 and if so Take(1), otherwise return all results for that group. We're using Take instead of First because the result has to be IEnumerable.
var grouped = list.GroupBy(g => g.A);
var collapsed = grouped.SelectMany(g =>
{
if (g.Key == 2)
{
return g.Take(1);
}
return g;
});
One possible solution (if you insist on LINQ):
int a = 2;
var output = list.GroupBy(o => o.A == a ? a.ToString() : Guid.NewGuid().ToString())
.Select(g => g.First())
.ToList();
Group all items with A=2 into group with key equal to 2, but all other items will have unique group key (new guid), so you will have many groups having one item. Then from each group we take first item.
Yet another way:
var newlist = list.Where (l => l.A != 2 ).ToList();
newlist.Add( list.First (l => l.A == 2) );
An alternative to other answers based on GroupBy can be Aggregate:
// Aggregate lets iterate a sequence and accumulate a result (the first arg)
var list2 = list.Aggregate(new List<Item>(), (result, next) => {
// This will add the item in the source sequence either
// if A != 2 or, if it's A == 2, it will check that there's no A == 2
// already in the resulting sequence!
if(next.A != 2 || !result.Any(item => item.A == 2)) result.Add(next);
return result;
});
What about this:
list.RemoveAll(l => l.A == 2 && l != list.FirstOrDefault(i => i.A == 2));
if you whould like more efficient way it would be:
var first = list.FirstOrDefault(i => i.A == 2);
list.RemoveAll(l => l.A == 2 && l != first);

Select top N records after filtering in each group

I am an old bee in .NET but very new to Linq! After some basic reading I have decided to check my skill and I failed completely! I don't know where I am making mistake.
I want to select highest 2 order for each person for while Amount % 100 == 0.
Here is my code.
var crecords = new[] {
new {
Name = "XYZ",
Orders = new[]
{
new { OrderId = 1, Amount = 340 },
new { OrderId = 2, Amount = 100 },
new { OrderId = 3, Amount = 200 }
}
},
new {
Name = "ABC",
Orders = new[]
{
new { OrderId = 11, Amount = 900 },
new { OrderId = 12, Amount = 800 },
new { OrderId = 13, Amount = 700 }
}
}
};
var result = crecords
.OrderBy(record => record.Name)
.ForEach
(
person => person.Orders
.Where(order => order.Amount % 100 == 0)
.OrderByDescending(t => t.Amount)
.Take(2)
);
foreach (var record in result)
{
Console.WriteLine(record.Name);
foreach (var order in record.Orders)
{
Console.WriteLine("-->" + order.Amount.ToString());
}
}
Can anyone focus and tell me what would be correct query?
Thanks in advance
Try this query:
var result = crecords.Select(person =>
new
{
Name = person.Name,
Orders = person.Orders.Where(order => order.Amount%100 == 0)
.OrderByDescending(x => x.Amount)
.Take(2)
});
Using your foreach loop to print the resulting IEnumerable, the output of it is:
XYZ
-->200
-->100
ABC
-->900
-->800
This has already been answered but if you didn't want to create new objects and simply modify your existing crecords, the code would look like this alternatively. But you wouldn't be able to use anonymous structures like shown in your example. Meaning you would have to create People and Order classes
private class People
{
public string Name;
public IEnumerable<Order> Orders;
}
private class Order
{
public int OrderId;
public int Amount;
}
public void PrintPeople()
{
IEnumerable<People> crecords = new[] {
new People{
Name = "XYZ",
Orders = new Order[]
{
new Order{ OrderId = 1, Amount = 340 },
new Order{ OrderId = 2, Amount = 100 },
new Order{ OrderId = 3, Amount = 200 }
}
},
new People{
Name = "ABC",
Orders = new Order[]
{
new Order{ OrderId = 11, Amount = 900 },
new Order{ OrderId = 12, Amount = 800 },
new Order{ OrderId = 13, Amount = 700 }
}
}
};
crecords = crecords.OrderBy(record => record.Name);
crecords.ToList().ForEach(
person =>
{
person.Orders = person.Orders
.Where(order => order.Amount%100 == 0)
.OrderByDescending(t => t.Amount)
.Take(2);
}
);
foreach (People record in crecords)
{
Console.WriteLine(record.Name);
foreach (var order in record.Orders)
{
Console.WriteLine("-->" + order.Amount.ToString());
}
}
}

Categories