Update list of items in c# - c#

I would like to know if you can suggest me an efficient way to update a list of items in c#. Here is a generic example:
If CurrentList is
[ {Id: 154, Name: "George", Salary: 10 000}
{Id: 233, Name: "Alice", Salary: 10 000}]
And NewList is
[ {Id: 154, Name: "George", Salary: 25 000}
{Id: 234, Name: "Bob", Salary: 10 000}]
Then the result should be:
[{Id: 154, Name: "George", Salary: 25 000}
{Id: 234, Name: "Bob", Salary: 10 000} ]
I don't want just to clear the first one and use the values from the second one, but want to update the ones with the same ID, remove the ones that have been deleted and add any new ones.
Thanks in advance.

I would do something like this: (for ordinairy lists)
// the current list
var currentList = new List<Employee>();
currentList.Add(new Employee { Id = 154, Name = "George", Salary = 10000 });
currentList.Add(new Employee { Id = 233, Name = "Alice", Salary = 10000 });
// new list
var newList = new List<Employee>();
newList.Add(new Employee { Id = 154, Name = "George", Salary = 25000 });
newList.Add(new Employee { Id = 234, Name = "Bob", Salary = 10000 });
// clean up
foreach (var oldEmployee in currentList.ToArray())
if (!newList.Any(item => oldEmployee.Id == item.Id))
currentList.Remove(oldEmployee);
// check if the new item is found within the currentlist.
// If so? update it's values else add the object.
foreach (var newEmployee in newList)
{
var oldEmployee = currentList.FirstOrDefault(item => item.Id == newEmployee.Id);
if (oldEmployee == null)
{
// add
currentList.Add(newEmployee);
}
else
{
// modify
oldEmployee.Name = newEmployee.Name;
oldEmployee.Salary = newEmployee.Salary;
}
}
You can speed it up, using dictionaries, but that's not your question (for now)

You can do it with use of for loop and Linq expression:
for (int i = 0; i < NewList.Count; i++)
{
var record = CurrentList.FirstOrDefault(item => item.Id == NewList[i].Id);
if (record == null) { CurrentList.Add(NewList[i]); }
else { record.Id = NewList[i].Id; record.Name = NewList[i].Name; record.Salary = NewList[i].Salary; }
}
CurrentList.RemoveAll(item => NewList.FirstOrDefault(item2 => item2.Id == item.Id) == null);
Example of usage:
Example

A LINQ'y version wrapped in an extension method, could modified to be generic if 'Id' is on a interface of some sort.
The merge Action could potentially be a Merge() method on entity objects such as employee but I chose to use a delegate here .
public class Tests
{
[Test]
public void MergeSpike()
{
// the current list
var currentList = new List<Employee>();
currentList.Add(new Employee { Id = 154, Name = "George", Salary = 10000 });
currentList.Add(new Employee { Id = 233, Name = "Alice", Salary = 10000 });
// new list
var newList = new List<Employee>();
newList.Add(new Employee { Id = 154, Name = "George", Salary = 25000 });
newList.Add(new Employee { Id = 234, Name = "Bob", Salary = 30000 });
currentList.Merge(newList, (o, n) =>
{
if(o.Id != n.Id) throw new ArgumentOutOfRangeException("Attempt to merge on mismatched IDs");
o.Name = n.Name;
o.Salary = n.Salary;
});
Assert.That(currentList.Count(), Is.EqualTo(2));
Assert.That(currentList.First(c => c.Id == 154).Salary, Is.EqualTo(25000));
Assert.That(currentList.Any(c => c.Id == 233), Is.False);
Assert.That(currentList.First(c => c.Id == 234).Salary, Is.EqualTo(30000));
}
}
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public int Salary { get; set; }
}
public static class EmployeeListExtensions
{
public static void Merge(this List<Employee> currentList, IEnumerable<Employee> newList, Action<Employee, Employee> merge)
{
// Updates
currentList.Where(e => newList.Any(n => n.Id == e.Id))
.ToList().ForEach(e => merge(e, newList.First(n1 => n1.Id == e.Id)));
// Deletes
var remove = currentList.Where(cl => newList.All(nl => cl.Id != nl.Id)).ToList();
currentList.RemoveAll(e => remove.Any(r => r.Id == e.Id));
// Inserts
currentList.AddRange(newList.Where(nl => currentList.Any(c => c.Id != nl.Id)));
}
}

