I have a small hierarchy. Example:
entity:
public class MyClass
{
public int ID { get; set; }
public string Name { get; set; }
public int ParentID { get; set; }
}
My hierarchy data look like:
Id = 1 Name = Item1 ParentId = NULL
Id = 2 Name = Item2 ParentId = 1
Id = 3 Name = Item3 ParentId = 2
Id = 4 Name = Item4 ParentId = 2
Id = 5 Name = Item5 ParentId = 3
The problem is I need to sort it that child nodes must be after its immediate parent. The example bellow must look like
Id = 1 Name = Item1 ParentId = NULL
Id = 2 Name = Item2 ParentId = 1
Id = 3 Name = Item3 ParentId = 2
// the elements with parentID = 3
Id = 5 Name = Item5 ParentId = 3
//continue
Id = 4 Name = Item4 ParentId = 2
Any adwices?
Assuming you have a _list of MyClass objects, then sort it first on Name field, then on ParentId field, like shown below using LINQ:
_list.OrderBy(L=>L.Name).ThenBy(L=>L.ParentId);
Hope this may help.
Try this
I assume that 1st you want to order by parentid and in each parent you want to sort by id.
myClassList.OrderBy(parent=>parent.ParentId).ThenBy(parent=>parent.Id);
Try this recursive code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
MyClass.data = new List<MyClass>() {
new MyClass() { ID = 1, Name = "Item1", ParentID = null},
new MyClass() { ID = 2, Name = "Item2", ParentID = 1 },
new MyClass() { ID = 3, Name = "Item3", ParentID = 2 },
new MyClass() { ID = 4, Name = "Item4", ParentID = 2 },
new MyClass() { ID = 5, Name = "Item5", ParentID = 3 }
};
MyClass myClass = new MyClass();
myClass.GetData(null, 0);
Console.ReadLine();
}
}
public class MyClass
{
public static List<MyClass> data = null;
public int ID { get; set; }
public string Name { get; set; }
public int? ParentID { get; set; }
public void GetData(int? id, int level)
{
List<MyClass> children = data.Where(x => x.ParentID == id).ToList();
foreach (MyClass child in children)
{
Console.WriteLine(" {0} ID : {1}, Name : {2}, Parent ID : {3}", new string(' ',4 * level),child.ID, child.Name, child.ParentID);
GetData(child.ID, level + 1);
}
}
}
}
Here you have a way to do it. As you can see, I overrode the ToString method and added a few more cases.
public class MyClass
{
public int Id { get; set; }
public string Name { get; set; }
public int? ParentId { get; set; }
public override string ToString()
{
return string.Format("{0}: {1} - {2}", Id, Name, ParentId);
}
}
class Program
{
static void Main(string[] args)
{
List<MyClass> list = new List<MyClass>();
list.Add(new MyClass { Id = 1, Name = "Item1", ParentId = null });
list.Add(new MyClass { Id = 2, Name = "Item2", ParentId = 1 });
list.Add(new MyClass { Id = 3, Name = "Item3", ParentId = 2 });
list.Add(new MyClass { Id = 4, Name = "Item4", ParentId = 2 });
list.Add(new MyClass { Id = 5, Name = "Item5", ParentId = 3 });
list.Add(new MyClass { Id = 6, Name = "Item6", ParentId = 1 });
list.Add(new MyClass { Id = 7, Name = "Item7", ParentId = null });
list.Add(new MyClass { Id = 8, Name = "Item8", ParentId = 2 });
list.Add(new MyClass { Id = 9, Name = "Item9", ParentId = 6 });
list.Add(new MyClass { Id = 10, Name = "Item10", ParentId = 7 });
foreach(var item in list.Where(x => !x.ParentId.HasValue).OrderBy(x => x.Id))
ProcessItem(item, list, 0);
Console.ReadKey();
}
private static void ProcessItem(MyClass item, List<MyClass> list, int level)
{
Console.WriteLine("{0}{1}", new string(' ', level * 2), item.ToString());
foreach (var subitem in list.Where(x => x.ParentId == item.Id).OrderBy(x => x.Id))
ProcessItem(subitem, list, level + 1);
}
}
Would something like this work for you?
If you need an actual ordered list, try this:
foreach (var item in OrderList(list))
Console.WriteLine(item.ToString());
(...)
private static List<MyClass> OrderList(List<MyClass> list)
{
List<MyClass> orderedList = new List<MyClass>(list.Count());
foreach (var item in list.Where(x => !x.ParentId.HasValue).OrderBy(x => x.Id))
AddItem(item, list, orderedList);
return orderedList;
}
private static void AddItem(MyClass item, List<MyClass> list, List<MyClass> orderedList)
{
orderedList.Add(item);
foreach (var subitem in list.Where(x => x.ParentId == item.Id).OrderBy(x => x.Id))
AddItem(subitem, list, orderedList);
}
The following should do the trick (and show some better performance because we save the hierarchy in a lookup, instead of searching the IEnumerable on the fly):
public List<MyClass> SortHierarchically(IEnumerable<MyClass> myClasses)
{
if(myClasses == null)
return new List<MyClass>();
var myClassesByParentId = myClasses.ToLookup(mc => mc.ParentId);
var result = new List<MyClass>(myClasses.Count());
int? currentParentId = null;
MyClass currentItem = myClassesByParentId[currentParentId].Single();
result.Add(currentItem);
currentParentId = currentItem.Id;
if(myClassesByParentId.Contains(currentParentId))
result.AddRange(myClassesByParentId[currentParentId].SelectMany(mc => GetAllSortedChildren(mc, myClassesByParentId)));
return result;
}
public List<MyClass> GetAllSortedChildren(MyClass parent, ILookup<int?, MyClass> myClassesByParentId)
{
var result = new List<MyClass>() { parent };
if(myClassesByParentId.Contains(parent.Id))
retsult.AddRange(myClassesByParentId[parent.Id].SelectMany(mc => GetAllSortedChildren(mc, myClassesByParentId)));
return result;
}
It would be interesting to find a method of sorting this by standard LINQ, with some clever comparer or such.
One of the answers above works well. This is a generic version.
public static class SortingMethods
{
public static IList<T> OrderByHierarchical<T>(
this IEnumerable<T> items,
Func<T, string> getId,
Func<T, string> getParentId)
{
if (items == null)
return new List<T>();
var itemsByParentId = items.ToLookup(item => getParentId(item));
var result = new List<T>(items.Count());
var currentParentId = "";
var currentItem = itemsByParentId[currentParentId].Single();
result.Add(currentItem);
currentParentId = getId(currentItem);
if (itemsByParentId.Contains(currentParentId))
result.AddRange(itemsByParentId[currentParentId].SelectMany(item => GetAllSortedChildren(item, itemsByParentId, getId)));
return result;
}
private static IList<T> GetAllSortedChildren<T>(T parent, ILookup<string, T> itemsByParentId, Func<T, string> getId)
{
var result = new List<T>() { parent };
if (itemsByParentId.Contains(getId(parent)))
{
result.AddRange(itemsByParentId[getId(parent)].SelectMany(item => GetAllSortedChildren(item, itemsByParentId, getId)));
}
return result;
}
}
Related
I am new to C# and I have been struggling to do the following:
I'm trying to List a list in a console application, I have a model called "TeamModel"
public class TeamModel
{
public int Id { get; set; }
public string TeamName { get; set; }
public List<PersonModel> TeamMembers { get; set; } = new List<PersonModel>();
public TeamModel()
{
}
}
In my main class I have the following:
class Program
{
static void Main(string[] args)
{
List<TeamModel> TeamOne = new List<TeamModel>(){new TeamModel() { Id =1, TeamName = "x's Team", TeamMembers = null}};
List<TeamModel> TeamTwo = new List<TeamModel>(){new TeamModel() { Id =2, TeamName = "y's Team", TeamMembers = null}};
List<TeamModel> TeamThree = new List<TeamModel>(){new TeamModel() { Id =3, TeamName = "z's Team", TeamMembers = null}};
List<List<TeamModel>> listOfTeams = new List<List<TeamModel>> (){TeamOne,TeamTwo,TeamThree};
foreach (List<TeamModel> list in listOfTeams)
{
Console.WriteLine(list);
}
}
}
Now when I run the program I expect the result to be:
1,x's Team ,
2,y's Team ,
3,z's Team
Instead what I'm getting is
System.Collections.Generic.List`1[TeamModel]
System.Collections.Generic.List`1[TeamModel]
System.Collections.Generic.List`1[TeamModel]
If I change the foreach to :
foreach (List<TeamModel> list in listOfTeams)
{
Console.WriteLine(String.Join(", ", list));
}
I get this:
TeamModel
TeamModel
TeamModel
You can achieve this by using
foreach (List<TeamModel> list in listOfTeams)
{
foreach (var team in list)
{
Console.WriteLine(team.Id + " " + team.Name);
}
}
Is there a reason that TeamOne, TeamTwo and TeamThree are created as lists given that each "list" contains a single team? The following implementation would achieve what you stated was the expected output:
static void Main(string[] args)
{
TeamModel TeamOne = new TeamModel { Id = 1, TeamName = "x's Team", TeamMembers = null };
TeamModel TeamTwo = new TeamModel { Id = 2, TeamName = "y's Team", TeamMembers = null };
TeamModel TeamThree = new TeamModel { Id = 3, TeamName = "z's Team", TeamMembers = null };
List<TeamModel> listOfTeams = new List<TeamModel> { TeamOne, TeamTwo, TeamThree };
foreach (TeamModel team in listOfTeams)
{
Console.WriteLine($"{team.Id}. {team.TeamName}");
}
}
If you genuinely need a list of lists then the following implementation will work. The only difference from your own solution is an additional foreach inside the one you had:
static void Main(string[] args)
{
List<TeamModel> TeamOne = new List<TeamModel>() { new TeamModel() { Id = 1, TeamName = "x's Team", TeamMembers = null } };
List<TeamModel> TeamTwo = new List<TeamModel>() { new TeamModel() { Id = 2, TeamName = "y's Team", TeamMembers = null } };
List<TeamModel> TeamThree = new List<TeamModel>() { new TeamModel() { Id = 3, TeamName = "z's Team", TeamMembers = null } };
List<List<TeamModel>> listOfTeams = new List<List<TeamModel>>() { TeamOne, TeamTwo, TeamThree };
foreach (List<TeamModel> list in listOfTeams)
{
foreach (TeamModel team in list)
{
Console.WriteLine($"{team.Id}. {team.TeamName}");
}
}
}
Using Linq
You can Flatten the nested list using .SelectMany() and then either you can iterate over flatten list or you can convert it into List of string and then print it.
Projects each element of a sequence to an IEnumerable and flattens
the resulting sequences into one sequence.
var strTeamDetails = listOfTeams
.SelectMany(teams => teams) //Flatten the List.
.Select(individualTeam => $"{individualTeam.Id} {individualTeam.Name}"); //Convert to IEnumearable of string
Console.WriteLine(string.Join(",\n", strTeamDetails)); //Print
You may want to consider overriding the ToString() method for your TeamModel class;
From the link above;
class Person
{
public string Name { get; set; }
public int Age { get; set; }
public override string ToString()
{
return "Person: " + Name + " " + Age;
}
}
You can test the ToString method as shown in the following code example:
Person person = new Person { Name = "John", Age = 12 };
Console.WriteLine(person);
// Output:
// Person: John 12
I have an application where tags can be structured in a hierarchy by using a backslash (\) character.
for example;
Country\Canada\Alberta
Country\Canada\British Columbia
Country\USA\California
Country\USA\Texas
would become in the user interface;
Country
Canada
Alberta
British Columbia
USA
California
Texas
In the database it is stored as a string and returned to the client as a TagDto. I have tried the following to accomplish this;
public class TagDto
{
public int Id { get; set; }
public string Name { get; set; }
}
public class TagLeaf
{
public string Id { get; }
public string ParentId { get; }
public int TagId { get; }
public string Name { get; }
public TagLeaf(string id, string parentId, int tagId, string name)
{
Id = id;
ParentId = parentId;
TagId = tagId;
Name = name;
}
// IEquatable implemented on Id property.
}
public class TagsViewModel : ReactiveObject
{
private IDisposable TagsSubscription { get; }
public SourceCache<TagDto, string> Tags { get } = new SourceCache<TagDto, string>(t => t.Id);
private readonly ReadOnlyObservableCollection<TagLeafViewModel> _tagTree;
public ReadOnlyObservableCollection<TagLeafViewModel> TagTree => _tagTree;
public ReactiveCommand AddBelgium { get; }
public TagsViewModel()
{
AddBelgium = ReactiveCommand.Create(() =>
Tags.AddOrUpdate(new TagDto {Id = 5, Name = #"Country\Belgium"});
// this comes from an web service normally.
Tags.AddOrUpdate(new[] {
new TagDto {Id = 1, Name = #"Country\Canada\Alberta"},
new TagDto {Id = 2, Name = #"Country\Canada\British Columbia"},
new TagDto {Id = 3, Name = #"Country\USA\California"},
new TagDto {Id = 4, Name = #"Country\USA\Texas"}
});
TagsSubscription = Tags
.Connect()
.TransformMany(dto =>
{
var names = dto.Name.Split(new[] {'\\'}, StringSplitOptions.RemoveEmptyEntries);
var results = new TagLeaf[names.Length];
var parentId = "";
for (var i = 0; i < names.Length; i++)
{
var name = names[i];
var id = $"{parentId}{name}\\";
results[i] = new TagLeaf(id, parentId, dto.Id, name);
parentId = id;
}
return results;
}, leaf => leaf.Id)
.TransformToTree(leaf => leaf.ParentId)
.Transform(leaf => new TagLeafViewModel(leaf))
.Sort(SortExpressionComparer<TagLeafViewModel>.Ascending(vm => vm.Name))
.Bind(out _tagTree)
.Subscribe();
}
}
public class TagLeafViewModel : ReactiveObject
{
private readonly ReadOnlyObservableCollection<TagLeafViewModel> _children;
public ReadOnlyObservableCollection<TagLeafViewModel> Children => _children;
private string _name;
public string Name
{
get => _name;
set => this.RaiseAndSetIfChanged(ref _name, value);
}
public TagLeafViewModel(Node<TagLeaf, string> node)
{
Name = node.Item.Name;
ChildrenSubscription = node.Children
.Connect()
.Transform(n => new TagLeafViewModel(n))
.Sort(SortExpressionComparer<TagLeafViewModel>.Ascending(vm => vm.Name))
.Bind(out _children)
.Subscribe();
}
}
// TagsView.xaml
<StackPanel>
<Button x:Name="AddBelgiumButton" Content="Add Belgium"/>
<telerik:RadTreeView x:Name="TagTreeView">
<telerik:RadTreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
</telerik:RadTreeView.ItemTemplate>
</telerik:RadtreeView>
</StackPanel>
// TagsView.xaml.cs constructor
public TagsView()
{
...
this.WhenActivated(d =>
{
d(this.AddBelgiumButton.Events().Click.Select(x => Unit.Default).InvokeCommand(ViewModel, vm => vm.AddBelgium));
d(this.OneWayBind(ViewModel, vm => vm.TagTree, v => v.TagTreeView.ItemsSource));
});
}
This produces a tree as I would expect, however if I expand Country and click Add Belgium, instead of seeing this insert into the tree as a new node underneath country - it collapses the whole country node.
Adding the new tag results in 2 new TagLeaf's being streamed into TramsformToTree. One for Country and one for Belgium so I understand why it's updating the country node but, I am not sure how I can overcome that - any suggestions would be much appreciated.
I believe I've made a breakthrough however, suggestions are still welcome.
Realizing that TransformMany was the problem in my earlier attempt, I decided it would be necessary to maintain 2 separate caches to achieve what I was after.
I now have a TagService which exposes both caches. Whenever an item is changed in the underlying TagDto cache I manually update the TagLeaf cache with the changes. In my sample application this now inserts the new node without collapsing the root node.
This is incomplete, I still need to handle removing the parent TagLeaf when they have no children in the TagLeaf cache but I believe I can make that work so I consider problem solved.
public class TagService : ITagService
{
private readonly SourceCache<TagDto, int> _tagDtos = new SourceCache<TagDto, int>(t => t.Id);
public IObservableCache<TagDto, int> TagDtos => _tagDtos;
private readonly SourceCache<TagLeaf, string> _tagLeafs = new SourceCache<TagLeaf, string>(t => t.Id);
public IObservableCache<TagLeaf, string> TagLeafs => _tagLeafs;
public TagService()
{
_tagDtos.AddOrUpdate(new[]
{
new TagDto {Id = 1, Name = #"Country\Canada\Alberta"},
new TagDto {Id = 2, Name = #"Country\Canada\British Columbia"},
new TagDto {Id = 3, Name = #"Country\USA\California"},
new TagDto {Id = 4, Name = #"Country\USA\Texas"}
});
_tagDtos
.Connect()
.Transform(dto =>
{
var names = dto.Name.Split(new[] {'\\'}, StringSplitOptions.RemoveEmptyEntries);
var results = new TagLeaf[names.Length];
var parentId = "";
for (var i = 0; i < names.Length; i++)
{
var name = names[i];
var id = $"{parentId}{name}\\";
results[i] = new TagLeaf(id, parentId, dto.Id, name);
parentId = id;
}
return new TagBranch(dto.Id, results);
})
.ForEachChange(change =>
{
var branch = change.Current;
switch (change.Reason)
{
case ChangeReason.Remove:
var lastLeaf = branch.Leaves.Last();
_tagLeafs.RemoveKey(lastLeaf.Id);
break;
case ChangeReason.Add:
foreach (var leaf in branch.Leaves)
{
if (_tagLeafs.Keys.Contains(leaf.Id))
continue;
_tagLeafs.AddOrUpdate(leaf);
}
break;
}
})
.Subscribe();
}
public void AddOrUpdate(TagDto dto)
{
_tagDtos.AddOrUpdate(dto);
}
}
The constructor in TagsViewModel now looks like this;
public TagsViewModel(ITagService tagService)
{
AddBelgium = ReactiveCommand.Create(() =>
tagService.AddOrUpdate(new TagDto {Id = 5, Name = #"Country\Belgium"}));
TagsSubscription = tagService.TagLeafs
.Connect()
.TransformToTree(leaf => leaf.ParentId)
.Transform(node => new TagLeafViewModel(node))
.Sort(SortExpressionComparer<TagLeafViewModel>.Ascending(vm => vm.Name))
.Bind(out _tagTree)
.Subscribe();
}
I have a list in c# :
public class Item
{
public int Id { get; set; }
public string Name { get; set; }
}
List<Item> items = new List<Item>()
{
new Item() { Id = 1, Name = "Item-1" },
new Item() { Id = 2, Name = "Item-2" },
new Item() { Id = 3, Name = "Item-3" },
new Item() { Id = 4, Name = "Item-4" },
new Item() { Id = 5, Name = "Item-5" },
};
Now i use where clause on the above list of items and fetch all items whose Id is greater than or equals to 3.
List<Item> itemsWithIdGreaterThan3 = items.Where(i => i.Id >= 3).ToList();
The above statement creates a new List but it copies the objects by reference, so if i change any object`s property in itemsWithIdGreaterThan3 list then it reflect the changes in item list:
itemsWithIdGreaterThan3[0].Name = "change-item-2"
This also changes the object with Id = 3 in items List.
Now what i want is to clone the object, so i found Select function like:
List<Item> itemsWithIdGreaterThan3 = items.Where(i => i.Id >= 3)
.Select(i => new Item() { Id = i.Id, Name = i.Name }).ToList();
This works, but what if i have an object contains 20 to 30 properties or even more. Then in than case we have to manually copy each property. Is there any shortcut solution for this problem ??
You could make a constructor for Item that takes an Item as it's parameter. Within that you would then do the property assignment. Then just call the constructor from the Select.
public class Item
{
public int Id { get; set; }
public string Name { get; set; }
public Item(Item i)
{
Id = i.Id;
Name = i.Name;
...
}
}
List<Item> itemsWithIdGreaterThan3 = items.Where(i => i.Id >= 3)
.Select(i => new Item(i)).ToList();
I have a class Items with properties (Id, Name, Code, Price).
The List of Items is populated with duplicated items.
For ex.:
1 Item1 IT00001 $100
2 Item2 IT00002 $200
3 Item3 IT00003 $150
1 Item1 IT00001 $100
3 Item3 IT00003 $150
How to remove the duplicates in the list using linq?
var distinctItems = items.GroupBy(x => x.Id).Select(y => y.First());
var distinctItems = items.Distinct();
To match on only some of the properties, create a custom equality comparer, e.g.:
class DistinctItemComparer : IEqualityComparer<Item> {
public bool Equals(Item x, Item y) {
return x.Id == y.Id &&
x.Name == y.Name &&
x.Code == y.Code &&
x.Price == y.Price;
}
public int GetHashCode(Item obj) {
return obj.Id.GetHashCode() ^
obj.Name.GetHashCode() ^
obj.Code.GetHashCode() ^
obj.Price.GetHashCode();
}
}
Then use it like this:
var distinctItems = items.Distinct(new DistinctItemComparer());
If there is something that is throwing off your Distinct query, you might want to look at MoreLinq and use the DistinctBy operator and select distinct objects by id.
var distinct = items.DistinctBy( i => i.Id );
This is how I was able to group by with Linq. Hope it helps.
var query = collection.GroupBy(x => x.title).Select(y => y.FirstOrDefault());
An universal extension method:
public static class EnumerableExtensions
{
public static IEnumerable<T> DistinctBy<T, TKey>(this IEnumerable<T> enumerable, Func<T, TKey> keySelector)
{
return enumerable.GroupBy(keySelector).Select(grp => grp.First());
}
}
Example of usage:
var lstDst = lst.DistinctBy(item => item.Key);
You have three option here for removing duplicate item in your List:
Use a a custom equality comparer and then use Distinct(new DistinctItemComparer()) as #Christian Hayter mentioned.
Use GroupBy, but please note in GroupBy you should Group by all of the columns because if you just group by Id it doesn't remove duplicate items always. For example consider the following example:
List<Item> a = new List<Item>
{
new Item {Id = 1, Name = "Item1", Code = "IT00001", Price = 100},
new Item {Id = 2, Name = "Item2", Code = "IT00002", Price = 200},
new Item {Id = 3, Name = "Item3", Code = "IT00003", Price = 150},
new Item {Id = 1, Name = "Item1", Code = "IT00001", Price = 100},
new Item {Id = 3, Name = "Item3", Code = "IT00003", Price = 150},
new Item {Id = 3, Name = "Item3", Code = "IT00004", Price = 250}
};
var distinctItems = a.GroupBy(x => x.Id).Select(y => y.First());
The result for this grouping will be:
{Id = 1, Name = "Item1", Code = "IT00001", Price = 100}
{Id = 2, Name = "Item2", Code = "IT00002", Price = 200}
{Id = 3, Name = "Item3", Code = "IT00003", Price = 150}
Which is incorrect because it considers {Id = 3, Name = "Item3", Code = "IT00004", Price = 250} as duplicate. So the correct query would be:
var distinctItems = a.GroupBy(c => new { c.Id , c.Name , c.Code , c.Price})
.Select(c => c.First()).ToList();
3.Override Equal and GetHashCode in item class:
public class Item
{
public int Id { get; set; }
public string Name { get; set; }
public string Code { get; set; }
public int Price { get; set; }
public override bool Equals(object obj)
{
if (!(obj is Item))
return false;
Item p = (Item)obj;
return (p.Id == Id && p.Name == Name && p.Code == Code && p.Price == Price);
}
public override int GetHashCode()
{
return String.Format("{0}|{1}|{2}|{3}", Id, Name, Code, Price).GetHashCode();
}
}
Then you can use it like this:
var distinctItems = a.Distinct();
Use Distinct() but keep in mind that it uses the default equality comparer to compare values, so if you want anything beyond that you need to implement your own comparer.
Please see http://msdn.microsoft.com/en-us/library/bb348436.aspx for an example.
Try this extension method out. Hopefully this could help.
public static class DistinctHelper
{
public static IEnumerable<TSource> DistinctBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
var identifiedKeys = new HashSet<TKey>();
return source.Where(element => identifiedKeys.Add(keySelector(element)));
}
}
Usage:
var outputList = sourceList.DistinctBy(x => x.TargetProperty);
List<Employee> employees = new List<Employee>()
{
new Employee{Id =1,Name="AAAAA"}
, new Employee{Id =2,Name="BBBBB"}
, new Employee{Id =3,Name="AAAAA"}
, new Employee{Id =4,Name="CCCCC"}
, new Employee{Id =5,Name="AAAAA"}
};
List<Employee> duplicateEmployees = employees.Except(employees.GroupBy(i => i.Name)
.Select(ss => ss.FirstOrDefault()))
.ToList();
Another workaround, not beautiful buy workable.
I have an XML file with an element called "MEMDES" with two attribute as "GRADE" and "SPD" to record the RAM module information.
There are lot of dupelicate items in SPD.
So here is the code I use to remove the dupelicated items:
IEnumerable<XElement> MList =
from RAMList in PREF.Descendants("MEMDES")
where (string)RAMList.Attribute("GRADE") == "DDR4"
select RAMList;
List<string> sellist = new List<string>();
foreach (var MEMList in MList)
{
sellist.Add((string)MEMList.Attribute("SPD").Value);
}
foreach (string slist in sellist.Distinct())
{
comboBox1.Items.Add(slist);
}
When you don't want to write IEqualityComparer you can try something like following.
class Program
{
private static void Main(string[] args)
{
var items = new List<Item>();
items.Add(new Item {Id = 1, Name = "Item1"});
items.Add(new Item {Id = 2, Name = "Item2"});
items.Add(new Item {Id = 3, Name = "Item3"});
//Duplicate item
items.Add(new Item {Id = 4, Name = "Item4"});
//Duplicate item
items.Add(new Item {Id = 2, Name = "Item2"});
items.Add(new Item {Id = 3, Name = "Item3"});
var res = items.Select(i => new {i.Id, i.Name})
.Distinct().Select(x => new Item {Id = x.Id, Name = x.Name}).ToList();
// now res contains distinct records
}
}
public class Item
{
public int Id { get; set; }
public string Name { get; set; }
}
I have a database return result which has flatten results like below. I want to use Linq to break the flat results into primary classes with the items populating the primary class items property collection.
public class Result
{
public string PrimaryKey { get; set; }
public string Status { get; set; }
public string ItemName { get; set; }
}
public class ObjectA
{
public string PrimaryKey { get; set; }
public string Status { get; set; }
public List<Item> Items = new List<Item>();
}
public class Item
{
public string Name { get; set; }
}
static void Main(string[] args)
{
GetObjectAs();
}
static List<ObjectA> GetObjectAs()
{
// this is our table results
List<Result> results = new List<Result>();
results.Add(new Result()
{
PrimaryKey = "1",
Status = "Done",
ItemName = "item1"
});
results.Add(new Result()
{
PrimaryKey = "2",
Status = "Fail",
ItemName = null
});
results.Add(new Result()
{
PrimaryKey = "3",
Status = "Done",
ItemName = "item2"
});
results.Add(new Result()
{
PrimaryKey = "3",
Status = "Done",
ItemName = "item3"
});
List<ObjectA> returnResults = new List<ObjectA>();
// need to break into 3 ObjectA objects
// ObjectA 1 needs an Item added to its Items collection with ItemName item1
// ObjectA 2 has no items since the ItemName above is null
// ObjectA 3 needs 2 Items added to its Items collection item2 and item3
// return our collection
return returnResults;
}
PS this is just sample code, I know you shouldn't expose a List as a public property and should return an IEnumerator instead of the actual List etc.
You can use GroupBy to group the results by the primary key, then you can operate on the subset of rows within the group to obtain the status (hopefully all values for Status are the same, which is why I used First) and the list of items.
var items = results.GroupBy(r => r.PrimaryKey).Select(grp => new ObjectA()
{
PrimaryKey = grp.Key,
Status = grp.Select(r => r.Status).First(),
Items = grp.Where(r => r.ItemName != null)
.Select(r => new Item() { Name = r.ItemName }).ToList()
}).ToList();
return results
.GroupBy(r => r.PrimaryKey)
.Select(grp => new ObjectA
{
PrimaryKey = grp.Key,
Status = grp.First().Status,
Items = grp.Where(i => i.ItemName != null).Select(i => new Item { Name = i.ItemName }).ToList()
}).ToList();