Compare and merge two List<T> into new List<T> - c#

I'm trying to figure out how best to compare and merge two List<T> with a new List<T> being generated that compares multiple properties within each object.
class Account
{
public Account() { }
public string ID { get; set; }
public string Name { get; set; }
public string Value { get; set; }
}
List<Account> Mine = new List<Account>();
List<Account> Yours = new List<Account>();
List<Account> Ours = new List<Account>();
Account m1 = new Account(){ ID = null, Name = "C_First", Value = "joe" };
Account m2 = new Account(){ ID = null, Name = "C_Last", Value = "bloggs" };
Account m3 = new Account(){ ID = null, Name = "C_Car", Value = "ford" };
Mine.Add(m1);
Mine.Add(m2);
Mine.Add(m3);
Account y1 = new Account(){ ID = "1", Name = "C_First", Value = "john" };
Account y2 = new Account(){ ID = "2", Name = "C_Last", Value = "public" };
Yours.Add(y1);
Yours.Add(y2);
The resulting List<Account> Ours would have the following List<Account> objects:
{ ID = "1", Name = "C_First", Value = "joe" };
{ ID = "2", Name = "C_Last", Value = "bloggs" };
{ ID = null, Name = "C_Car", Value = "ford" };
I need to figure out how best to compare the ID and Value properties between both List<Account> objects where the List<Account> Yours ID takes precedence over the List<Account> Mine and the List<Account> Mine Value takes precedence over List<Account> Yours along with any object that's not in List<Account> Yours being added as well.
I've tried the following:
Ours = Mine.Except(Yours).ToList();
which results in List<Ours> being empty.
I've read this post Difference between two lists in which Jon Skeet mentions using a custom IEqualityComparer<T> to do what I need but I'm stuck on how to create an IEqualityComparer<T> that compares more than 1 property value.

Not sure if it can be done in "pue" LINQ, but a bit of procedural code would do the trick:
var dict = Yours.ToDictionary(y => y.Name);
foreach (var m in Mine) {
Account y;
if (dict.TryGetValue(m.Name, out y))
Ours.Add(new Account { ID = y.ID, Name = m.Name, Value = m.Value });
else
Ours.Add(m);
}
After that, printing Ours...
foreach (var o in Ours)
Console.WriteLine("{0}\t{1}\t{2}", o.ID, o.Name, o.Value);
...gives the following result:
1 C_First joe
2 C_Last bloggs
C_Car ford

Try this:
var index = Mine.ToDictionary(x => x.Name);
foreach(var account in Yours)
{
if(index.ContainsKey(account.Name))
{
var item = index[account.Name];
if(item.ID == null)
item.ID = account.ID;
}
index.Add(account.Name, account);
}
Ours = index.Values.ToList();

try this code for IEqualityComparer:
public class D : IEqualityComparer<Account>
{
public bool Equals(Account x, Account y)
{
return x.ID == y.ID && x.Value==y.Value;
}
public int GetHashCode(Account obj)
{
return obj.ID.GetHashCode() ^ obj.Value.GetHashCode();
}
}
used like this:
Ours = Mine.Except(Yours, new D()).ToList();

Related

How to pick item from another list if my conditions match?

