C# converting a list to a hierarchy - c#

Hej hej,
i'm currently working on a c# wpf project where i want to dynamically populate a TreeView from a linear list of Items.
My starting point was this article on codeproject:
https://www.codeproject.com/Articles/26288/Simplifying-the-WPF-TreeView-by-Using-the-ViewMode
John Smith shows how to use the HierarchalDataTemplate withing TreeViews. So far this is working. My problem is to dynamically generate a hierarchical Tree from a linear list of items. I have tried to adapt the solutions found here
Mapping a flat list to a hierarchical list with parent IDs C#
and here
TreeView directories in C# WPF
but somehow i did not succeed.
My item class looks like this:
public class Item
{
public string Name { get; set; }
public ItemPath[] ParentPath { get; set; }
public ItemPath[] Path { get; set; }
}
ItemPath class
public class ItemPath
{
public int Level { get; set; }
public string Name { get; set; }
}
Targeted hierarchial class
public class ItemTree
{
public string Name { get; set; }
public Item Item { get; set; }
public List<ItemTree> ChildTree { get; set; }
}
I am using this flat list to test my methods:
var items = new List<Item>()
{
new Item()
{
Name = "item 0",
Path = new ItemPath[]
{
new ItemPath() { Name = "Red", Level = 1 },
}
},
new Item()
{
Name = "item 1",
Path = new ItemPath[]
{
new ItemPath() { Name = "Red", Level = 1 },
new ItemPath() { Name = "Green", Level = 2 },
}
},
new Item()
{
Name = "item 2",
Path = new ItemPath[]
{
new ItemPath() { Name = "Red", Level = 1 },
new ItemPath() { Name = "Violet", Level = 2 },
}
},
new Item()
{
Name = "item 3",
Path = new ItemPath[]
{
new ItemPath() { Name = "Blue", Level = 1 },
new ItemPath() { Name = "Black", Level = 2 },
}
},
new Item()
{
Name = "item 4",
Path = new ItemPath[]
{
new ItemPath() { Name = "Blue", Level = 1 },
new ItemPath() { Name = "Green", Level = 2 },
}
},
new Item()
{
Name = "item 5",
Path = new ItemPath[]
{
new ItemPath() { Name = "Red", Level = 1 },
new ItemPath() { Name = "Green", Level = 2 },
}
},
};
My goal is to have a hierarchy like this
[Red]
item 0
[Green]
item 1
item 5
[Violet]
item 2
[Blue]
[Black]
item 2
[Green]
item 4
My current attempt is below. It's the rewritten version of the stackoverflow post from above:
// class to create dummy data as described above
var dummyData = new DummyData();
var items = dummyData.CreateTier2DummyList();
var cat = items.Select(r => new ItemTree()
{
Path = r.Path,
Item = r,
// parent path is generated dynamically
ParentPath = r.Path.Reverse().Skip(1).Reverse().ToArray(),
}).ToList();
var lookup = cat.ToLookup(c => c.ParentPath);
foreach (var c in cat)
{
if (lookup.Contains(c.Path))
c.ChildTree = lookup[c.ParentPath].ToList();
}
Somehow i think that using an array as the path and parent path is not a good idea. But it reflects an absolute path (comparable to a file path in a file system).

Related

How to get same results from select as foreach when using GroupBy()

