Related
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
I have the below set of data
Where each City belongs to a specific Department, which belongs to a specific Region, which belongs to a specific Country (in this case there is only one country: France).
This data is contained in a CSV file which I can read from on a row-by-row basis, however my goal is to convert this data into a tree structure (with France being at the root).
Each of these nodes will be given a specific Id value, which is something I've already gone and done, but the tricky part is that each node here must also contain a ParentId (for instance Belley and Gex need the ParentId of Ain, but Moulins and Vichy need the ParentId of Aller).
Below is a snippet of code I've written that has assigned an Id value to each name in this data set, along with some other values:
int id = 0;
List<CoverageAreaLevel> coverageAreas = GetCoverageAreaDataFromCsv(path, true);
List<LevelList> levelLists = new List<LevelList>
{
new LevelList { Names = coverageAreas.Select(a => a.Level1).Distinct().ToList(), Level = "1" },
new LevelList { Names = coverageAreas.Select(a => a.Level2).Distinct().ToList(), Level = "2" },
new LevelList { Names = coverageAreas.Select(a => a.Level3).Distinct().ToList(), Level = "3" },
new LevelList { Names = coverageAreas.Select(a => a.Level4).Distinct().ToList(), Level = "4" }
};
List<CoverageArea> newCoverageAreas = new List<CoverageArea>();
foreach (LevelList levelList in levelLists)
{
foreach (string name in levelList.Names)
{
CoverageArea coverageArea = new CoverageArea
{
Id = id++.ToString(),
Description = name,
FullDescription = name,
Level = levelList.Level
};
newCoverageAreas.Add(coverageArea);
}
}
The levelLists variable contains a sort-of heirarchical structure of the data that I'm looking for, but none of the items in that list are linked together by anything.
Any idea of how this could be implemented? I can manually figure out each ParentId, but I'd like to automate this process, especially if this needs to be done in the future.
The solution from #Camilo is really good and pragmatic. I would also suggest the use of a tree.
A sample implementation:
var countries = models.GroupBy(xco => xco.Country)
.Select((xco, index) =>
{
var country = new Tree<String>();
country.Value = xco.Key;
country.Children = xco.GroupBy(xr => xr.Region)
.Select((xr, xrIndex) =>
{
var region = new Tree<String>();
region.Value = xr.Key;
region.Parent = country;
region.Children =
xr.GroupBy(xd => xd.Department)
.Select((xd, index) =>
{
var department = new Tree<String>();
department.Value = xd.Key;
department.Parent = region;
department.Children = xd
.Select(xc => new Tree<String> { Value = xc.City, Parent = department });
return department;
});
return region;
});
return country;
});
public class Tree<T>
{
public IEnumerable<Tree<T>> Children;
public T Value;
public Tree<T> Parent;
}
One way you could solve this is by building dictionaries with the names and IDs of each level.
Assuming you have data like this:
var models = new List<Model>
{
new Model { Country = "France", Region = "FranceRegionA", Department = "FranceDept1", City = "FranceA" },
new Model { Country = "France", Region = "FranceRegionA", Department = "FranceDept1", City = "FranceB" },
new Model { Country = "France", Region = "FranceRegionA", Department = "FranceDept2", City = "FranceC" },
new Model { Country = "France", Region = "FranceRegionB", Department = "FranceDept3", City = "FranceD" },
new Model { Country = "Italy", Region = "ItalyRegionA", Department = "ItalyDept1", City = "ItalyA" },
new Model { Country = "Italy", Region = "ItalyRegionA", Department = "ItalyDept2", City = "ItalyB" },
};
You could do something like this, which can probably be improved further if needed:
var countries = models.GroupBy(x => x.Country)
.Select((x, index) => Tuple.Create(x.Key, new { Id = index + 1 }))
.ToDictionary(x => x.Item1, x => x.Item2);
var regions = models.GroupBy(x => x.Region)
.Select((x, index) => Tuple.Create(x.Key, new { ParentId = countries[x.First().Country].Id, Id = index + 1 }))
.ToDictionary(x => x.Item1, x => x.Item2);
var departments = models.GroupBy(x => x.Department)
.Select((x, index) => Tuple.Create(x.Key, new { ParentId = regions[x.First().Region].Id, Id = index + 1 }))
.ToDictionary(x => x.Item1, x => x.Item2);
var cities = models
.Select((x, index) => Tuple.Create(x.City, new { ParentId = departments[x.Department].Id, Id = index + 1 }))
.ToDictionary(x => x.Item1, x => x.Item2);
The main idea is to leverage the index parameter of the Select method and the speed of dictionaries to find the parent ID.
Sample output from a fiddle:
countries:
[France, { Id = 1 }],
[Italy, { Id = 2 }]
regions:
[FranceRegionA, { ParentId = 1, Id = 1 }],
[FranceRegionB, { ParentId = 1, Id = 2 }],
[ItalyRegionA, { ParentId = 2, Id = 3 }]
departments:
[FranceDept1, { ParentId = 1, Id = 1 }],
[FranceDept2, { ParentId = 1, Id = 2 }],
[FranceDept3, { ParentId = 2, Id = 3 }],
[ItalyDept1, { ParentId = 3, Id = 4 }],
[ItalyDept2, { ParentId = 3, Id = 5 }]
cities:
[FranceA, { ParentId = 1, Id = 1 }],
[FranceB, { ParentId = 1, Id = 2 }],
[FranceC, { ParentId = 2, Id = 3 }],
[FranceD, { ParentId = 3, Id = 4 }],
[ItalyA, { ParentId = 4, Id = 5 }],
[ItalyB, { ParentId = 5, Id = 6 }]
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);
}
I have 2 lists of the same type.
List 1:
ID
Name
Value
1,"Prod1", 0
2,"Prod2", 50
3,"Prod3", 0
List 2:
ID
Name
Value
1,"Prod1", 25
2,"Prod2", 100
3,"Prod3", 75
I need to combine these 2 lists into 1, but I only want the values from list2 if the corresponding value from list1 == 0
So my new list should look like this:
1,"Prod1", 25
2,"Prod2", 50
3,"Prod3", 75
I've tried many variations of something like this:
var joined = from l1 in List1.Where(x=>x.Value == "0")
join l2 in List2 on l1.ID equals l2.ID into gj
select new { gj };
I've also tried a variation of the concat
What is the best way of doing this?
You just need to select the individual properties and conditionally select either the Value from the first or second list item.
var List1 = new[]
{
new { Name = "Prod1", Id = 1, Value = 0 },
new { Name = "Prod2", Id = 2, Value = 50 },
new { Name = "Prod3", Id = 3, Value = 0 },
new { Name = "NotInList2", Id = 4, Value = 0}
};
var List2 = new[]
{
new { Name = "Prod1", Id = 1, Value = 25 },
new { Name = "Prod2", Id = 2, Value = 100 },
new { Name = "Prod3", Id = 3, Value = 75 }
};
var results = from l1 in List1
join l2temp in List2 on l1.Id equals l2temp.Id into grpj
from l2 in grpj.DefaultIfEmpty()
select new
{
l1.Id,
l1.Name,
Value = l1.Value == 0 && l2 != null ? l2.Value : l1.Value
};
foreach(var item in results)
Console.WriteLine(item);
Will output
{ Id = 1, Name = Prod1, Value = 25 }
{ Id = 2, Name = Prod2, Value = 50 }
{ Id = 3, Name = Prod3, Value = 75 }
{ Id = 4, Name = NotInList2, Value = 0 }
NOTE: This assumes that you only want all the ids that are in List1 (not any that are only in List2) and that the ids are unique and that the Name from List1 is what you want even if it is different in List2.
clone l1 and
foreach (var item in l1Clone)
if (item.value == 0)
item.value == l2.FirstOrDefault(l2item => l2item.ID == item.ID)
Refer to the code below:
IEnumerable<item> join_lists(IEnumerable<item> list1, IEnumerable<item> list2)
{
var map = list2.ToDictionary(i => i.id);
return list1.Select(i => new item()
{
id = i.id,
name = i.name,
value = i.value == 0 ? map[i.id].value : i.value
});
}
You could use Zip:
var combined = list1
.Zip(list2, (product1, product2) => product1.Value == 0 ? product2 : product1);
I have the following list of TestParam... This is just a parameter list that is doing to determine how a query is going to be run. In the following case, the expected result would be to be executed against all the combinations of different parameters. Hence, a list of lists, with CustomerId 33 together with each product Id available in the list...
List<TestParam> testList = new List<TestParam>();
testList.Add(new TestParam() { Name = "CustomerId", Value = "33" });
testList.Add(new TestParam() { Name = "ProductId", Value = "1" });
testList.Add(new TestParam() { Name = "ProductId", Value = "2" });
testList.Add(new TestParam() { Name = "ProductId", Value = "3" });
testList.Add(new TestParam() { Name = "ProductId", Value = "4" });
testList.Add(new TestParam() { Name = "ProductId", Value = "5" });
testList.Add(new TestParam() { Name = "ProductId", Value = "6" });
testList.Add(new TestParam() { Name = "ProductId", Value = "7" });
testList.Add(new TestParam() { Name = "ProductId", Value = "8" });
TestParam is a normal encapsulated parameter class having a name and a value...
public class TestParam
{
public string Name { get; set; }
public string Value { get; set; }
}
The end result would be a list of lists, having CustomerId 33, with all the rest of the products. The same result would be acquired if I had different names and values in the list of TestParam (the above is just an example).
The following code, ends up with several lists depending on the combinations of the list above...
// First get a list of distinct unique param collections...
List<string> distinctParameterNames = new List<string>();
testList.GroupBy(x => x.Name).ForEach(paramName => {
distinctParameterNames.Add(paramName.Key);
});
// Get counts
List<int> combinationList = new List<int>();
foreach (var x in distinctParameterNames) {
combinationList.Add(testList.Where(y=>y.Name == x).Count());
}
// Will contain 2 lists, one having all combinations of parameters named CustomerId, and another with ProductId combinations...
List<List<TestParam>> parameterList = new List<List<TestParam>>();
foreach (var x in distinctParameterNames) {
// Loop
List<TestParam> parameter = new List<TestParam>();
testList.Where(paramName => paramName.Name == x).ForEach(y =>
{
parameter.Add(new TestParam() { Name = y.Name, Value = y.Value });
});
parameterList.Add(parameter);
}
It would be an intersect between the list, and the end result will be a list of lists, and each list will have the combinations below... So a run would return (in this case) :
Customer 33, Product Id 1
Customer 33, Product Id 2
Customer 33, Product Id 3
Customer 33, Product Id 4
Customer 33, Product Id 5
Customer 33, Product Id 6
Customer 33, Product Id 7
Customer 33, Product Id 8
What would be the most efficient and generic way to do this?
The following is the solution that I was looking for...
public static List<List<T>> AllCombinationsOf<T>(params List<T>[] sets)
{
// need array bounds checking etc for production
var combinations = new List<List<T>>();
// prime the data
foreach (var value in sets[0])
combinations.Add(new List<T> { value });
foreach (var set in sets.Skip(1))
combinations = AddExtraSet(combinations, set);
return combinations;
}
private static List<List<T>> AddExtraSet<T>
(List<List<T>> combinations, List<T> set)
{
var newCombinations = from value in set
from combination in combinations
select new List<T>(combination) { value };
return newCombinations.ToList();
}
Usage (continues with my code snippet of the question itself) :
var intersection = AllCombinationsOf(parameterList.ToArray());
get all the list of customer first like this
var customers = from a in testlist where a.name='customerid'
select a;
var products = from a in testlist where a.name='productid'
select a;
then loop customers
for(var c in customers)
{
loop products
for(var p in products)
{
var customerproducts = new CustomerProducts{
Customer = c.Name +' ' + c.Value
Product = p.Name + ' ' + p.value
};
then add it into a list
}
}
The list needs to be grouped by Name, then it can be joined several times depending on count of groups:
var groups = testList.GroupBy(_ => _.Name);
IEnumerable<IEnumerable<TestParam>> result = null;
foreach (var g in groups)
{
var current = g.Select(_ => new[] { _ });
if (result == null)
{
result = current;
continue;
}
result = result.Join(current, _ => true, _ => true, (actual, c) => actual.Concat(c));
}
// check result
foreach (var i in result)
{
Console.WriteLine(string.Join(", ", i.Select(_ => string.Format("{0}-{1}", _.Name, _.Value))));
}