I have a query regarding C# list.
I have two different types (account and accountDescriptionHistory) of lists. accountID and accountdescription two column present in both the list.
var account = new List<Account>();
var accountDescriptionHistory = new List<AccountDescriptionHistory>();
Now, I want to prepare a result list based on below conditions.
If accountID match, Pick a accountdescription from accountDescriptionHistory list.
Result list should be type of List();
Class definition
public class Account
{
/// <summary>
/// The account number
/// </summary>
public virtual string AccountNumber { get; set; }
/// <summary>
/// The account's description
/// </summary>
public virtual string Description { get; set; }
}
Account Description class :
public class AccountDescriptionHistory : EntityModel<AccountDescriptionHistory>
{
#region Public Properties
/// <summary>
/// The account description of an account that is valid for a specific date range
/// </summary>
public virtual string AccountDescription { get; set; }
/// <summary>
/// The account this AccountDescriptionHistory is associated with.
/// </summary>
public virtual Account Account { get; set; }
}
Your question is unclear on the exact output. Having said that.
Given
List<Account> accounts = new List<Account>
{
new Account {AccountNumber = "1", Description = "Account 1"},
new Account {AccountNumber = "2", Description = "Account 2"},
new Account {AccountNumber = "4", Description = "Account 4"},
};
List<AccountDescriptionHistory> accountHistories = new List<AccountDescriptionHistory>
{
new AccountDescriptionHistory {AccountDescription = "History Account 1", Account = accounts[0] },
new AccountDescriptionHistory {AccountDescription = "History Account 2", Account = accounts[1] },
new AccountDescriptionHistory {AccountDescription = "History Account 3", Account = new Account {AccountNumber = "3", Description = "Account 3"} },
};
And we're wanting to find all the accountHistories that exist in accounts using the AccountNumber, we can write a Where clause to find all this:
List<AccountDescriptionHistory> result = accountHistories
.Where(x => accounts.Any(y => y.AccountNumber == x.Account.AccountNumber))
.ToList();
If you're wanting to only retrieve the AccountDescription from the result, you can write:
List<string> result = accountHistories
.Where(x => accounts.Any(y => y.AccountNumber == x.Account.AccountNumber))
.Select(x => x.AccountDescription)
.ToList();
If you're wanting to transform the result into an Account with the description from AccountDescriptionHistory, you can do the following:
List<Account> result = accountHistories
.Where(x => accounts.Any(y => y.AccountNumber == x.Account.AccountNumber))
.Select(x => new Account {AccountNumber = x.Account.AccountNumber, Description = x.AccountDescription})
.ToList();
Edit
You can create a function like this:
static Account WithDescriptionHistory(Account account, IEnumerable<AccountDescriptionHistory> accountHistories)
{
AccountDescriptionHistory accountHistory = accountHistories.FirstOrDefault(x => x.Account.AccountNumber == account.AccountNumber);
account.Description = accountHistory?.AccountDescription ?? account.Description;
return account;
}
Than transform the original account variable like:
accounts = accounts
.Select(x => WithDescriptionHistory(x, accountHistories))
.ToList();
Output
History Account 1
History Account 2
Account 4
So base on your comment you need this code
foreach (var a in account)
{
if (!accountDescriptionHistory.Any(x => x.accountID == a.accountID)) continue;
a.Description = accountDescriptionHistory.FirstOrDefault(x => x.accountID == a.accountID).AccountDescription;
}
Assuming that you had these two classes:
public class A
{
public int Id { get; set; }
}
public class B
{
public int Id { get; set; }
public string desc { get; set; }
}
You could use the LINQ Join statement to create a list of the descriptions matched to the Id from the first list
List<A> one = new List<A>
{
new A {Id = 1},
new A {Id = 2}
};
List<B> two = new List<B>
{
new B {Id = 1, desc = "test"},
new B {Id = 2, desc = "test two"}
};
var result = one.Join(
two,
x => x.Id,
y => y.Id,
(x, y) => new {Id = x.Id, Desc = y.desc}
).ToList();
Dear Amit I hope this helps
public partial class TesterForm : Form
{
List<Account> account = new List<Account>();
List<AccountDescriptionHistory> accountDescriptionHistory = new List<AccountDescriptionHistory>();
List<AccountDescriptionHistory> resultlist = new List<AccountDescriptionHistory>();
public TesterForm()
{
InitializeComponent();
}
public class Account
{
public int accountID { get; set; }
public string accountdescription { get; set; }
}
public class AccountDescriptionHistory
{
public int accountID { get; set; }
public string accountdescription { get; set; }
}
private void button1_Click(object sender, EventArgs e)
{
Account accountmodel = new Account();
accountmodel.accountID = 1;
accountmodel.accountdescription = "desc1";
account.Add(accountmodel);
Account accountmodel1 = new Account();
accountmodel1.accountID = 2;
accountmodel1.accountdescription = "desc2";
account.Add(accountmodel1);
Account accountmodel3 = new Account();
accountmodel3.accountID = 3;
accountmodel3.accountdescription = "desc3";
account.Add(accountmodel3);
AccountDescriptionHistory accountdescmodel = new AccountDescriptionHistory();
accountdescmodel.accountID = 3;
accountdescmodel.accountdescription = "desc4";
accountDescriptionHistory.Add(accountdescmodel);
AccountDescriptionHistory accountdescmodel2 = new AccountDescriptionHistory();
accountdescmodel2.accountID = 4;
accountdescmodel2.accountdescription = "desc5";
accountDescriptionHistory.Add(accountdescmodel2);
AccountDescriptionHistory accountdescmodel3 = new AccountDescriptionHistory();
accountdescmodel3.accountID = 5;
accountdescmodel3.accountdescription = "desc6";
accountDescriptionHistory.Add(accountdescmodel3);
}
private void button2_Click(object sender, EventArgs e)
{
foreach (Account item in account)
{
foreach (AccountDescriptionHistory item2 in accountDescriptionHistory)
{
if (item.accountID==item2.accountID)
{
AccountDescriptionHistory history = new AccountDescriptionHistory();
history.accountID = item.accountID;
history.accountdescription = item2.accountdescription;
resultlist.Add(history);
}
}
}
comboBox1.DataSource = resultlist;
comboBox1.DisplayMember = "accountdescription";
}
}
I am using Visual studio 2019 Forms, you will need two buttons and a combobutton to see the result as i tested it.
add to your form combobox1, button1 and button2.

