How could distinct multi array list on c# - c#

Consider objModels is object of ChildModel class
public class ChildModel
{
public string Title { get; set; }
public long Id { get; set; }
}
It has some data like:
title,id: a,1 b,1 a,1
Two of these data are same, and after distinct it could be like: a,1 b,1
Question is how could I distinct it on c#
List<ChildModel> obj= objModels.ToList();
Also these aren't help
objModels.Distinct();
obj.Distinct();

You could use a library named MoreLINQ
This is the query you could use with MoreLINQ to find elements that are distinct by multiple properties:
var query = objModels.DistinctBy(p => new { p.Id, p.Title});

try this:
var answer= obj.DistinctBy(p => new { p.Id , p.Title });
and check this link for other way

I would do the following:
namespace ConsoleApplication7
{
class Program
{
static void Main()
{
List<ChildModel> list = new List<ChildModel>();
list.Add(new ChildModel { Title = "a", Id = 1 });
list.Add(new ChildModel { Title = "b", Id = 1 });
list.Add(new ChildModel { Title = "a", Id = 1 });
var x = list.Distinct(new ChildModelComparer()).ToList();
var y = x; //This has only got two child items.
}
}
class ChildModelComparer : IEqualityComparer<ChildModel>
{
public bool Equals(ChildModel x, ChildModel y)
{
return x.Id.Equals(y.Id) && x.Title.Equals(y.Title);
}
public int GetHashCode(ChildModel obj)
{
if (string.IsNullOrEmpty(obj.Title) && obj.Id == 0)
{
return 0;
}
return $"{obj.Id}{obj.Title}".GetHashCode();
}
}
public class ChildModel
{
public string Title { get; set; }
public long Id { get; set; }
}
}

you can use .GroupBy:
var result= obj
.GroupBy(p => p.Title)
.Select(g => g.First()) // choose which one
.ToList();
edit: if you want to GroupBy more than one Property you can just change p.Title to new {p.Title, p.Id} like so
var result= obj
.GroupBy(p => new {p.Title, p.Id})
.Select(g => g.First()) // choose which one
.ToList();

Related

Creating a list of an object which either does or doesn't exist in another different object list - C#