Related

Linq query to filter multi level classes

I have my departments data coming from the database. I want to filter this data based on certain criteria.
[
{
"Id":10,
"Name":"Name 10",
"Teachers":[
{
"TeacherId":100,
"TeacherName":null,
"DepartmentId":100,
"Students":[
{
"StudentId":1001,
"StudentName":null,
"TeacherId":10,
"DepartmentId":100
}
]
},
{
"TeacherId":101,
"TeacherName":null,
"DepartmentId":100,
"Students":[
{
"StudentId":1001,
"StudentName":null,
"TeacherId":10,
"DepartmentId":100
}
]
}
]
},
{
"Id":100,
"Name":"Name 10",
"Teachers":[
{
"TeacherId":0,
"TeacherName":null,
"DepartmentId":100,
"Students":[
{
"StudentId":5000,
"StudentName":null,
"TeacherId":50,
"DepartmentId":100
}
]
}
]
},
{
"Id":50,
"Name":"Name 10",
"Teachers":[
{
"TeacherId":0,
"TeacherName":null,
"DepartmentId":100,
"Students":[
{
"StudentId":2000,
"StudentName":null,
"TeacherId":50,
"DepartmentId":100
}
]
}
]
}
]
Now I have to filter the departments based on some values as shown below
var departmenIds = new List<int>() { 10, 20, 30 };
var teachers = new List<int>() { 100, 200, 300 };
var students = new List<int>() { 1000, 2000, 3000 };
I am looking for a query that will return the data in a following fashion
If all department ids exists in the json it will return entire data. If a department with a particular teacher is in the list then only return that teacher and the department. like wise for the student.
I tried this to test if it atleast work at the second level but I am getting all the teachers
var list = allDeplrtments.Where(d => d.Teachers.Any(t => teachers.Contains(t.TeacherId))).ToList();
var list = allDepartments
.Where(d => departmentIds.Contains(d.Id))
.Select(d => new Department() {
Id = d.Id,
Name = d.Name,
Teachers = (d.Teachers.Any(t => teacherIds.Contains(t.TeacherId))
? d.Teachers.Where(t => teacherIds.Contains(t.TeacherId))
: d.Teachers)
.Select(t => new Teacher() {
TeacherId = t.TeacherId,
TeacherName = t.TeacherName,
DepartmentId = d.Id,
Students = t.Students.Any(s => studentIds.Contains(s.StudentId))
? t.Students.Where(s => studentIds.Contains(s.StudentId))
: t.Students
})
})
Would something like this work for you?

Sort array on one variable that can hold multiple values

