Merge C# list elements based on attribute of the object - c#

I have a list in C#. List where User object has few parameters. username, age something like that.
In the list there are duplicate (only twice) entities according to the username. Eventhoug the usernames are same, other attributes are not same.
How can I merge those elements and remove duplications of elements in that list.
P.S: Eventhough there are duplicate entities according to the username, other atteributes empty in one element and other element has the values for those attributes.

var duplicates = Users
.GroupBy(u => u.UserName)
.Where(g => g.Count() > 1)
.ToList();
Each member is now an IEnumerable with the same UserName
foreach(var duplicate in duplicates)
{
// write some logic to combine >= 2 Users
// and remove all but 1 from original Users
// a rough idea:
var main = duplicate.First();
foreach(var user in duplicate.Skip(1))
{
// merge user with main
....
toDeleteList.Add(user);
}
}

You can use a IEqualityComparer
internal class UserEqualChecker : IEqualityComparer<User>
{
public bool Equals(User x, User y)
{
//Code for what makes them equal
//for instance
return x.UserName.Equals(y.UserName, System.StringComparison.OrdinalIgnoreCase);
}
//.....
}
And then...
var list = new List<User>();
//put the data into the list...
list.Distinct(new UserEqualChecker());
This way, you have a reusable comparer

You can group your list using linq and then create a new object with your merged data:
var merged = from item in mylist
group item by item.UserName into grp
select new YourClass {
Username = grp.Key,
Proeperty1 = grp.Where(g => g.Porperty1 != null).FirstOrDefault(),
Property2 = ...
}
This assume usernames are CaseSensitive, you can change the group by using UserName.Toupper() or something similar...
As #HenkHolterman said, you have to define how select values for your properties.
The rule i wrote for Prperty1 is only an example...

Related

Update a property field in a List

I have a List<Map> and I wanted to update the Map.Target property based from a matching value from another List<Map>.
Basically, the logic is:
If mapsList1.Name is equal to mapsList2.Name
Then mapsList1.Target = mapsList2.Name
The structure of the Map class looks like this:
public class Map {
public Guid Id { get; set; }
public string Name { get; set; }
public string Target { get; set; }
}
I tried the following but obviously it's not working:
List<Map> mapsList1 = new List<Map>();
List<Map> mapsList2 = new List<Map>();
// populate the 2 lists here
mapsList1.Where(m1 => mapsList2.Where(m2 => m1.Name == m2.Name) ) // don't know what to do next
The count of items in list 1 will be always greater than or equal to the count of items in list 2. No duplicates in both lists.
Assuming there are a small number of items in the lists and only one item in list 1 that matches:
list2.ForEach(l2m => list1.First(l1m => l1m.Name == l2m.Name).Target = l2m.Target);
If there are more than one item in List1 that must be updated, enumerate the entire list1 doing a First on list2.
list1.ForEach(l1m => l1m.Target = list2.FirstOrDefault(l2m => l1.Name == l2m.Name)?.Target ?? l1m.Target);
If there are a large number of items in list2, turn it into a dictionary
var d = list2.ToDictionary(m => m.Name);
list1.ForEach(m => m.Target = d.ContainsKey(m.Name) ? d[m.Name].Target : m.Target);
(Presumably list2 doesn't contain any repeated names)
If list1's names are unique and everything in list2 is in list1, you could even turn list1 into a dictionary and enumerate list2:
var d=list1.ToDictionary(m => m.Name);
list2.ForEach(m => d[m.Name].Target = m.Target);
If List 2 has entries that are not in list1 or list1 has duplicate names, you could use a Lookup instead, you'd just have to do something to avoid a "collection was modified; enumeration may not execute" you'd get if you were trying to modify the list it returns in response to a name
mapsList1.Where(m1 => mapsList2.Where(m2 => m1.Name == m2.Name) ) // don't know what to do next
LINQ Where doesn't really work like that / that's not a statement in itself. The m1 is the entry from list1, and the inner Where would produce an enumerable of list 2 items, but it doesn't result in the Boolean the outer Where is expecting, nor can you do anything to either of the sequences because LINQ operations are not supposed to have side effects. The only thing you can do with a Where is capture or use the sequence it returns in some other operation (like enumerating it), so Where isn't really something you'd use for this operation unless you use it to find all the objects you need to alter. It's probably worth pointing out that ForEach is a list thing, not a LINQ thing, and is basically just another way of writing foreach(var item in someList)
If collections are big enough better approach would be to create a dictionary to lookup the targets:
List<Map> mapsList1 = new List<Map>();
List<Map> mapsList2 = new List<Map>();
var dict = mapsList2
.GroupBy(map => map.Name)
.ToDictionary(maps => maps.Key, maps => maps.First().Target);
foreach (var map in mapsList1)
{
if (dict.TryGetValue(map.Name, out var target))
{
map.Target = target;
}
}
Note, that this will discard any possible name duplicates from mapsList2.

Linq intersect to filter multiple criteria against list

I'm trying to filter users by department. The filter may contain multiple departments, the users may belong to multiple departments (n:m). I'm fiddling around with LINQ, but can't find the solution. Following example code uses simplified Tuples just to make it runnable, of course there are some real user objects.
Also on CSSharpPad, so you have some runnable code: http://csharppad.com/gist/34be3e2dd121ffc161c4
string Filter = "Dep1"; //can also contain multiple filters
var users = new List<Tuple<string, string>>
{
Tuple.Create("Meyer", "Dep1"),
Tuple.Create("Jackson", "Dep2"),
Tuple.Create("Green", "Dep1;Dep2"),
Tuple.Create("Brown", "Dep1")
};
//this is the line I can't get to work like I want to
var tuplets = users.Where(u => u.Item2.Intersect(Filter).Any());
if (tuplets.Distinct().ToList().Count > 0)
{
foreach (var item in tuplets) Console.WriteLine(item.ToString());
}
else
{
Console.WriteLine("No results");
}
Right now it returns:
(Meyer, Dep1)
(Jackson, Dep2)
(Green, Dep1;Dep2)
(Brown, Dep1)
What I would want it to return is: Meyer,Green,Brown. If Filter would be set to "Dep1;Dep2" I would want to do an or-comparison and find *Meyer,Jackson,Green,Brown" (as well as distinct, as I don't want Green twice). If Filter would be set to "Dep2" I would only want to have Jackson, Green. I also played around with .Split(';'), but it got me nowhere.
Am I making sense? I have Users with single/multiple departments and want filtering for those departments. In my output I want to have all users from the specified department(s). The LINQ-magic is not so strong on me.
Since string implements IEnumerable, what you're doing right now is an Intersect on a IEnumerable<char> (i.e. you're checking each letter in the string). You need to split on ; both on Item2 and Filter and intersect those.
var tuplets = users.Where(u =>
u.Item2.Split(new []{';'})
.Intersect(Filter.Split(new []{';'}))
.Any());
string[] Filter = {"Dep1","Dep2"}; //Easier if this is an enumerable
var users = new List<Tuple<string, string>>
{
Tuple.Create("Meyer", "Dep1"),
Tuple.Create("Jackson", "Dep2"),
Tuple.Create("Green", "Dep1;Dep2"),
Tuple.Create("Brown", "Dep1")
};
//I would use Any/Split/Contains
var tuplets = users.Where(u => Filter.Any(y=> u.Item2.Split(';').Contains(y)));
if (tuplets.Distinct().ToList().Count > 0)
{
foreach (var item in tuplets) Console.WriteLine(item.ToString());
}
else
{
Console.WriteLine("No results");
}
In addition to the other answers, the Contains extension method may also be a good fit for what you're trying to do if you're matching on a value:
var result = list.Where(x => filter.Contains(x.Value));
Otherwise, the Any method will accept a delegate:
var result = list.Where(x => filter.Any(y => y.Value == x.Value));