I would like to be able to attain the same results that I can get by using foreach on a grouping when using the select method and an anonymous method.
public class ExportData
{
public int Id { get; set; }
public string Colour { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public int Money { get; set; }
}
public class ExportDataDictionary
{
public IDictionary<string, object> ColumnData { get; set; } = new Dictionary<string, object>();
}
Given the two classes above as an example.
I create some data..
var dataCollection = new List<ExportData>
{
new ExportData { Name = "Name1", Age = 1, Colour = "Blue", Id = 1, Money = 10 },
new ExportData { Name = "Name1", Age = 2, Colour = "Red", Id = 2, Money = 20 },
new ExportData { Name = "Name1", Age = 2, Colour = "Green", Id = 3, Money = 30 },
new ExportData { Name = "Name2", Age = 1, Colour = "Yellow", Id = 4, Money = 40 },
new ExportData { Name = "Name3", Age = 2, Colour = "Blue", Id = 5, Money = 50 },
new ExportData { Name = "Name4", Age = 3, Colour = "Blue", Id = 6, Money = 10 }
};
Next I group this data by, for example, two properties as follows..
var dataGrouping = dataCollection.GroupBy(g => new { g.Name, g.Age });
I then create a list of ExportDataDictionaries and foreach through each group in the grouping, creating a new ExportDataDictionary each time and adding both of the keys to the dictionary.
var data = new List<ExportDataDictionary>();
foreach (var grouping in dataGrouping)
{
var datadictionary = new ExportDataDictionary();
datadictionary.ColumnData.Add("NAME", grouping.Key.Name);
datadictionary.ColumnData.Add("AGE", grouping.Key.Age);
data.Add(datadictionary);
}
The result is a collection of 5 ExportDataDictionaries with 2 Columns in each one that contain the pair of keys that correspond to each of the groupings.
My attempt to achieve the same with the Select method is shown below.
var data2 = new List<ExportDataDictionary>();
var mydata = dataGrouping.Select(d =>
{
var datadictionary = new ExportDataDictionary();
datadictionary.ColumnData.Add("NAME", d.Key.Name);
datadictionary.ColumnData.Add("AGE", d.Key.Age);
data2.Add(datadictionary);
return data2;
});
The result is of the type:
mydata = {System.Linq.Enumerable.WhereSelectEnumerableIterator<System.Linq.IGrouping<<>f__AnonymousType0<string, int>, ConsoleApp2.Program.ExportData>, System.Collections.Generic.List<ConsoleApp2.Program.ExportDataDictionary>>}
and it contains 5 items and each item contains 10 dictionaries. The 5 dictionaries that I expect are there with the same values as when using foreach but then there are 2 copies of each. I believe that this must be because it is creating the dictionaries for both of the keys used in the grouping. So, I am wondering how to only do this for one of the keys or just each group in the collection?
The requirement is that mydata should contain the same result as obtained by foreach in data variable
Any help much appreciated :)
Just Add .ToList() at the end of your last statement remove the data2.Add(datadictionary); statement and only return the datadictionary return datadictionary; like this
var mydata = dataGrouping.Select(d =>
{
var datadictionary = new ExportDataDictionary();
datadictionary.ColumnData.Add("NAME", d.Key.Name);
datadictionary.ColumnData.Add("AGE", d.Key.Age);
return datadictionary;
}).ToList();
I have run your code and checked and saw that mydata contains 5 items, and each item contains 2 ColumnData members.
Actually, your Linq query is only executed when you call the .ToList() function