So I am trying to compare two different lists that both contain differently structured objects. One is easily accessible while the other is very nested in arrays, but sadly, these are responses from API calls so it's not a structure I can change to make them easier to compare. I want to have a list of the complete structures of items found:
var foundList = new List<Structure1>();
var notFoundList = new List<Structure1>();
The way the objects are structured are as follows:
public class ObjectsA
{
public Structure1[] Structure1 {get; set;}
}
public class Structure1
{
public int id {get; set;} //1
}
And the other object looks like:
public class ObjectsB
{
public Array1[] array1{get; set}
}
public class Array1
{
public Array2[] array2{get; set;}
}
public class Array2
{
public string id {get; set;} //"0001"
}
In a list, I added all the objects that came back from the API call, so ObjectAList contains technically just 1 deserialized object response, which contains an array of objects, while ObjectBList contains a list of objects added to it via AddRange.
At first I tried to putting an Array.Exists() inside of 2 foreach() statements.
foreach (var arr1 in ObjectsBList){
foreach (var arr2 in a.Array2){
if (Array.Exists(ObjectAList.Structure1, item => item.id == Convert.ToInt32(arr2.id)) == true){
foundList.AddRange(ObjectAList.Structure1);
}
else{
notFoundList.AddRange(ObjectAList.Structure1)
}}};
This code seemed to keep looping on the "item => item.id == Convert.ToInt32(arr2.id)" part of it, so consequently, it kept going till it found its match and so the answer was always 'true', therefore just putting everything in the foundList. I know I'm probably going at this wrong. I'm just starting out C# programming and I'm having trouble wrapping my mind around some of these things and knowing what all functions exists to help with what I need, etc. Any help would be great!
You can use linq for querying the in-memory objects.
pseudo code
public class Test {
public void T()
{
var ObjectsBList = new ObjectsB();
var ObjectAList = new ObjectsA();
var foundList = new List<Structure1>();
var notFoundList = new List<Structure1>();
var bList = ObjectsBList
.array1
.SelectMany(x => x.array2)
.Select(x => Convert.ToInt32(x.id))
.Distinct()
.ToList();
if (ObjectAList.Structure1.Any(x => bList.Contains(x.id)))
{
foundList.AddRange(ObjectAList.Structure1);
}
else
{
notFoundList.AddRange(ObjectAList.Structure1);
}
}
}
More simplified version:
Introduce an ID list property
using System;
using System.Text;
using System.Collections.Generic;
using System.Collections;
public class ObjectsA
{
public Structure1[] Structure1 { get; set; }
public List<int> IDs
{
get
{
return Structure1.Select(x => x.id).Distinct().ToList();
}
}
}
public class Structure1
{
public int id { get; set; } //1
}
public class ObjectsB
{
public Array1[] array1 { get; set; }
public List<int> IDs
{
get
{
return array1
.SelectMany(x => x.array2)
.Select(x => Convert.ToInt32(x.id))
.Distinct()
.ToList();
}
}
}
public class Array1
{
public Array2[] array2 { get; set; }
}
public class Array2
{
public string id { get; set; } //"0001"
}
public class Test
{
public void T()
{
var ObjectsBList = new ObjectsB();
var ObjectAList = new ObjectsA();
var foundList = new List<Structure1>();
var notFoundList = new List<Structure1>();
if (ObjectAList.IDs.Any(x => ObjectsBList.IDs.Contains(x)))
{
foundList.AddRange(ObjectAList.Structure1);
}
else
{
notFoundList.AddRange(ObjectAList.Structure1);
}
}
}
var objA = new ObjectsA();
var objB = new ObjectsB();
var objAIds = objA.Structure1.Select(x => x.Id).Distinct();
var objBIds = objB.Array1.SelectMany(x => x.Array2).Select(x => int.Parse(x.Id)).Distinct();
var foundInBothList = objAIds.Intersect(objBIds);
var notFoundinBList = objAIds.Except(objBIds);
var inBoth = objA.Structure1.Where(x => foundInBothList.Contains(x.Id));
var notInB = objA.Structure1.Where(x => notFoundinBList.Contains(x.Id));
Starting from .NET 6
var objBIds = objB.Array1.SelectMany(x => x.Array2).Select(x => int.Parse(x.Id)).Distinct();
var foundList = objA.Structure1.IntersectBy(objBIds, x => x.Id);
var notFoundList = objA.Structure1.ExceptBy(objBIds, x => x.Id);

C# how to remove duplicate objects from a list

I want to remove duplicate objects from a list. My code works, but I'm still afraid I'll make a mistake. Especially if the amount of data is larger, this solution doesn't make sense to me. I ask for your comments on my code.
// Print the list with duplicates
PrintList(listWithDuplicates);
// This code is not working
noDuplicates = listWithDuplicates.Distinct().ToList();
// This code is working but I am not sure if it is good practice
// especially if I have a large number of data
noDuplicates = listWithDuplicates
.GroupBy(x => x.input1)
.Select(x => x.First())
.GroupBy(x => x.input2)
.Select(x => x.First())
.GroupBy(x => x.output1)
.Select(x => x.First())
.GroupBy(x => x.output2)
.Select(x => x.First())
.ToList();
// Print the list without duplicates
PrintList(noDuplicates);
Console.ReadLine();
}
class Data
{
public string input1 { get; set; }
public string input2 { get; set; }
public string output1 { get; set; }
public string output2 { get; set; }
}
You tried to use .Distinct() without further telling it how to compare instances of your Data-class.
Therefore you could create a Comparer class which you'd then pass to .Distinct() as a parameter:
public class Data
{
public string input1 { get; set; }
public string input2 { get; set; }
public string output1 { get; set; }
public string output2 { get; set; }
}
public class DataComparer : EqualityComparer<Data>
{
public override bool Equals(Data x, Data y)
{
if (x.input1 == y.input1 &&
x.input2 == y.input2 &&
x.output1 == y.output1 &&
x.output2 == y.output2)
{
return true;
}
return false;
}
public override int GetHashCode(Data obj)
{
return $"{obj.input1}{obj.input2}{obj.output1}{obj.output2}".GetHashCode();
}
}
Here is an example:
var dataList = new List<Data>()
{
new Data(){ input1="A", input2="B", output1="B", output2="A"},
new Data(){ input1="A", input2="B", output1="B", output2="A"},
new Data(){ input1="C", input2="D", output1="D", output2="C"},
new Data(){ input1="C", input2="D", output1="D", output2="C"}
};
dataList = dataList.Distinct(new DataComparer()).ToList();
A friend showed me how to do the job without a comparer and without override methods.
noDuplicates = listWithDuplicates.GroupBy(x => new { x.input1, x.input2, x.output1, x.output2 }).Select(y => y.First()).ToList();