How to modify only one or two field(s) in LINQ projections?

I have this LINQ query:
List<Customers> customers = customerManager.GetCustomers();
return customers.Select(i => new Customer {
FullName = i.FullName,
Birthday = i.Birthday,
Score = i.Score,
// Here, I've got more fields to fill
IsVip = DetermineVip(i.Score)
}).ToList();
In other words, I only want one or two fields of the list of the customers to be modified based on a condition, in my business method. I've got two ways to do this,
Using for...each loop, to loop over customers and modify that field (imperative approach)
Using LINQ projection (declarative approach)
Is there any technique to be used in LINQ query, to only modify one property in projection? For example, something like:
return customers.Select(i => new Customer {
result = i // telling LINQ to fill other properties as it is
IsVip = DetermineVip(i.Score) // then modifying this one property
}).ToList();
you can use
return customers.Select(i => {
i.IsVip = DetermineVip(i.Score);
return i;
}).ToList();
Contrary to other answers, you can modify the source content within linq by calling a method in the Select statement (note that this is not supported by EF although that shouldn't be a problem for you).
return customers.Select(customer =>
{
customer.FullName = "foo";
return customer;
});
You "can", if you create a copy constructor, which initializes a new object with the values of an existing object:
partial class Customer
{
public Customer(Customer original)
{
this.FullName = original.FullName;
//...
}
}
Then you can do:
return customers.Select(i => new Customer(i) { IsVip = DetermineVip(i.Score)})
.ToList()
But the downfall here is you will be creating a new Customer object based on each existing object, and not modifying the existing object - this is why I have put "can" in quotes. I do not know if this is truly what you desire.
No, Linq was designed to iterate over collections without affecting the contents of the source enumerable.
You can however create your own method for iterating and mutating the collection:
public static void ForEach<T>(this IEnumerable<T> enumeration, Action<T> action)
{
foreach(T item in enumeration)
{
action(item);
}
}
You can then use as follows:
return customers.ToList()
.ForEach(i => i.IsVip = DetermineVip(i.Score))
.ToList();
Note that the first ForEach will clone the source list.
As customers already is a List, you can use the ForEach method:
customers.ForEach(c => c.IsVip = DetermineVip(c.Score));

Get one unique List<T> from three non-unique List<T>'s

I have three lists of User:
ICollection<User> listOne = _somewhere.GetUsers(1);
ICollection<User> listTwo = _somewhere.GetUsers(2);
ICollection<User> listThree = _somewhere.GetUsers(3);
The "unique" identifier to compare on is a string field called "Email".
How do i get a unique list from the three (e.g no dupes).
I've got a unique list from two lists before using Except, but not sure how to do it with three? Do i have to use Except on the first two, then do it again on the result of the first two and the third?
Also, i should mention that the list of User's comes from external Web API calls, and there is no guarantee that each list has a unique list of email addresses.
So it's like i need two steps:
In each list, get rid of dupes
Combine the three unique lists to get one unique list.
You can just union the lists and do a dedupe (using Distinct()) once on the combined list.
var uniqueList = listOne.Union(listTwo)
.Union(listThree)
.Distinct(new EmailComparer())
.ToList();
For the comparer could be as simple as this:
class EmailComparer : IEqualityComparer<User>
{
public bool Equals(User x, User y)
{
return x.Email == y.Email;
}
public int GetHashCode(User obj)
{
return obj.Email.GetHashCode();
}
}
Edit:
As pointed out in comments Distinct() is not needed if we apply the custom email comparer to Union():
var emailComparer = new EmailComparer();
var uniqueList = listOne.Union(listTwo, emailComparer)
.Union(listThree, emailComparer)
.ToList();
If it does not matter which user you pick from the list of users with the same e-mail, you can do this:
var res = listOne.Concat(listTwo).Concat(listThree)
.GroupBy(u => u.Email)
.Select(g => g.First());
Again, this assumes that when e-mail addresses are the same, it does not matter which user you'd pick.
First define how we want to define uniqueness:
private class EmailEqComparer : IEqualityComparer<User>
{
public bool Equals(User x, User y)
{
//don't bother shortcutting on reference equality, since if they come from
//separate web-calls it's unlikely to happen, though it could
//with some optimisations on the web-client code.
if(x == null)
return y == null;
if(y == null)
return false;
return x.Email == y.Email;
}
public int GetHashCode(User obj)
{
return obj == null ? 0 : obj.Email.GetHashCode();
}
}
Now call Distinct on the items of each, and put the results into a list:
var distinctUnion = listOne.Concat(listTwo).Concat(listThree).Distinct(new EmailEqComparer());

Checking two lists for Modifications

I have got two lists of two different type which has the following common properties.
Id -->used to identify corresponding objects
Bunch of other Properties
ModificationDate
Need to compare these two lists based on Modification date.If the modified date is different (first list ModificationDate greater than second list's ModificationDate then, copy all the properties if that item from first list to second.
Please let me know the best way to do this.
EDITED:Second list may or maynot contain all elements of the first and vice versa.My first list is always the source list. so if an item is present in list 1 and not present in list 2 we need to add it in list 2. also if an item present in list 2 but in not in list 1 then remove it from list2.
Finding added/deleted items
var list1 = new List<MyType>();
var list2 = new List<MyType>();
// These two assume MyType : IEquatable<MyType>
var added = list1.Except(list2);
var deleted = list2.Except(list1);
// Now add "added" to list2, remove "deleted" from list2
If MyType does not implement IEquatable<MyType>, or the implementation is not based solely on comparing ids, you will need to create an IEqualityComparer<MyType>:
class MyTypeIdComparer : IEqualityComparer<MyType>
{
public bool Equals(MyType x, MyType y)
{
return x.Id.CompareTo(y.Id);
}
public int GetHashCode(MyType obj)
{
return obj.Id.GetHashCode();
}
}
Which will allow you to do:
// This does not assume so much for MyType
var comparer = new MyTypeIdComparer();
var added = list1.Except(list2, comparer);
var deleted = list2.Except(list1, comparer);
Finding modified items
var modified = list1.Concat(list2)
.GroupBy(item => item.Id)
.Where(g => g.Select(item => item.ModificationDate)
.Distinct().Count() != 1);
// To propagate the modifications:
foreach(var grp in modified) {
var items = grp.OrderBy(item => item.ModificationDate);
var target = items.First(); // earliest modification date = old
var source = grp.Last(); // latest modification date = new
// And now copy properties from source to target
}
This might be able to help. The Linq library has lots of decent functions, such as Except, Intersection.
http://msdn.microsoft.com/en-us/library/bb397894.aspx
The provided link was helpful in comparing two lists of different types
Comparing Collections in .Net

Categories

Resources