Compare 2 lists of same object type

I have 2 lists of a specific type, in this case it is List. In the class DataDictionary there is a property called TableName. I have 2 lists with the same type I am trying to compare. I have other properties aswell which I need to hold association with that specific TableName so I can't just compare them separately.
I need to find a way to compare the TableName in 2 different lists of DataDictionary and then find which ones they don't have in common. From there I then need to compare all the other properties against the 2 items in each list with the same TableName.
I have tried to use the Except IEnumerate solution which works if you just compare the strings directly but I don't know how to keep the association with the object.
List<DataDictionary> ColumnsDataDict = daDD.getTablesandColumnsDataDictionary();
List<DataDictionary> ColumnsWizard = daWiz.getColumnsWizard();
var newlist = ColumnsWizard.Except(ColumnsDataDict);
foreach(DataDictionary item in newlist)
{
Console.WriteLine(item.TableName);
}
Here is the DataDictionary class:
public string TableName { get; set; }
public string Description { get; set; }
public string TableID { get; set; }
public string ColumnDesc { get; set; }
public string ColumnName { get; set; }
This directly compares the objects, but I just want to compare the TableName property in my DataDictionary class. I want this to then get a list of objects that doesn't have the same table name in each list. Any help is appreciated, thanks!
I don't believe your problem can be solved with LINQ alone and there for I would recommend using a good old loop.
If you want to compare two lists, you will have to use two loops nested in one another.
Like this:
public class Program
{
public static void Main()
{
var listA = new List<Foo>() {
new Foo() { TableName = "Table A", Value = "Foo 1" },
new Foo() { TableName = "Table B", Value = "Foo 1" },
};
var listB = new List<Foo>() {
new Foo() { TableName = "Table A", Value = "Foo 10" },
new Foo() { TableName = "Table C", Value = "Foo 12" },
};
foreach (var itemA in listA)
{
foreach (var itemB in listB)
{
if (itemA.TableName == itemB.TableName)
{
Console.WriteLine($"ItemA's Value: {itemA.Value}");
Console.WriteLine($"ItemB's Value: {itemB.Value}");
}
}
}
}
}
public class Foo
{
public string TableName { get; set; }
public string Value { get; set; }
}
At the position where I am printing the values of itemA and itemB you can compare your objects and find the difference between them.
So instead of:
Console.WriteLine($"ItemA's Value: {itemA.Value}");
Console.WriteLine($"ItemB's Value: {itemB.Value}");
Maybe something like this:
if (itemA.Value != itemB.Value) //extend the `if` with all the properties you want to compare
{
Console.WriteLine($"ItemA's Value isn't equal to ItemB's Value");
}
If you need to check if listA doesn't have an entry in listB then you can extend the inner loop like this:
foreach (var itemA in listA)
{
var found = false;
foreach (var itemB in listB)
{
if (itemA.TableName == itemB.TableName)
{
found = true;
}
}
if (found == false)
{
Console.WriteLine("ItemA's TableName wasn't found in listB");
}
}
SOLUTION:
// Merges both objects
List<DataDictionary> duplicatesRemovedLists = ColumnsDataDict.Concat (ColumnsWizard).ToList ();
// Removes common objects based on its property value (eg: TableName)
foreach (var cddProp in ColumnsDataDict) {
foreach (var cwProp in ColumnsWizard) {
if ((cddProp.TableName == cwProp.TableName)) {
duplicatesRemovedLists.Remove (cddProp);
duplicatesRemovedLists.Remove (cwProp);
}
}
}
// Prints expected output
foreach (DataDictionary item in duplicatesRemovedLists) Console.WriteLine (item.TableName);
You can use several LINQ methods implementing a customer IEqualityComparer.
For instance having a comparer for the property TableName:
public class TableNameEqualityComparer : IEqualityComparer<DataDictionary>
{
public bool Equals(DataDictionary x, DataDictionary y)
{
if (x == null && y == null)
{
return true;
}
return x != null && y != null && x.TableName == y.TableName;
}
public int GetHashCode(DataDictionary obj)
{
return obj?.TableName?.GetHashCode() ?? 0;
}
}
Two instances returning true for the Equals method must return
the same value for GetHashCode.
Just make an instance of your custom comparer.
var listA = new List<DataDictionary>()
{
new DataDictionary() {TableName = "Table A"},
new DataDictionary() {TableName = "Table B"},
};
var listB = new List<DataDictionary>()
{
new DataDictionary() {TableName = "Table A"},
new DataDictionary() {TableName = "Table C"},
};
var tableNameComparer = new TableNameEqualityComparer();
And then use it with different LINQ methods:
// A - B
var listAExceptB = listA.Except(listB, tableNameComparer);
Assert.Collection(listAExceptB,
x => Assert.Equal("Table B", x.TableName));
// B - A
var listBExceptA = listB.Except(listA, tableNameComparer);
Assert.Collection(listBExceptA,
x => Assert.Equal("Table C", x.TableName));
// A ∩ B
var listIntersect = listA.Intersect(listB, tableNameComparer);
Assert.Collection(listIntersect,
x => Assert.Equal("Table A", x.TableName));
// A ∪ B
var listUnion = listA.Union(listB, tableNameComparer);
Assert.Collection(listUnion,
x => Assert.Equal("Table A", x.TableName),
x => Assert.Equal("Table B", x.TableName),
x => Assert.Equal("Table C", x.TableName));