Best approach to compare if one list is subset of another in C#

I have the below two classes:
public class FirstInner
{
public int Id { get; set; }
public string Type { get; set; }
public string RoleId { get; set; }
}
public class SecondInner
{
public int Id { get; set; }
public string Type { get; set; }
}
Again, there are lists of those types inside the below two classes:
public class FirstOuter
{
public int Id { get; set; }
public string Name { get; set; }
public string Title { get; set; }
public List<FirstInner> Inners { get; set; }
}
public class SecondOuter
{
public int Id { get; set; }
public string Name { get; set; }
public List<SecondInner> Inners { get; set; }
}
Now, I have list of FirstOuter and SecondOuter. I need to check if FirstOuter list is a subset of SecondOuter list.
Please note:
The names of the classes cannot be changed as they are from different systems.
Some additional properties are present in FirstOuter but not in SecondOuter. When comparing subset, we can ignore their presence in SecondOuter.
No.2 is true for FirstInner and SecondInner as well.
List items can be in any order---FirstOuterList[1] could be found in SecondOuterList[3], based on Id, but inside that again need to compare that FirstOuterList[1].FirstInner[3], could be found in SecondOuterList[3].SecondInner[2], based on Id.
I tried Intersect, but that is failing as the property names are mismatching. Another solution I have is doing the crude for each iteration, which I want to avoid.
Should I convert the SecondOuter list to FirstOuter list, ignoring the additional properties?
Basically, here is a test data:
var firstInnerList = new List<FirstInner>();
firstInnerList.Add(new FirstInner
{
Id = 1,
Type = "xx",
RoleId = "5"
});
var secondInnerList = new List<SecondInner>();
secondInner.Add(new SecondInner
{
Id = 1,
Type = "xx"
});
var firstOuter = new FirstOuter
{
Id = 1,
Name = "John",
Title = "Cena",
Inners = firstInnerList
}
var secondOuter = new SecondOuter
{
Id = 1,
Name = "John",
Inners = secondInnerList,
}
var firstOuterList = new List<FirstOuter> { firstOuter };
var secondOuterList = new List<SecondOuter> { secondOuter };
Need to check if firstOuterList is part of secondOuterList (ignoring the additional properties).
So the foreach way that I have is:
foreach (var item in firstOuterList)
{
var secondItem = secondOuterList.Find(so => so.Id == item.Id);
//if secondItem is null->throw exception
if (item.Name == secondItem.Name)
{
foreach (var firstInnerItem in item.Inners)
{
var secondInnerItem = secondItem.Inners.Find(sI => sI.Id == firstInnerItem.Id);
//if secondInnerItem is null,throw exception
if (firstInnerItem.Type != secondInnerItem.Type)
{
//throw exception
}
}
}
else
{
//throw exception
}
}
//move with normal flow
Please let me know if there is any better approach.
First, do the join of firstOuterList and secondOuterList
bool isSubset = false;
var firstOuterList = new List<FirstOuter> { firstOuter };
var secondOuterList = new List<SecondOuter> { secondOuter };
var jointOuterList = firstOuterList.Join(
secondOuterList,
p => new { p.Id, p.Name },
m => new { m.Id, m.Name },
(p, m) => new { FOuterList = p, SOuterList = m }
);
if(jointOuterList.Count != firstOuterList.Count)
{
isSubset = false;
return;
}
foreach(var item in jointOuterList)
{
var jointInnerList = item.firstInnerList.Join(
item.firstInnerList,
p => new { p.Id, p.Type },
m => new { m.Id, m.type },
(p, m) => p.Id
);
if(jointInnerList.Count != item.firstInnerList.Count)
{
isSubset = false;
return;
}
}
Note: I am assuming Id is unique in its outer lists. It means there will not be multiple entries with same id in a list. If no, then we need to use group by in above query
I think to break the question down..
We have two sets of Ids, the Inners and the Outers.
We have two instances of those sets, the Firsts and the Seconds.
We want Second's inner Ids to be a subset of First's inner Ids.
We want Second's outer Ids to be a subset of First's outer Ids.
If that's the case, these are a couple of working test cases:
[TestMethod]
public void ICanSeeWhenInnerAndOuterCollectionsAreSubsets()
{
HashSet<int> firstInnerIds = new HashSet<int>(GetFirstOuterList().SelectMany(outer => outer.Inners.Select(inner => inner.Id)).Distinct());
HashSet<int> firstOuterIds = new HashSet<int>(GetFirstOuterList().Select(outer => outer.Id).Distinct());
HashSet<int> secondInnerIds = new HashSet<int>(GetSecondOuterList().SelectMany(outer => outer.Inners.Select(inner => inner.Id)).Distinct());
HashSet<int> secondOuterIds = new HashSet<int>(GetSecondOuterList().Select(outer => outer.Id).Distinct());
bool isInnerSubset = secondInnerIds.IsSubsetOf(firstInnerIds);
bool isOuterSubset = secondOuterIds.IsSubsetOf(firstOuterIds);
Assert.IsTrue(isInnerSubset);
Assert.IsTrue(isOuterSubset);
}
[TestMethod]
public void ICanSeeWhenInnerAndOuterCollectionsAreNotSubsets()
{
HashSet<int> firstInnerIds = new HashSet<int>(GetFirstOuterList().SelectMany(outer => outer.Inners.Select(inner => inner.Id)).Distinct());
HashSet<int> firstOuterIds = new HashSet<int>(GetFirstOuterList().Select(outer => outer.Id).Distinct());
HashSet<int> secondInnerIds = new HashSet<int>(GetSecondOuterList().SelectMany(outer => outer.Inners.Select(inner => inner.Id)).Distinct());
HashSet<int> secondOuterIds = new HashSet<int>(GetSecondOuterList().Select(outer => outer.Id).Distinct());
firstInnerIds.Clear();
firstInnerIds.Add(5);
firstOuterIds.Clear();
firstOuterIds.Add(5);
bool isInnerSubset = secondInnerIds.IsSubsetOf(firstInnerIds);
bool isOuterSubset = secondOuterIds.IsSubsetOf(firstOuterIds);
Assert.IsFalse(isInnerSubset);
Assert.IsFalse(isOuterSubset);
}
private List<FirstOuter> GetFirstOuterList() { ... }
private List<SecondOuter> GetSecondOuterList() { ... }

