Intersect between 2 collections in C# - c#

I have:
List<INFRAESTRUCTURA> l1 = listQ1.ToList();
List<INFRAESTRUCTURA> l2 = listQ2.ToList();
And I need to intersect it comparing ids. Something like that:
l1.Intersect(l2, l1[].id_infraestructura == l2[].id_infraestructura)
But I don't know which method I must use and it sintax.
I found this:
var ids = list1.Select(a => a.id).Intersect(list2.Select(b => b.id));
But this return a list of ids and i need a list of elements contained in both lists.
Thank you!

I would use Enumerable.Join:
var intersecting = from i1 in l1
join i2 in l2
on i1.id_infraestructura equals i2.id_infraestructura
select i1;
List<INFRAESTRUCTURA> result = intersecting.ToList();
If you would override Equals + GetHashCode in INFRAESTRUCTURA or provide a custom IEqualityComparer<INFRAESTRUCTURA> you could use Enumerable.Intersect directly:
List<INFRAESTRUCTURA> result = l1.Intersect(l2).ToList();
Here's a possible implementation:
public class InfrastructureComparer : IEqualityComparer<INFRAESTRUCTURA>
{
public bool Equals(INFRAESTRUCTURA x, INFRAESTRUCTURA y)
{
if (x == null && y == null) return true;
if (x == null || y == null) return false;
return x.id_infraestructura == y.id_infraestructura;
}
public int GetHashCode(INFRAESTRUCTURA obj)
{
if (obj == null) return 0;
return obj.id_infraestructura;
}
}
you can use the overloads which take an IEqualityComparer<T> like here:
List<INFRAESTRUCTURA> result = l1.Intersect(l2, new InfrastructureComparer()).ToList();
If you want both objects in the result you could use an anonymous type:
var intersecting = from i1 in l1
join i2 in l2
on i1.id_infraestructura equals i2.id_infraestructura
select new { i1, i2 };

The other answers are correct, but you can use Intersect with your custom comparer. You can create custom comparer by implementing IEqualityComparer<> interface. And for implementing this interface we must implmenet two methods, Equals and GetHashCode.
public class InfraestructuraComparer: IEqualityComparer<INFRAESTRUCTURA>
{
/// <summary>
/// Whether the two INFRAESTRUCTURA are equal.
/// </summary>
public bool Equals(INFRAESTRUCTURA firstObj, INFRAESTRUCTURA secondObj)
{
if (firstObj == null && secondObj == null)
return true;
if (firstObj == null || secondObj == null)
return false;
// Your equality logic goes to here
return firstObj.ID == secondObj.ID;
}
/// <summary>
/// Return the hash code for this instance.
/// </summary>
public int GetHashCode(INFRAESTRUCTURA obj)
{
// Don't compute hash code on null object.
if (obj == null) return 0;
unchecked
{
var hash = 17;
hash = hash * 23 + obj.Id.GetHashCode();
return hash;
}
}
}
And then:
var result = list1.Intersect(list2, new InfraestructuraComparer());
You can also use this comparer in Except method, for finding the difference of two sequences.
var result = list1.Except(list2, new InfraestructuraComparer());
Additionally:
From the first point of view you may misunderstood GetHashCode(). You can read about this method in many question of StackOverflow. You can read the answer to this question.

You can use Linq Join
l1.Join(l2, l => l.id_infraestructura, r => r.id_infraestructura, (l,r) => l.id_infraestructura);

Related

Get new and existing items between lists

First we pull in new alerts and deserialize them. Now I only care about 2 properties that need to be compared: CommandID and AlertID, all others can be ignored so I create a new object which I assumed would have been easier to compare the results. All other properties become null.
List<AlertModel> alerts = JsonConvert.DeserializeObject<List<AlertModel>>(json)
.Select(x => new AlertModel() { CommandID = x.CommandID, AlertID = x.AlertID }).ToList();
Now I want to find new alerts that don't already exist
List<AlertModel> newAlerts = alerts.Except(currentAlerts).ToList();
Next we pull what alerts already exist.
List<AlertModel> existingAlerts = currentAlerts.Intersect(alerts).ToList();
Now we store new and existing alerts.
currentAlerts.Clear();
currentAlerts.AddRange(newAlerts);
currentAlerts.AddRange(existingAlerts);
1st run alerts contains 1 item newAlerts contains 1 item and existingAlerts contains 0 as they should.
2nd run through isn't what I was expecting.
alerts contains 1 as it should.
newAlerts contains 1 and this should be 0. currentAlerts contains the exact same CommandID and AlertID as in alerts
existingAlerts contains 0 and this should be 1 since the same CommandID and AlertID exists in currentAlerts and alerts.
Not sure what i'm missing here and maybe there is a better way to do this.
Replace this code:
List<AlertModel> newAlerts = alerts.Except(currentAlerts).ToList();
Whit this:
List<AlertModel> newAlerts = alerts.Where(x => !currentAlerts.Any(y => y.CommandID == x.CommandID && y.AlertID == x.AlertID)).ToList();
The issue is that your alerts list contains new elements (new AlertModel() { CommandID = x.CommandID, AlertID = x.AlertID }). This is a reference problem.
Animal a = new Animal { Color = "Red" };
Animal b = new Animal { Color = "Red" };
a == b; // This returns false
Alternatively you can override Equals method in you class. To do this in your class:
public class AlertModel {
// Some things
public override bool Equals(object model) {
return model != null && CommandID == model.CommandId && AlertID == model.AlertID;
}
}
Override Equals and GetHashCode in your AlertModel class. Return a constant value in GetHashCode() (e.g. -1) if you want to force to call your Equals method.
public override bool Equals(object obj)
{
var that = obj as AlertModel;
return that != null && that.AlertId == this.AlertId && that.CommandId == this.CommandId;
}
public override int GetHashCode()
{
int hash = 13;
return (this.AlertId.GetHashCode() * this.CommandID.GetHashCode()) ^ hash;
}
var uniqueAlerts = alerts.Where(a=> !currentAlerts.Any(c=> c.CommandID == a.CommandID && c.AlertID== a.AlertID));