How can I sort an array of Accounts that need to be sorted on an array of PersonRoles that say which role an associated person has with that account?
For example,
Bob is the Owner (O) of account 12, the Co-signer (CO) of account 123, and the Beneficiary of account 1234.
Joe is the Owner (O) of account 123 and account 1234 the Beneficiary (BE) of account 12.
How would I sort the array of Accounts for Bob in the order of Owners (O) first, then Co-signer('CO'), then Beneficiary (BE) in that order.
Accounts object structure
Accounts
{
AccountNumber: 12,
PersonRoles: [
{
AccountRoleCode: "O",
AccountRoleDescription: "Owner",
Person: "Bob"
},
{
AccountRoleCode: "CO",
AccountRoleDescription: "Co-Signer",
Person: ""
},
{
AccountRoleCode: "BE",
AccountRoleDescription: "Beneficiary",
Person: "Joe"
},
],
Balance: 5.00
},
{
AccountNumber: 123,
PersonRoles: [
{
AccountRoleCode: "O",
AccountRoleDescription: "Owner",
Person: "Joe"
},
{
AccountRoleCode: "CO",
AccountRoleDescription: "Co-Signer",
Person: "Bob"
},
{
AccountRoleCode: "BE",
AccountRoleDescription: "Beneficiary",
Person: null
},
],
Balance: 100.00
},
{
AccountNumber: 1234,
PersonRoles: [
{
AccountRoleCode: "O",
AccountRoleDescription: "Owner",
Person: "Joe"
},
{
AccountRoleCode: "CO",
AccountRoleDescription: "Co-Signer",
Person: null
},
{
AccountRoleCode: "BE",
AccountRoleDescription: "Beneficiary",
Person: "Bob"
},
],
Balance: 10000000.00
}
Original array of Accounts listed under Bob returned from API.
[1234, 12, 123]
Desired Sorted array.
[12, 123, 1234]
My initial approach is to use LINQ on the array, but I'm unsure how to loop through the Accounts[] and then loop through the PersonRoles[] to sort the Accounts[] based on the PersonRoles[].
Does this need like a double LINQ query? Or would another approach be better?
public class AccountsByNameComparer : IComparer<Account>
{
private readonly string _name;
public AccountsByNameComparer(string name)
{
_name = name;
}
public int Compare(Account x, Account y)
{
return AccountSortValue(x).CompareTo(AccountSortValue(y));
}
private int AccountSortValue(Account account)
{
if (account.PersonRoles.Any(role => role.AccountRoleCode == "O"
&& role.Name == _name)) return 0;
if (account.PersonRoles.Any(role => role.AccountRoleCode == "CO"
&& role.Name == _name)) return 1;
if (account.PersonRoles.Any(role => role.AccountRoleCode == "BE"
&& role.Name == _name)) return 2;
return 3;
}
}
Now you can do
accounts.Sort(new AccountsByNameComparer("Bob"));
or
var sorted = accounts.OrderBy(a => a, new AccountsByNameComparer("Bob"));
The benefits of this are
You can unit test the comparer
You can have different comparers for the same class, in case in some other context you want to sort differently
Putting it in a separate class ensures that you won't duplicate code if you find yourself needing the same sorting in multiple places.
This is a regrettably long and convoluted unit test. But you have to verify that it works somehow, and this is usually easier than actually running the whole application.
[TestClass]
public class SortAccountsByNameTests
{
[TestMethod]
public void AccountsAreSortedInCorrectOrder()
{
var account1 = new Account
{
PersonRoles = new PersonRole[]
{
new PersonRole {AccountRoleCode = "BE", Name = "Bob"},
new PersonRole {AccountRoleCode = "CO", Name = "Steve"},
new PersonRole {AccountRoleCode = "O", Name = "John"},
}
};
var account2 = new Account
{
PersonRoles = new PersonRole[]
{
new PersonRole {AccountRoleCode = "CO", Name = "Bob"},
new PersonRole {AccountRoleCode = "O", Name = "Steve"},
new PersonRole {AccountRoleCode = "BE", Name = "John"},
}
};
var account3 = new Account
{
PersonRoles = new PersonRole[]
{
new PersonRole {AccountRoleCode = "O", Name = "Bob"},
new PersonRole {AccountRoleCode = "CO", Name = "Steve"},
new PersonRole {AccountRoleCode = "BE", Name = "John"},
}
};
var account4 = new Account
{
PersonRoles = new PersonRole[]
{
new PersonRole {AccountRoleCode = "O", Name = "Al"},
new PersonRole {AccountRoleCode = "CO", Name = "Steve"},
new PersonRole {AccountRoleCode = "BE", Name = "John"},
}
};
var unsorted = new Account[] {account1, account2, account3, account4};
var comparer = new AccountsByNameComparer("Bob");
var sorted = unsorted.OrderBy(a => a, comparer);
var expectedOrder = new Account[]{account3, account2, account1, account4};
Assert.IsTrue(expectedOrder.SequenceEqual(sorted));
}
}
I'm going to get carried away now. What if you want to change the order in which you sort these without rewriting the entire comparer? Or you just don't like those if statements? (Sorry, this is obnoxious and useless. Why am I doing this?)
public class AccountsByNameComparer : IComparer<Account>
{
private readonly string _name;
private readonly List<string> _preferredRoleSequence;
public AccountsByNameComparer(string name, IEnumerable<string> preferredRoleSequence)
{
_name = name;
_preferredRoleSequence = preferredRoleSequence.ToList();
}
public int Compare(Account x, Account y)
{
return AccountSortValue(x).CompareTo(AccountSortValue(y));
}
private int AccountSortValue(Account account)
{
var rolesMatchedByName = account.PersonRoles
.Where(role => role.Name == _name);
var preferredRoleMatches =
rolesMatchedByName.Select(role =>
_preferredRoleSequence.IndexOf(role.AccountRoleCode))
.Where(index => index > -1)
.ToArray();
if (preferredRoleMatches.Any())
return preferredRoleMatches.Min();
return Int32.MaxValue;
}
}
public class ExecutiveAccountsByNameComparer : AccountsByNameComparer
{
public ExecutiveAccountsByNameComparer(string name)
:base(name, new []{"O", "CO", "BE" }) { }
}
Something like this should work:
var result = accounts
.Select(a => new
{
a.AccountNumber,
RoleCodes = a.PersonRoles
.Where(r => r.Person == "Bob")
.Select(r => r.AccountRoleCode)
.ToArray(),
})
.OrderBy(a => a.RoleCodes.Select(code => GetOrderByCode(code)).Max())
.ThenBy(a => a.AccountNumber);
Here's one way, assuming Bob has one and only one role in all accounts:
var ordered =
from a in Accounts
from r in a.PersonRoles
where r.Person == "Bob"
let ordering = r.AccountRoleCode == "O" ? 1 : r.AccountRoleCode == "CO" ? 2 : 3
orderby ordering
select a.AccountNumber;
Variation, if Bob can have multiple roles (and Bob has a role in every account). In this case, we first select the right role, the first one in the order specified:
var ordered =
from a in Accounts
let bobsrole = (
from r in a.PersonRoles
where r.Person == "Bob"
let o = r.AccountRoleCode == "O" ? 1 : r.AccountRoleCode == "CO" ? 2 : 3
orderby o
select (rolename: r,ordering: o)
).First()
orderby bobsrole.ordering
select a.AccountNumber;
Excercise for the reader: what if there are accounts where Bob is not involved?
You can use a Linq statement like this:
var givenPerson = "Bob";
Accounts.Where(a => a.PersonRoles.SelectMany(r => r.Person).Contains(givenPerson))
.OrderBy(a => a, new CustomComparerForRoleCode(givenPerson));
To do a custom compare to sort by the AccountRoleCode, you'll need a comparer class:
public class CustomComparerForRoleCode : IComparer<PersonRole[]>
{
public string PersonInRole { get; set; }
public CustomComparerForRoleCode(string personInRole) {
this.PersonInRole = personInRole;
}
public int Compare(PersonRole[] x, PersonRole[] y) {
var roleCode = x.First(r => r.Person == PersonInRole).AccountRoleCode;
var otherRoleCode = y.First(r => r.Person == PersonInRole).AccountRoleCode;
if (roleCode == otherRoleCode)
return 0;
switch (roleCode) {
case "O":
return 1;
case "BE":
return -1;
case "CO":
if (otherRoleCode == "O")
return -1;
return 1;
}
}
}
This assumes a person can have only one role per account. Adjust as needed.