(List<Dictionary<Object, Object>> in Linq to extract data

I have a data definition
I Deserialize JSON to this object
#return is JSON
JsonConvert.DeserializeObject<List<Dictionary<Object, Object>>>(utils.RemoveJsonOuterClass("GetTable", JsonConvert.DeserializeObject(#return).ToString()));
olist = [
[{
"item": 1
"Name "One"
}],
[{
"item": 2
"Name "Two"
}],
[{
"item": 1
"Name "One Two"
}]
];
This is a List<Dictionary<Object, Object>>
I need to find all of the items where "item" == 1.
Can I Use Linq? or is there any other way while using a large amount of data?
First: Your json is not correct fix that.
A colon should be present between Name and value.
A comma should be present after item value
and then change your code as below
//Create a class matching response object
public class ResponseItem
{
[JsonProperty("item")]
public int Item { get; set; }
public string Name { get; set; }
}
var responseJson = utils.RemoveJsonOuterClass("GetTable",
JsonConvert.DeserializeObject(#return).ToString();
var responseData = Newtonsoft.Json.JsonConvert.DeserializeObject<List<List<ResponseItem, ResponseItem>>>(responseJson);
Then use foreach with Where and apply condition
foreach (var responseObject in responseData.Where(x=>x.First().Item.Equals(1)))
{
}
Where is deferred execution and on each loop, it returns an object.
Here is the screenshot of my local execution.
Don't know if u're right with the object type. But the task is easy to solve:
static void Main(string[] args)
{
// Build the object
List<Dictionary<int, TestObject>> list = new List<Dictionary<int, TestObject>>();
// fill it with dictionaries
list.Add(new List<TestObject>()
{
new TestObject(){ Id = 1, Name = "One" },
new TestObject() { Id = 2, Name = "Two" },
new TestObject() { Id = 3, Name = "Three" }
}.ToDictionary(d => d.Id));
list.Add(new List<TestObject>()
{
new TestObject() { Id = 2, Name = "Two" },
new TestObject() { Id = 3, Name = "Three" }
}.ToDictionary(d => d.Id));
list.Add(new List<TestObject>()
{
new TestObject(){ Id = 1, Name = "One" },
new TestObject() { Id = 2, Name = "Two" }
}.ToDictionary(d => d.Id));
// Let's build a single list to work with
IEnumerable<TestObject> completeList = list.SelectMany(s => s.Values);
// aaaand filter it
IEnumerable<TestObject> filteredList = completeList.Where(l => l.Id == 1);
}
public class TestObject
{
public int Id { get; set; }
public string Name { get; set; }
}
Most part is initialization ;-)

Map value to item from list and add the new value to the same list C#

I have an Array of colors viz.
var colorPallete = new string[]{color1, color2, color3, color4, color5};
I also have a list of objects which contains an ID.
eg. var previousList<MyModel> = new List<MyModel>();
MyModel.cs
public class MyModel()
{
public int ID {get; set;}
public string Class{get; set;}
public string Name {get; set;}
public string Color {get; set;}
}
I want to assign the objects with same ID with a certain color. And then add the assigned color as a new value to the list.
for eg:
Previous list :-
ID :1
Name: abc
Class: Senior
ID :2
Name: xyz
Class: Medium
ID :3
Name: pqr
Class: junior
ID :1
Name: mno
Class: junior
New List :-
ID :1
Name: abc
Class: Senior
Color :color1
ID :2
Name: xyz
Class: Medium
Color :color2
ID :3
Name: pqr
Class: junior
Color :color3
ID :1
Name: mno
Class: junior
Color :color1
This works for me:
var colorPallete = new string[]
{
"color1", "color2", "color3", "color4", "color5",
};
var previousList = new []
{
new { ID = 1, Name = "abc", Class = "Senior", },
new { ID = 2, Name = "xyz", Class = "Medium", },
new { ID = 3, Name = "pqr", Class = "junior", },
new { ID = 1, Name = "mno", Class = "junior", },
};
var newList =
previousList
.Select(x => new
{
x.ID,
x.Name,
x.Class,
Color = colorPallete.ElementAtOrDefault(x.ID - 1),
})
.ToList();
I get this result:
With the question update providing the class MyModel the code can then be written like so:
var colorPallete = new string[]
{
"color1", "color2", "color3", "color4", "color5",
};
var previousList = new List<MyModel>()
{
new MyModel() { ID = 1, Name = "abc", Class = "Senior", },
new MyModel() { ID = 2, Name = "xyz", Class = "Medium", },
new MyModel() { ID = 3, Name = "pqr", Class = "junior", },
new MyModel() { ID = 1, Name = "mno", Class = "junior", },
};
var newList =
previousList
.Select(x => new MyModel()
{
ID = x.ID,
Name = x.Name,
Class = x.Class,
Color = colorPallete.ElementAtOrDefault(x.ID - 1),
})
.ToList();
Which gives:
Now, this approach produces a new list keeping the old list and the old objects intact. Generally this is what you should try to do. It's best to mutate objects only when you know that's what they're designed to do.
So it becomes possible to do an in-place update of the original list like so:
previousList.ForEach(x => x.Color = colorPallete.ElementAtOrDefault(x.ID - 1));
This results in modifying the previousList objects without creating a newList.
If you are using List<T> (not IEnumerable<T>) and you don't want to create a new list, but need to update values in the existing list instead, you can do it with the single query. There are three ways to process your scenario (A, B, C):
var colorPallete = new string[]
{
"Red", "Green", "Blue"
};
var list = new List<MyModel>()
{
new MyModel() { ID = 1, Name = "model1", Class = "A", },
new MyModel() { ID = 1, Name = "model11", Class = "AA", },
new MyModel() { ID = 2, Name = "model2", Class = "B", },
new MyModel() { ID = 3, Name = "model3", Class = "C", },
new MyModel() { ID = 4, Name = "model4", Class = "D", },
new MyModel() { ID = 5, Name = "model5", Class = "E", },
};
//A. This code assigns null for unknown IDs
//I.e. if (ID > 0 && ID < colorPallete.Length) then color will be picked from colorPallete[],
//else it will be null
list.ForEach(x => x.Color = colorPallete.ElementAtOrDefault(x.ID - 1));
//B. This code apply some default color for unknown IDs
//I.e. if (ID > 0 && ID < colorPallete.Length) then color will be picked from colorPallete,
//else it will be "DefaultColor"
list.ForEach(x => x.Color = colorPallete.ElementAtOrDefault(x.ID - 1) ?? "DefaultColor");
//C. This code can assign the same color to models with different IDs,
//but models with identical IDs always will have identical color
list.ForEach(x => x.Color = colorPallete.ElementAtOrDefault((x.ID - 1) % colorPallete.Length));
I would create a class for the objects with a color property like this:
public class MyClass
{
public int ID { get; set; }
public string Name { get; set; }
public string Class { get; set; }
public string Color { get; set; } // Nullable
}
And for the colors I would create another class with an ID to compare with the ID of MyClass:
public class MyColor
{
public int ID { get; set; }
public string Color { get; set; }
}
For each color in colorPalette you would assign an ID that matches the ID of the list of MyClass.
So at first the color from MyClass would be null. And then you could loop over the list of MyClass:
foreach (MyClass myClass in myClassList)
{
myClass.Color = colorPalette.FirstOrDefault(col => col.ID = myClass.ID);
}
Or without an ID in Color class (comparing the names of the variables which is not a beautiful solution):
foreach (MyClass myClass in myClassList)
{
myClass.Color = colorPalette.FirstOrDefault(col => int.Parse(nameof(col.Color).Replace("color", "")) == myClass.ID);
}

Using LINQ to remove items from a list that do not apear in another list

//basic item class
public class myItem
{
public Int Id { get; set;}
public String Name { get; set;}
}
//My original List
List<myItem> masterList = new List<myItem>() { new myItem{id = 1, Name = "item 1"},
new myItem{id = 2, Name = "item 2"},
new myItem{id = 3, Name = "item 3"},
new myItem{id = 4, Name = "item 4"}
};
//List of ids of items I want to KEEP in my original list
List<int> keepList = new List<int>() {2,3};
Basically I want to remove all items that arent id 2 or 3 from my master list
public class myItem
{
public int id { get; set;}
public String Name { get; set;}
}
void Main()
{
//My original List
List<myItem> masterList = new List<myItem>() { new myItem{ id = 1, Name = "item 1"},
new myItem{id = 2, Name = "item 2"},
new myItem{id = 3, Name = "item 3"},
new myItem{id = 4, Name = "item 4"}
};
//List of ids of items I want to KEEP in my original list
List<int> keepList = new List<int>() {2,3};
// what you want
masterList = masterList.Where(i => keepList.Contains(i.id)).ToList();
}

Adding data to ObservableCollection in WPF

I have some problem here. Here it is:
I have this class
public class NewsFeedResources
{
public string Name { get; set; }
public string Id { get; set; }
public string Message { get; set; }
public static ObservableCollection<NewsFeedResources> _newsfeed = new ObservableCollection<NewsFeedResources>
{
new NewsFeedResources { Name = "Joe", Id = "1", Message="Foo" },
new NewsFeedResources { Name = "Wandy", Id = "2", Message="Bar" },
new NewsFeedResources { Name = "Yuliana", Id = "3", Message="Baz" },
new NewsFeedResources { Name = "Hardi", Id = "4", Message="Baz" },
};
public static ObservableCollection<NewsFeedResources> newsFeedResources
{ get { return _newsfeed; }
}
}
If I have another data such as
Name=John, Id=5, Message="Stack overflow"
Name=Jane, Id=6, Message="Hello world"
How can I add the data into the class, but not from the constructor? Thanks for the help
ObservableCollection exposes the Collection<T>.Add Method:
Adds an object to the end of the Collection.
So you'd have:
_newsfeed.Add(new NewsFeedResources {Name = "John",
Id = 5,
Message = "Stack overflow"});
_newsfeed.Add(new NewsFeedResources {Name = "Jane",
Id = 6,
Message = "Hello world"});
(typed from memory)
call a function from constructor or anywhere as u like and add items like below
NewsFeedResources NFR=new NewsFeedResources(){Name=John, Id=5, Message="Stack overflow"};
_newsfeed.add(NFR);
NewsFeedResources NFR1 =new NewsFeedResources(){Name=Jane, Id=6, Message="Hello world"};
_newsfeed.add(NFR);

Categories