Linq between two list with condition

I have settings class
public class Setting
{
public virtual string Key { get; set; }
public virtual string Value { get; set; }
}
and i have to list
IEnumerable<Setting> A1 => contain {"A","1"}{"B","2"}
IEnumerable<Setting> A2 => contain {"A","1"}{"B","5"}
i want linq statment to chose the element from list A2 that have same key and different value here is {"B","5"}
I have try
A2.Where(x => A1.Any(y => y.Value != x.Value)).ToList();
this give me the two elemnts in A2
can any one help me
thank you
**Edit **
my settings class
public class Setting : Entity<int>
{
public virtual DateTime? ModificationDate { get; set; }
public virtual string Name { get; set; }
public virtual string Description { get; set; }
public virtual string Key { get; set; }
public virtual string Value { get; set; }
public virtual string ModifiedBy { get; set; }
public virtual string Type { get; set; }
public virtual string ValidateRegex { get; set; }
public virtual bool IsSystem { get; set; }
}
and i have return from mvc IEnumerable<Setting> let it name settings,
then i get from database the original settings IEnumerable<Setting> let it name dbsettings
i want to know the changed value from settings to make update on it
You need to compare the Key as well:
A2.Where(x => A1.Any(y => y.Key == x.Key && y.Value != x.Value)).ToList();
The following sample returns { "B", "5" } as the result:
void Main()
{
var a1 = new List<Setting>(new Setting[] {
new Setting() { Key = "A", Value = "1" },
new Setting() { Key = "B", Value = "2" } });
var a2 = new List<Setting>(new Setting[] {
new Setting() { Key = "A", Value = "1" },
new Setting() { Key = "B", Value = "5" } });
var result = a2.Where(x => a1.Any(y => y.Key == x.Key && y.Value != x.Value)).ToList();
Console.WriteLine(result);
}
As you are comparing strings, you should be aware that == and != respectively always compares case-sensitive. So the keys need to be written in the same way in both lists (and also differences in case will be recognized as relevant differences). You can also use an overload of string.Compare to specify the comparison options in greater detail.
This should do it:
A2.Where(x => A1.Any(y => y.Key == x.Key && y.Value != x.Value))
BTW, your Setting class seems like reinventing the wheel. Dictionary, Tuple and NameValueCollection can all do that for you.
var A1 = new List<Setting>(){new Setting(){Key = "A", Value = "1"}};
var A2 = new List<Setting>() { new Setting() { Key = "A", Value = "2" } };
var a1Keys = A1.Select(x => x.Key).ToList();
var dupKeys = A2.Where(x => a1Keys.Contains(x.Key)).Select(x=>x.Key);
var res = A2.Where(x => dupKeys.Contains(x.Key));
For performance reasons you could use a lookup:
var a1KeyLookup = A1.ToLookup(x => x.Key);
List<Setting> a2List = A2
.Where(a2 => a1KeyLookup[a2.Key].Any(a1 => a1.Value != a2.Value))
.ToList();
Here's your sample data:
IEnumerable<Setting> A1 = new List<Setting> {
new Setting{Key="A", Value="1"},
new Setting{Key="B", Value="2"},
};
IEnumerable<Setting> A2 = new List<Setting> {
new Setting{Key="A", Value="1"},
new Setting{Key="B", Value="5"},
};
var a1KeyLookup = A1.ToLookup(x => x.Key);
List<Setting> a2List = A2
.Where(a2 => a1KeyLookup[a2.Key].Any(a1 => a1.Value != a2.Value))
.ToList();
It returns as expected a list with a single item: Key="B", Value="5"