Get all combinations of a list grouped by name

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))));
}

Code to collapse duplicate and semi-duplicate records?

I have a list of models of this type:
public class TourDude {
public int Id { get; set; }
public string Name { get; set; }
}
And here is my list:
public IEnumerable<TourDude> GetAllGuides {
get {
List<TourDude> guides = new List<TourDude>();
guides.Add(new TourDude() { Name = "Dave Et", Id = 1 });
guides.Add(new TourDude() { Name = "Dave Eton", Id = 1 });
guides.Add(new TourDude() { Name = "Dave EtZ5", Id = 1 });
guides.Add(new TourDude() { Name = "Danial Maze A", Id = 2 });
guides.Add(new TourDude() { Name = "Danial Maze B", Id = 2 });
guides.Add(new TourDude() { Name = "Danial", Id = 3 });
return guides;
}
}
I want to retrieve these records:
{ Name = "Dave Et", Id = 1 }
{ Name = "Danial Maze", Id = 2 }
{ Name = "Danial", Id = 3 }
The goal mainly to collapse duplicates and near duplicates (confirmable by the ID), taking the shortest possible value (when compared) as name.
Where do I start? Is there a complete LINQ that will do this for me? Do I need to code up an equality comparer?
Edit 1:
var result = from x in GetAllGuides
group x.Name by x.Id into g
select new TourDude {
Test = Exts.LongestCommonPrefix(g),
Id = g.Key,
};
IEnumerable<IEnumerable<char>> test = result.First().Test;
string str = test.First().ToString();
If you want to group the items by Id and then find the longest common prefix of the Names within each group, then you can do so as follows:
var result = from x in guides
group x.Name by x.Id into g
select new TourDude
{
Name = LongestCommonPrefix(g),
Id = g.Key,
};
using the algorithm for finding the longest common prefix from here.
Result:
{ Name = "Dave Et", Id = 1 }
{ Name = "Danial Maze ", Id = 2 }
{ Name = "Danial", Id = 3 }
static string LongestCommonPrefix(IEnumerable<string> xs)
{
return new string(xs
.Transpose()
.TakeWhile(s => s.All(d => d == s.First()))
.Select(s => s.First())
.ToArray());
}
I was able to achieve this by grouping the records on the ID then selecting the first record from each group ordered by the Name length:
var result = GetAllGuides.GroupBy(td => td.Id)
.Select(g => g.OrderBy(td => td.Name.Length).First());
foreach (var dude in result)
{
Console.WriteLine("{{Name = {0}, Id = {1}}}", dude.Name, dude.Id);
}