Remove duplicate rows from two dimensional list

I have a two-dimensional list of strings (List<List<string>>).
Is there an easy way to remove the duplicate rows? That is the List<string> that are equal.
Build a custom IEqualityComparer based on SequenceEqual :
class ListComparer : IEqualityComparer<List<string>>
{
public bool Equals(List<string> x, List<string> y)
{
if (x == y)
return true ;
if (x == null || y == null)
return false ;
// Order if you need
return x.SequenceEqual(y) ;
}
public int GetHashCode(List<string> obj)
{
if (obj == null)
return 0;
unchecked
{
return obj.Select(e => e.GetHashCode()).Aggregate(17, (a, b) => 23 * a + b);
}
}
}
Apply Distinct() with the comparer :
List<List<string>> original = ...
var sortedListOfList = original.Distinct(new ListComparer()).ToList() ;
You did not specify if the lists should be compared with or without ordering.
Without ordering it should be:
List<List<string>> source = *yourLists*;
var sortedList = source.Distinct();

List<string>.Contains using trim

It would be nice if this worked, but alas it doesn't.
List<string> items = new List<string>();
items.Add("a ");
bool useTrim = true;
if (items.Contains("a", useTrim)) {
Console.WriteLine("I'm happy");
}
I ended up implementing it as an extension method below. But I was wondering if anyone else had any elegant ideas other than creating a comparer class or looping through.
/// <summary>
/// Determines whether an element in the List of strings
/// matches the item. .Trim() is applied to each element
/// for the comparison
/// </summary>
/// <param name="value">a list of strings</param>
/// <param name="item">the string to search for in the list</param>
/// <returns>true if item is found in the list</returns>
public static bool ContainsTrimmed(this List<string> value, string item) {
bool ret = false;
if ((value.FindIndex(s => s.Trim() == item)) >= 0) {
ret = true;
}
return ret;
}
Well you'll either need to loop through it each time, or create another list of just the trimmed values, and use that for searching. (Heck, you could create a HashSet<string> if you only need to know whether or not a trimmed value is present.)
However, if you want to stick to just a single list, then rather than using FindIndex I'd use Any from LINQ:
if (items.Any(x => x.Trim() == item))
Note that even if you do want to keep your ContainsTrimmed method, you can simplify it to just:
return value.FindIndex(s => s.Trim() == item) >= 0;
I would suggest creating a custom IEqualityComparer to supply to the overloaded function Contains.
This is exactly the reason why this overload exists.
class TrimmedEqualityComparer : IEqualityComparer<string>
{
public bool Equals(string x, string y)
{
if (x == null && y != null || x != null && y == null)
return false;
if (x == null && y == null)
return true;
return x.Trim() == y.Trim();
}
public int GetHashCode(string obj)
{
return obj != null ? obj.GetHashCode() : 0;
}
}
You call it like this.
var strs = new string[] {"a ", "b ", "c"};
if (strs.Contains("b", new TrimmedEqualityComparer()))
Console.WriteLine("I'm happy");

How to filter results based on selected results in a LINQ query?

