I found this post Match elements between 2 collections with Linq in c# which explained how you can use Intersect to find matching elements between two lists.
Can you use this to match elements in two lists that are not of exactly the same, but have "sub values" that you want to match?
My example is this: I have two collections, each containing lists of XElements. The one with elements called <link> and the other with elements called <file>, each have attributes called "path", and it is this attribute I want to match. If the path attribute is equal, I want a match.
In the result set I would like a list of all the elements whose paths match the paths of the elements.
How can this be done?
I would suggest to use LambdaComparer which can be passed into the Intersect() method as Equality Comparer, it allows specifying comparison logic in place by providing boolean condition instead introducing a new comparer class each time, so your code would be clear enough:
firstCollection.Intersect(
secondCollection,
new LambdaComparer<YourClass>(
(item1, item2) => item1.PropertyName == item2.PropertyName));
// Below are lists and User class which demonstrates LambdaComparer and Intersect()
public class User
{
public string Name { get; set; }
}
IList<User> list1 = new List<User>
{
new User {Name = "A"},
new User { Name = "B"}
};
List<User> list2 = new List<User>
{
new User {Name = "C"},
new User { Name = "B"}
};
var resultSet = list1.Intersect<User>(
list2,
new LambdaComparer<User>((item1, item2) => item1.Name == item2.Name));
Basically if you need to compare cusotm attributes, you still can encapsulate this logic into
Func<User, User, bool> userNameComparer = (user1, user2) =>
{
// check attributes using user1.GetType().GetCustomAttributes()
};
And then use this comparer funciton as:
var resultSet = list1.Intersect<User>(
list2,
new LambdaComparer<User>((item1, item2) => userNameComparer));
EDIT: Note ragarding particular impelemntaion referenced in this answer
There is could be a problem that by default for hash funciton is hardcoded 0
6 public LambdaComparer(Func<T, T, bool> lambdaComparer) :
7 this(lambdaComparer, o => 0)
8 {
9 }
This can lead to performance issues in some cases so I would recommend to refactor it as:
public LambdaComparer(Func<T, T, bool> lambdaComparer) :
this(lambdaComparer,
EqualityComparer<T>.Default.GetHashCode(o))
{
}
So it wil use built in GetHashCode() implementation
Related
I have a list of strings where I need to sort it by the ending substring. For example, assume we have these strings in the list:
Get_USER_By_ID
Get_Product_By_Name
Get_Product_By_ID
Get_Location_By_Name
...
I Need to sort it so that all the functions matching the same By_ are after each other
I could loop over the list, and create many new lists, and if the string contains a specific string (i.e. By_ID), I add it to its related List.
I want to sort them in the same list (same as if I say sort ascending or descending for instance) rather than creating many new lists (in my case I have to create 9 new lists)
You could create Custom Comparer. IComparer defines how to compare objects of type T. This could be used with List.Sort for customized Sorting of the collection. For example, for the input collection
var strList = new List<string>
{
"Get_USER_By_ID",
"Get_Product_By_Name",
"Get_Product_By_ID",
"Get_Location_By_Name"
};
You could sort by
strList.Sort(new CustomStringComparer());
Or using Linq
var result = strList.OrderBy(x=>x,new CustomStringComparer());
Where CustomStringComparer is defined as
public class CustomStringComparer : IComparer<string>
{
private Regex _regex = new Regex(#"By_(?<Tag>[\S]*)",RegexOptions.Compiled);
public int Compare(string first, string second)
{
var firstSubString = _regex.Match(first).Groups["Tag"].Value;
var secondSubString = _regex.Match(second).Groups["Tag"].Value;
return firstSubString.CompareTo(secondSubString);
}
}
Ouput
Get_USER_By_ID
Get_Product_By_ID
Get_Product_By_Name
Get_Location_By_Name
myList = myList.OrderBy(str => str.Split(new[] {"By_"}, StringSplitOptions.None)
.Last()).ToList();
I have a problem using Fluent Assertions to compare two collections of type List<List<string>>. When using the Should().Equal() method (order is important) I get the following (cryptic ;-) message:
Expected collection to be equal to {{"Telefoonnummer"}, {"E-mailadres"}, {"E-mailadres controle"}}, but {{"Telefoonnummer"}, {"E-mailadres"}, {"E-mailadres controle"}} differs at index 0.
So, the objects appear to be equal. Also when debugging the objects appear to be exactly the same. When comparing two List<string> objects the test passes, no problems, but the same test with List<List<string>> fails. Am I using the wrong assertion method? Or does Fluent Assertions not handle this type of collection correctly?
Instead of Should().Equal() use actualList.ShouldBeEquivalentTo(expectedList. config => config.WithStrictOrder());
ShouldBeEquivalentTo method will check that both lists contains items with same values. In cases when list contains instance of reference types, it will compare the full object graph of those instances. Adding as a second parameter
config => config.WithStrictOrdering() will check that order of items is same as in expected list.
Should().Equal() on other hand will use "standard" equality check, where var listOne = new List<string> { "test" }; and var listTwo = new List<string> { "test" }; will be different instances.
While comparing a string using == checks value equality, List<string> checks the address of the list. This means two lists, containing the same elements are not the same because you are comparing the addresses of the lists instead of the items inside. Lets make an example:
List<string> listA = new List<string> { "item" };
List<string> listB = new List<string> { "item" };
bool equal = listA == listB; //equal will be false
To solve your problem you could combine SelectMany and SequenceEqual to compare the items inside the lists. Here is a small example:
List<List<string>> listToCompare = new List<List<string>>()
{
new List<string> {"first", "second"},
new List<string> {"third"}
};
List<string> expectedList = new List<string> { "first", "second", "third" };
bool sequenceEqual = listToCompare.SelectMany(i => i).SequenceEqual(expectedList); //sequenceEqual will be true
I apologize for posting such a seemingly simple question. I know there are many similar questions already posted (and I have looked at many of these before posting my question), but I struggle to apply the answers to my situation. I am relatively new to C#, and would appreciate your input and help.
How can I compare my 2 Lists with a foreach loop, and create a new List with the records found that does not exist in my comparison?
Below is the code outline I already have, and comments of what needs to happen:
private void updateHolidays()
{
List<Holiday> localHolidays = getLocalHolidays();
List<Holiday> remoteHolidays = getRemoteHolidays();
List<Holiday> holidayDifference = new List<Holiday>();
foreach (Holiday holiday in remoteHolidays)
{
if (true) // holiday does not exist in localHolidays
{
// add holiday to holidayDifference
holidayDifference.Add(holiday);
}
}
createNewHolidays(holidayDifference);
}
Thank you in advance!
The easiest way would be using LinQ. The Except methods returns all items from the source which not exists in the second list.
holidayDifference = remoteHolidays.Except(localHolidays).ToList();
The Except method accepts a optional second parameter to customize the comparison. If you don't pass a IEqualityComparer<T> the standard comparison with the Holiday.Equals method will be used. Alternatively you can override this method instead of passing a comparer.
Like most of LinQ methods, Except returns a IEnumerable<T>, this can be easily converted to a List<T> with the ToList method.
The MSDN documentations are linked inline.
If you still want to implement this yourself, you can use the Contains method of List<T>:
foreach (Holiday holiday in remoteHolidays)
{
if (!localHolidays.Contains(holidy))
{
An alternative to Contains would be LinQs Any which allows you to compare your objects with a function/lamda expression.
Assuming you have a overload of the Equals method and Holiday objects are compareable
List<Holiday> holidayDifference = remoteHolidays.Except(localHolidays).ToList();
You can use Linq's Except extension method:
holidayDifference = remoteHolidays
.Except(localHolidays)
.ToList();
Note that this will also requires Holiday to implement a valid Equals method of IEquatable<Holiday> method override, also GetHashCode must return an identical hash for two Holidays for which Equals returns true.
Also, Except is an extension which returns (in this case) an IEnumerable<Holiday> therefore you will have to use the ToList extension method in order to retrieve a List<Holiday>
Alternatively, you can use the other overload of Except which allows you to provide an IEqualityComparer<Holiday> instead of modifying your original class.
Example with strings:
List<string> holidayDifference = new List<string>();
List<string> remoteHolidays = new List<string> { "1", "2", "3" };
List<string> localHolidays = new List<string> { "1", "3" };
holidayDifference = remoteHolidays
.Except(localHolidays)
.ToList();
holidayDifference.ForEach(Console.WriteLine);
Output:
2
Example With Holiday : IEquatable<Holiday>:
class Holiday : IEquatable<Holiday>
{
public string Name { get; set; }
public bool Equals(Holiday other)
{
return Name == other.Name;
}
// GetHashCode must return true whenever Equals returns true.
public override int GetHashCode()
{
//Get hash code for the Name field if it is not null.
return Name?.GetHashCode() ?? 0;
}
}
public class Program
{
public static void Main()
{
List<Holiday> holidayDifference = new List<Holiday>();
List<Holiday> remoteHolidays = new List<Holiday>
{
new Holiday { Name = "Xmas" },
new Holiday { Name = "Hanukkah" },
new Holiday { Name = "Ramadan" }
};
List<Holiday> localHolidays = new List<Holiday>
{
new Holiday { Name = "Xmas" },
new Holiday { Name = "Ramadan" }
};
holidayDifference = remoteHolidays
.Except(localHolidays)
.ToList();
holidayDifference.ForEach(x => Console.WriteLine(x.Name));
}
}
Output:
Hanukkah
If you insist on foreach loop you can use HashSet<Holiday> to store the Holidays to exclude:
HashSet<Holiday> hs = new HashSet<Holiday>(localHoliday);
foreach (Holiday holiday in remoteHolidays)
if (!hs.Contains(holiday))
holidayDifference.Add(holiday);
private void updateHolidays()
{
List<Holiday> localHolidays = getLocalHolidays();
List<Holiday> remoteHolidays = getRemoteHolidays();
List<Holiday> holidayDifference = new List<Holiday>();
foreach (Holiday holiday in remoteHolidays)``
{
if (localHolidays.Contains(holiday))
{
// add holiday to holidayDifference
holidayDifference.Add(holiday);
}
}
createNewHolidays(holidayDifference);
}
It depends if both the lists contain the same Holiday objects, if they are the same objects that are in both lists then you don't need to make a comparer that implements IEqualityComparer. I will assume they are not the same objects and hence you are doing comparisons by value rather than reference.
The quickest way of doing this is without a foreach loop, you can do this using LINQ as such.
var holidayDifference = remoteHolidays.Except(localHolidays, new HolidaysComparer()).ToList();
If you want to do it using a foreach loop you can do it several ways. You can do it using a HashSet (make sure to initialize the set with a HolidayComparer) by iterating on the values in localHolidays and adding them to the set, then pulling out the ones from remoteHolidays that aren't in that set. However the easy way would be to use the LINQ contains function (The except function above essentially wraps this into a loop).
var holidayDifference = new List<Holiday>();
var comparer = new HolidayComparer();
foreach(Holiday holiday in remoteHolidays)
{
if(!localHolidays.Contains(holiday, comparer))
holidayDifference.Add(holiday);
}
If you are comparing each holiday by reference then you won't need to implement the IEqualityComparer interface.
To learn how to implement this interface look here IEqualityComparer Interface
I have a fairly large collection of foo { int id, int parentid, string name}.
I am looking to collect a list of foo objects where the object has a name of "bar3", and is a child of an object named "bar2", which is a child of an object with an ID of 1.
What sort of collection should I use (I've been playing with lookups and dictionaries with not a whole lot of success) and how should I write this to make an efficient function out of this? There are approximately 30K foo objects and my method is choking to death.
Thanks!
If I really had to stick with this layout for foo, and I really had to make lookups as fast as possible (I don't care about memory size, and will be reusing the same objects repeatedly, so the cost of setting up a set of large structures in memory would be worth it), then I would do:
var byNameAndParentLookup = fooSource.ToLookup(f => Tuple.Create(f.parentid, f.name)); //will reuse this repeatedly
var results = byNameAndParentLookup[Tuple.Create(1, "bar2")].SelectMany(f => byNameAndParentLookup[Tuple.Create(f.id, "bar3")]);
That said, if I was going to store tree data in memory, I'd prefer to create a tree-structure, where each foo had a children collection (perhaps a dictionary keyed on name).
Edit: To explain a bit.
fooSource.ToLookup(f => Tuple.Create(f.parentid, f.name))
Goes through all the items in fooSource (wherever our foo objects are coming from), and for each one creates a tuple of the parentid and the name. This is used as a key for a lookup, so for each parentid-name combination we can retrieve 0 or more foo objects with that combo. (This will use the default string comparison, if you want something else such as case-insensitive, create an IEqualityComparer<Tuple<int, string>> implementation that does the comparison you want and use .ToLookup(f => Tuple.Create(f.parentid, f.name), new MyTupleComparer())).
The second line can be broken down into:
var partWayResults = byNameAndParentLookup[Tuple.Create(1, "bar2")];
var results = partWayResults.SelectMany(f => byNameAndParentLookup[Tuple.Create(f.id, "bar3")]);
The first line simply does a search on our lookup, so it returns an enumeration of those foo objects which have a parentid of 1 and a name of "bar2".
SelectMany takes each item of an enumeration or queryable, and computes an expression that returns an enumeration, which is then flattened into a single enumeration.
In other words, it works a bit like this:
public static SelectMany<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, IEnumerable<TResult>> func)
{
foreach(TSource item in source)
foreach(TResult producedItem in func(item))
yield return producedItem;
}
In our case, the expression passed through takes the id of the element found in the first lookup, and then looks for any elements that have that as their parentid and have the name "bar2".
Hence, for every item with parentid 1 and name bar2, we find every item with that first item's id as its parentid and the name bar3. Which is what was wanted.
Check this out: QuickGraph
I've never actually used it but it seems well documented.
Alternatively you can try the C5 Generic Collection Library
I got this from this tread
I can suggest you to group all items by parentId first then apply conditions on it. First you will need to find group with bar1 element, than you should select all its childs and try to find element with name bar 2...
I can suggest such solution, it not the best but it work (thirdLevelElements will contain needed elements). I've used foreachs to make it clear, this logic could be written in linq statements but for me it will be complicated to understand.
var items = new[]
{
new Foo{id=1,parentid = 0, name="bar1"},
new Foo{id=2,parentid = 1, name="bar2"},
new Foo{id=3,parentid = 2, name="bar3"},
new Foo{id=4,parentid = 0, name="bar12"},
new Foo{id=5,parentid = 1, name="bar13"},
new Foo{id=6,parentid = 2, name="bar14"},
new Foo{id=7,parentid = 2, name="bar3"}
};
var groups = items.GroupBy(item => item.parentid).ToList();
var firstLevelElements = items.Where(item => item.name == "bar1");
List<Foo> secondLevelElements = new List<Foo>();
foreach (var firstLevelElement in firstLevelElements)
{
secondLevelElements.AddRange(groups[firstLevelElement.id]
.Where(item => item.name == "bar2"));
}
List<Foo> thirdLevelElements = new List<Foo>();
foreach (var secondLevelElement in secondLevelElements)
{
thirdLevelElements.AddRange(groups[secondLevelElement.id]
.Where(item => item.name == "bar3"));
}
How can I sort a list of list?
persons.OrderBy(p => p.rate).ToList();
The list of list (persons) is declared like this:
public class Persons : List<Person> { }
When I'm trying to run the first statement I get an error:
Cannot convert from 'System.Collections.Generic.List' to
'Persons'
Is there a way to do this using LINQ?
Just because it inherits from a list doesn't mean you can use it like one.
Remember for everything else to see it as a list use interfaces (IList<T>). Then methods depending on IEnumerable, IList, ICollection, etc. can see that it's something it can deal with.
Otherwise, whose to say your Add() (As defined by IList) method isn't named AddPerson in your class?
You can achive it with that statement:
var persons = new Persons ();
persons.AddRange(persons.OrderBy(p => p.rate));
If you want to order all persons in all lists and huddle up them into one list:
var persons = new System.Collections.Generic.List<Persons>();
var trio = new Persons() { new Person(7), new Person(3), new Person(8) };
var pair = new Persons() { new Person(1), new Person(2) };
persons.Add(trio);
persons.Add(pair);
var ordered = persons.SelectMany(p => p).OrderBy(p => p.rate).ToList();
http://msdn.microsoft.com/en-us/library/system.linq.enumerable.selectmany.aspx
To achieve a SortBy behavior, you have to follow these three easy steps:
Store the old items (a. by storing the reference to the old list | b. by copying all entries of the old list into a new one)
Create an empty instance of your container class (a. by creating a new object of the needed type | b. by clearing the old list)
Fill your empty list with the entries while ordering them as you desire.
This little extension method should do the Trick:
public static void SortBy<TList, TItem, TOrder>(this TList source,
Func<TItem, TOrder> sortFunc)
where TList : List<TItem>
{
var l = source.ToList();
source.Clear();
source.AddRange(l.OrderBy(sortFunc));
}