Select top N records after filtering in each group

I am an old bee in .NET but very new to Linq! After some basic reading I have decided to check my skill and I failed completely! I don't know where I am making mistake.
I want to select highest 2 order for each person for while Amount % 100 == 0.
Here is my code.
var crecords = new[] {
new {
Name = "XYZ",
Orders = new[]
{
new { OrderId = 1, Amount = 340 },
new { OrderId = 2, Amount = 100 },
new { OrderId = 3, Amount = 200 }
}
},
new {
Name = "ABC",
Orders = new[]
{
new { OrderId = 11, Amount = 900 },
new { OrderId = 12, Amount = 800 },
new { OrderId = 13, Amount = 700 }
}
}
};
var result = crecords
.OrderBy(record => record.Name)
.ForEach
(
person => person.Orders
.Where(order => order.Amount % 100 == 0)
.OrderByDescending(t => t.Amount)
.Take(2)
);
foreach (var record in result)
{
Console.WriteLine(record.Name);
foreach (var order in record.Orders)
{
Console.WriteLine("-->" + order.Amount.ToString());
}
}
Can anyone focus and tell me what would be correct query?
Thanks in advance
Try this query:
var result = crecords.Select(person =>
new
{
Name = person.Name,
Orders = person.Orders.Where(order => order.Amount%100 == 0)
.OrderByDescending(x => x.Amount)
.Take(2)
});
Using your foreach loop to print the resulting IEnumerable, the output of it is:
XYZ
-->200
-->100
ABC
-->900
-->800
This has already been answered but if you didn't want to create new objects and simply modify your existing crecords, the code would look like this alternatively. But you wouldn't be able to use anonymous structures like shown in your example. Meaning you would have to create People and Order classes
private class People
{
public string Name;
public IEnumerable<Order> Orders;
}
private class Order
{
public int OrderId;
public int Amount;
}
public void PrintPeople()
{
IEnumerable<People> crecords = new[] {
new People{
Name = "XYZ",
Orders = new Order[]
{
new Order{ OrderId = 1, Amount = 340 },
new Order{ OrderId = 2, Amount = 100 },
new Order{ OrderId = 3, Amount = 200 }
}
},
new People{
Name = "ABC",
Orders = new Order[]
{
new Order{ OrderId = 11, Amount = 900 },
new Order{ OrderId = 12, Amount = 800 },
new Order{ OrderId = 13, Amount = 700 }
}
}
};
crecords = crecords.OrderBy(record => record.Name);
crecords.ToList().ForEach(
person =>
{
person.Orders = person.Orders
.Where(order => order.Amount%100 == 0)
.OrderByDescending(t => t.Amount)
.Take(2);
}
);
foreach (People record in crecords)
{
Console.WriteLine(record.Name);
foreach (var order in record.Orders)
{
Console.WriteLine("-->" + order.Amount.ToString());
}
}
}

Categories