Linq Join Not Equal

I have 3 classes:
public class HoteAvail
{
public int HotelID { get; set; }
public string Name { get; set; }
public List<Room> Rooms { get; set; }
}
public class Room
{
public int RoomID { get; set; }
public string RoomName { get; set; }
}
public class DAL
{
public static List<HoteAvail> GetAll()
{
return new List<HoteAvail>()
{
new HoteAvail{HotelID=1,Name="Taj",Rooms=new List<Room>(){new Room{RoomID=1,RoomName="Deliux"}}},
new HoteAvail{HotelID=2,Name="x",Rooms=new List<Room>(){new Room{RoomID=2,RoomName="dd"},new Room{RoomID=1,RoomName="qq"}}},
new HoteAvail{HotelID=3,Name="y",Rooms=new List<Room>(){new Room{RoomID=3,RoomName="yy"},new Room{RoomID=1,RoomName="rr"}}},
};
}
public static List<HoteAvail> GetAllII()
{
return new List<HoteAvail>()
{
new HoteAvail{HotelID=1,Name="Taj",Rooms=new List<Room>(){new Room{RoomID=1,RoomName="Deliux"},new Room{RoomID=1,RoomName="pp"}}},
new HoteAvail{HotelID=4,Name="x",Rooms=new List<Room>(){new Room{RoomID=2,RoomName="dd"},new Room{RoomID=1,RoomName="qq"}}},
new HoteAvail{HotelID=5,Name="y",Rooms=new List<Room>(){new Room{RoomID=3,RoomName="yy"},new Room{RoomID=1,RoomName="rr"}}},
};
}
}
I want to join the values of DAL.GetAll() and DAL.GetAllII() and result should contain only those values whose HotelID doesnot matches.
The final result set should be like :
new HoteAvail{HotelID=2,Name="x",Rooms=new List<Room>(){new Room{RoomID=2,RoomName="dd"},new Room{RoomID=1,RoomName="qq"}}},
new HoteAvail{HotelID=3,Name="y",Rooms=new List<Room>(){new Room{RoomID=3,RoomName="yy"},new Room{RoomID=1,RoomName="rr"}}},
new HoteAvail{HotelID=4,Name="x",Rooms=new List<Room>(){new Room{RoomID=2,RoomName="dd"},new Room{RoomID=1,RoomName="qq"}}},
new HoteAvail{HotelID=5,Name="y",Rooms=new List<Room>(){new Room{RoomID=3,RoomName="yy"},new Room{RoomID=1,RoomName="rr"}}}
I tried:
var groupBNames = new HashSet<string>(DAL.GetAll().Select(x => x.HotelID.ToString()));
var filteredEmployees = DAL.GetAllII().Where(x => !groupBNames.Contains(x.HotelID.ToString()));
var resultList = from a in DAL.GetAll()
where !(DAL.GetAllII().Any(HotelID => HotelID == a))
select a;
But I am not getting any success. Thanks in advance.
I'd recommend doing 2 excepts using a custom IEqualityComparer. You can use this method to create the comparer:
// create a comparer to compare HotelAvail objects by hotelId
// see http://www.codeducky.org/10-utilities-c-developers-should-know-part-two/
// for the implementation of EqualityComparers.Create, which is a nice shortcut
var comparer = EqualityComparers.Create<HoteAvail>(ha => ha.HotelId); // compare by hotelId
var results =
// first take all entries in GetAll() NOT IN GetAllII()
DAL.GetAll().Except(DAL.GetAllII(), comparer)
// then add all entries in GetAllII() NOT IN GetAll()
.Concat(DAL.GetAllII()).Except(DAL.GetAll(), comparer);
You could implement a custom IEqualityComparer<HoteAvail>:
public class HoteAvailComparer: IEqualityComparer<HoteAvail>
{
public bool Equals(HoteAvail x, HoteAvail y)
{
return x != null && y != null && x.HotelID == y.HotelID;
}
public int GetHashCode(HoteAvail obj)
{
return obj.HotelID;
}
}
that you can use for Enumerable.Except which is efficient since it's using a set:
var resultList = DAL.GetAll().Except(DAL.GetAllII(), new HoteAvailComparer());
Console.WriteLine(String.Join(",", resultList.Select(h => h.HotelID))); // 2,3
Update
It is giving me HotelId 2, 3 where as I want to join the values of
DAL.GetAll() and DAL.GetAllII() and result should contain only those
values whose HotelID doesnot matchesi.e. The result should have
HotelId 2,3,4,5
Then you need to use Except from both perspectives:
var hotelComparer = new HoteAvailComparer();
var all1 = DAL.GetAll();
var all2 = DAL.GetAllII();
var resultList = all1.Except(all2, hotelComparer).Concat(all2.Except(all1, hotelComparer));
The desired result 2,3,4,5:
Console.WriteLine(String.Join(",", resultList.Select(h => h.HotelID)));
Of course you could also use Concat and GroupBy, but it's less efficient and maintainable:
resultList = all1.Concat(all2).GroupBy(h => h.HotelID)
.Where(g => g.Count() == 1)
.SelectMany(g => g);
You can use the IEqualityComparer<HoteAvail> for many other LINQ methods like GroupBy+Distinct,Join,Intersect etc.

Categories