Converting list from one class to another

I'm trying to convert a group a complex list in C# (with Linq)
public class classA
{
public string Name { get; set; }
public int id { get; set; }
public string phone { get; set; }
public string interest { get; set; }
}
My first class is classA where it contains many list of elements like below.
List<classA> obj = new List<classA>();
obj.Add(new classA { id = 1, Name = "a", phone = "321", interest = "Playing" });
obj.Add(new classA { id = 1, Name = "2", phone = "123", interest="Tv" });
From this I need to group by using the id, So I've used Linq
var item = obj.GroupBy(a => a.id).Select(ac => ac.ToList()).ToList();
I've another class called classB which hold's the values others than id from the classA (where it'd be hold all subset of different attributes)
public class classB
{
public string Name { get; set; }
public string phone { get; set; }
public string interest { get; set; }
}
My Final Class looks likes,
public class Final
{
public int id { get; set; }
public List<classB> details { get; set; }
public Final()
{
details = new List<classB>();
}
}
My requirements are, after grouping the classA based on id, I need to convert that into my final class.
So I did like below,
public static void Main()
{
Console.WriteLine("Hello World");
List<classA> obj = new List<classA>();
obj.Add(new classA { id = 1, Name = "a", phone = "321", interest = "Playing" });
obj.Add(new classA { id = 1, Name = "b", phone = "123", interest = "Tv" });
obj.Add(new classA { id = 2, Name = "c", phone = "12322", interest = "Tv" });
obj.Add(new classA { id = 3, Name = "d", phone = "12333", interest = "Tv" });
var item = obj.GroupBy(a => a.id).Select(ac => ac.ToList()).ToList();
List<Final> finalobjList = new List<Final>();
foreach (var report in item)
{
Final finalObj = new Final();
foreach (var result in report)
{
finalObj.id = result.id;
}
var data = report.Select(x => new classB { Name = x.Name, phone = x.phone, interest = x.interest }).ToList();
finalObj.details = data;
finalobjList.Add(finalObj);
}
Console.WriteLine(finalobjList.Count());
}
I believe there is another easy way to achieve this using linq without using foreach multiple times
Appreciate your help!
You should be able to use your existing code except when you do your Select, select a new Final and use the group's Key for the Id, and convert the ac.ToList to a list of ClassB for the Details:
var item = obj
.GroupBy(a => a.id)
.Select(ac =>
new Final
{
Id = ac.Key,
Details = ac.Select(a =>
new classB {interest = a.interest, phone = a.phone, Name = a.Name})
.ToList()
});
var finalobjList = obj.GroupBy(a => a.id).Select(x => new Final() { id = x.Key, details = x.Select(y => new classB() { Name = y.Name }).ToList() } ).ToList();
(Code only answer - please dont hate me)
var items = (from a in obj
group new classB {Name = a.Name, phone = a.phone, interest = a.interest} by a.id into aa
select new Final { id = aa.Key, B= aa.ToList()}).ToList();

How to flatten nested objects (LINQ)

I'm doing some work on an old Winforms grid and i have two Models that i am trying to flatten and assign to a DataGridView.
Here are my sample models.
public class StockItem
{
public string StockName { get; set; }
public int Id { get; set; }
public List<Warehouse> Warehouses { get; set; }
}
public class Warehouse
{
public string WarehouseName { get; set; }
public int Id { get; set; }
}
The data works in a way that a warehouse must first be created and then assigned to each StockItem. A StockItem may have all the warehouses or may only have one.
I need to flatten the data so that the grid shows the StockName and then all the associated warehouses for the stock item.
Example
StockCode1 Warehouse1 Warehouse2 Warehouse3
StockCode2 Warehouse1 Warehouse2
StockCode2 Warehouse1 Warehouse3
I've attempted to do this via a Linq query but can only get a record per StockItem\Warehouse.
You can achieve it by creating a DataTable that yon can easily use as a source for the gridview. First add all columns and then for each stock add the warehouses:
var warehouseNames =
stocks
.SelectMany(x => x.Warehouses.Select(y => y.WarehouseName)).Distinct();
var dt = new DataTable();
dt.Columns.Add("StockCode");
foreach (var name in warehouseNames)
{
dt.Columns.Add(name);
}
foreach (var stock in stocks)
{
var row = dt.NewRow();
row["StockCode"] = stock.Id;
foreach (var warehouse in stock.Warehouses)
{
row[warehouse.WarehouseName] = warehouse.Id;
}
dt.Rows.Add(row);
}
I do not recommend it, but you can use dynamic objects to create objects with the shape you want. Doing this is not a common C# pattern. This is more common in languages like Python or Javascript.
C# is a strongly typed language and venturing into the world of dynamic objects should only be considered when absolutely necessary (think parsing a json blob). I strongly consider you reevaluate what you need to do and approach it from a different angle.
Something like this:
var availableWarehouses = new [] {
new Warehouse {
WarehouseName = "Warehouse1",
Id = 1
},
new Warehouse {
WarehouseName = "Warehouse2",
Id = 2
},
new Warehouse {
WarehouseName = "Warehouse3",
Id = 3
}
};
var stocks = new [] {
new StockItem {
StockName = "StockCode1",
Id = 1,
Warehouses = new List<Warehouse> { availableWarehouses[0], availableWarehouses[1], availableWarehouses[2] }
},
new StockItem {
StockName = "StockCode2",
Id = 2,
Warehouses = new List<Warehouse> { availableWarehouses[0], availableWarehouses[1] }
},
new StockItem {
StockName = "StockCode3",
Id = 3,
Warehouses = new List<Warehouse> { availableWarehouses[0], availableWarehouses[2] }
}
};
var flatten = stocks.Select(item => new {
StockName = item.StockName,
WarehousesNames = availableWarehouses.Select(warehouse => item.Warehouses.Contains(warehouse) ? warehouse.WarehouseName : " ")
.Aggregate((current, next) => current + "\t" + next)
});
foreach(var item in flatten) {
Console.WriteLine("{0}\t{1}", item.StockName, item.WarehousesNames);
}
That should give you what you need:
var flattened = stockItems
.Select(x => new {
StockName = x.StockName,
WarehouseNames = x.Warehouses
.Select(y => y.WarehouseName)
.ToList() })
.ToList();
It will result in a collection of items that contain StockName and a list of WarehouseName strings. ToList added to enumerate the query.
For these sample data:
List<StockItem> stockItems = new List<StockItem>
{
new StockItem
{
StockName ="A",
Id = 1,
Warehouses = new List<Warehouse>
{
new Warehouse { Id = 1, WarehouseName = "x" },
new Warehouse { Id = 2, WarehouseName = "y" }
}
},
new StockItem
{
StockName = "B",
Id = 2,
Warehouses = new List<Warehouse>
{
new Warehouse { Id = 3, WarehouseName = "z" },
new Warehouse { Id = 4, WarehouseName = "w" }
}
}
};
I've got the following result:

How to make select query by string property name in lambda expression?

I would like to make a query by using lambda select,
Like below:
public class Foo{
public int Id {get;set;}
public string Name {get;set;}
public string Surname {get;set;}
}
var list = new List<Foo>();
var temp = list.Select(x=> x("Name"),("Surname"));
The property name needs to be sent as a string,
I dont know how to use, I have given it for being a example.
is it possible?
Edit:
Foo list :
1 A B
2 C D
3 E F
4 G H
I don't know type of generic list, I have property name such as "Name", "Surname"
I want to be like below:
Result :
A B
C D
E F
G H
The following code snippet shows 2 cases. One filtering on the list, and another creating a new list of anonymous objects, having just Name and Surname.
List<Foo> list = new List<Foo>();
var newList = list.Select(x=> new {
AnyName1 = x.Name,
AnyName2 = x.Surname
});
var filteredList = list.Select(x => x.Name == "FilteredName" && x.Surname == "FilteredSurname");
var filteredListByLinq = from cust in list
where cust.Name == "Name" && cust.Surname == "Surname"
select cust;
var filteredByUsingReflection = list.Select(c => c.GetType().GetProperty("Name").GetValue(c, null));
Interface
If you have access to the types in question, and if you always want to access the same properties, the best option is to make the types implement the same interface:
public interface INamable
{
string Name { get; }
string Surname { get; }
}
public class Foo : INamable
{
public int Id { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
}
This will preserve type safety and enable queries like this one:
public void ExtractUsingInterface<T>(IEnumerable<T> list) where T : INamable
{
var names = list.Select(o => new { Name = o.Name, Surname = o.Surname });
foreach (var n in names)
{
Console.WriteLine(n.Name + " " + n.Surname);
}
}
If, for some reason, you can't alter the original type, here are two more options.
Reflection
The first one is reflection. This is Mez's answer, i'll just rephrase it with an anonymous type like in the previous solution (not sure what you need exactly):
public void ExtractUsingReflection<T>(IEnumerable<T> list)
{
var names = list.Select(o => new
{
Name = GetStringValue(o, "Name"),
Surname = GetStringValue(o, "Surname")
});
foreach (var n in names)
{
Console.WriteLine(n.Name + " " + n.Surname);
}
}
private static string GetStringValue<T>(T obj, string propName)
{
return obj.GetType().GetProperty(propName).GetValue(obj, null) as string;
}
Dynamic
The second uses dynamic:
public void ExtractUsingDynamic(IEnumerable list)
{
var dynamicList = list.Cast<dynamic>();
var names = dynamicList.Select(d => new
{
Name = d.Name,
Surname = d.Surname
});
foreach (var n in names)
{
Console.WriteLine(n.Name + " " + n.Surname);
}
}
With that in place, the following code:
IEnumerable<INamable> list = new List<Foo>
{
new Foo() {Id = 1, Name = "FooName1", Surname = "FooSurname1"},
new Foo() {Id = 2, Name = "FooName2", Surname = "FooSurname2"}
};
ExtractUsingInterface(list);
// IEnumerable<object> list... will be fine for both solutions below
ExtractUsingReflection(list);
ExtractUsingDynamic(list);
will produce the expected output:
FooName1 FooSurname1
FooName2 FooSurname2
FooName1 FooSurname1
FooName2 FooSurname2
FooName1 FooSurname1
FooName2 FooSurname2
I'm sure you can fiddle with that and get to what you are trying to achieve.
var temp = list.Select(x => x.Name == "Name" && x.Surname == "Surname");
var temp = list.Select(x => new {Name = x.Name, Surname = x.Surname});

Categories