I have a list of Foos that I would like to filter according to foo.HasBar.
Foo also have a property Baz.
When a Foo is selected, all Foos with the same Baz object should be filtered.
Is it possible to achieve this using a LINQ query or should I use a foreach instead?
Edit: Here's a sample dataset:
Foo.HasBar = true; Foo.Baz = 1;
Foo.HasBar = true; Foo.Baz = 1;
Foo.HasBar = false; Foo.Baz = 1;
Foo.HasBar = true; Foo.Baz = 2;
Foo.HasBar = false; Foo.Baz = 2;
What I'm trying to achieve is that no other iteration on another Foo.HasBar = true; Foo.Baz = 1; will be performed or no another iteration on Foo.HasBar = false; Foo.Baz = 2; will be performed if Foo.HasBar = true; Foo.Baz = 2; was already selected.
Here's how I would have done it with a foreach loop:
var selectedFoos = new List<Foo>();
foreach(var foo in foos)
{
if (selectedFoos.Exists(f => f.Baz == foo.Baz))
continue;
if (foo.HasBar)
selectedFoos.Add(foo);
}
Use IEnumerable<Foo>.Distinct, and implement your equals operator in a performant manner, where the Baz property is checked, and the HasBar property is ignored if Baz isn't equal. You can do this with &&, because if the left-hand expression is false, the right-hand expression isn't evaluated.
Then, filter based on HasBar with IEnumerable<Foo>.Where.
If you do not want to clutter your Foo object with an Equals operator, or you need different Equals implementations for different cases, then implement a separate IEqualityComparer<Foo>.
This also has the advantage that you can avoid checking the HasBar property completely while getting distinct values. If you skipped the check in the class itself, it might cause subtle bugs because people might expect them to be equal. But with a well named custom comparer, it is unlikely people will assume that it will ensure absolute equality.
Here is some example code:
IEnumerable<Foo> selectedFoos =
sampleDataSet
.Distinct(new PerformantFooComparer())
.Where(f => f.HasBar);
// ...
private class PerformantFooComparer : IEqualityComparer<Foo>
{
public bool Equals(Foo x, Foo y)
{
bool isXNull = x == null;
bool isYNull = y == null;
return isXNull == isYNull
&& isXNull
|| (
x.Baz == y.Baz
// && x.HasBar == y.HasBar
// HasBar commented out to avoid performance overhead.
// It is handled by a Where(foo => foo.HasBar) filter
);
}
public int GetHashCode(Foo obj)
{
if (obj == null)
return 0;
// See: http://stackoverflow.com/questions/263400/what-is-the-best-algorithm-for-an-overridden-system-object-gethashcode
int hash = 17;
hash = hash * 23 + obj.Baz.GetHashCode();
// HasBar intentionally not hashed
return hash;
}
}
var results = from f in Foos where (foo.HasBar) && (foo.Baz equals SelectedBaz) select f;
var q = from f in foos
group f by f.Baz into g
let tempFoo = g.FirstOrDefault(foo => foo.HasBar)
where tempFoo != null
select tempFoo;
The best I could come up with (for now).
The use of let should avoid multiple calls to FirstOrDefault so your performance intensive HasBar won't be called more times than needed assuming that the implementation of FirstOrDefault would not iterate after it finds a result. If let was not used FirstOrDefault should be used in the where clause.
you can simply go with
var filtered = from f in foos
where foo.HasBar == true
select f;
From your code, you just need to:
foos.Where(f => !foos.Where(f2 => f2.HasBar).Any(s => s.Baz == f.Baz));

Select unique items with LINQ

When I use the following code I get the same items multiple times.
XElement neededFiles = new XElement("needed",
from o in _9nFiles.Elements()
join t in addedToSitePull.Elements()
on o.Value equals
t.Value
where o.Value == t.Value
select new XElement("pic", o.Value));
I'd like to get only unique items. I saw a Stack Overflow post, How can I do SELECT UNIQUE with LINQ?, that used it, and I tried to implement it, but the change had no affect.
The code:
XElement neededFiles = new XElement("needed",
(from o in _9nFiles.Elements()
join t in addedToSitePull.Elements()
on o.Value equals
t.Value
where o.Value == t.Value
select new XElement("pic", o.Value)).Distinct() );
I imagine the reason this doesn't work is because XElement.Equals uses a simple reference equality check rather than comparing the Value properties of the two items. If you want to compare the values, you could change it to:
_9nfiles.Elements()
.Join(addedToSitePull, o => o.Value, t => t.Value, (o, t) => o.Value)
.Distinct()
.Select(val => new XElement("pic", val));
You could also create your own IEqualityComparer<T> for comparing two XElements by their values. Note this assumes all values are non-null:
public class XElementValueEqualityComparer : IEqualityComparer<XElement>
{
public bool Equals(XElement x, XElement y)
{
return x.Value.Equals(y.Value);
}
public int GetHashCode(XElement x)
{
return x.Value.GetHashCode();
}
}
Then you could replace the existing call to Distinct with Distinct(new XElementValueEqualityComparer()).
Distinct doesn't work because XElements are compared by reference, not by value.
The solution is to use another overload of Distinct - Distinct(IEqualityComparer);
You need to implement IEqualityComparer for example as follows:
class XElementEqualityComparer : IEqualityComparer<XElement>
{
#region IEqualityComparer<XElement> Members
public bool Equals(XElement x, XElement y)
{
if (x == null ^ y == null)
return false;
if (x == null && y == null)
return true;
return x.Value == y.Value;
}
public int GetHashCode(XElement obj)
{
if (obj == null)
return 0;
return obj.Value.GetHashCode();
}
#endregion
}
It's not a good solution - but really easy.
foreach (XElement pic in neededFiles.Elements())
{
unSyncedPictures.Add(pic.Value);
}
List<string> temp = new List<string>();
temp.AddRange(unSyncedPictures.Distinct());

Categories