I want to find similar objects in a collection depending on a Method I implement
for example this sample class:
class myObj
{
public int Data1 { get; set; }
public int Data2 { get; set; }
public int Data3 { get; set; }
}
then implement Similar method in the class:
public bool Similar(myObj obj)
{
if (obj.Data1 == this.Data1 && obj.Data2 == this.Data2)
return true;
return false;
}
now I have this collection:
List<myObj> items = new List<myObj>();
// none similar
items.Add(new myObj() { Data1 = 1, Data2 = 2, Data3 = 4 });
items.Add(new myObj() { Data1 = 2, Data2 = 3, Data3 = 18 });
items.Add(new myObj() { Data1 = 3, Data2 = 4, Data3 = 75 });
items.Add(new myObj() { Data1 = 4, Data2 = 2, Data3 = 3 });
//similar
items.Add(new myObj() { Data1 = 5, Data2 = 26, Data3 = 97 });
items.Add(new myObj() { Data1 = 5, Data2 = 26, Data3 = 37 });
items.Add(new myObj() { Data1 = 10, Data2 = 45, Data3 = 47 });
items.Add(new myObj() { Data1 = 10, Data2 = 45, Data3 = 19 });
to get the similar objects I did this:
private static List<myObj> GetSimilars(List<myObj> items)
{
List<myObj> similars = new List<myObj>();
while (items.Count > 0)
{
var q = (from c in items
where c.Similar(items[0])
select c).ToList();
if (q.Count > 1)
{
similars.AddRange(q);
foreach (var obj in q)
items.Remove(obj);
}
else
items.Remove(items[0]);
}
return similars;
}
is there a better way to do that?
How about making this class, which is reusable.
public class MyObjSimilarity : EqualityComparer<myObj>
{
public override bool Equals(myObj a, myObj b)
{
if (obj.Data1 == this.Data1 && obj.Data2 == this.Data2)
{
return true;
}
return false;
}
public override int GetHashCode(myObj o)
{
int hash = 17;
hash = hash * 23 + o.Data1.GetHashCode();
hash = hash * 23 + o.Data2.GetHashCode();
return hash;
}
}
You could use like this,
var similarity = new MyObjSimilarity();
items.Where(o => similarity.Equals(o, w));
or pass into the constructor of a dictionary,
var similarity = new MyObjSimilarity();
var lookup = new Dictionary<myObj, string>(similarity);
or in a GroupBy
var similarity = new MyObjSimilarity();
items.GroupBy(o => o, o => o, similarity);
or like the other answer
var similarity = new MyObjSimilarity();
items.GroupBy(
o => o,
o => new { Instance = o, Count = Count(o) },
similarity);
or in other framework friendly places.
try this:
private static List<myObj> GetSimilars(List<myObj> items)
{
return items.SelectMany(x => items.Where(z => x != z && x.Similar(z))).ToList();
}
or if you prefer this:
private static List<myObj> GetSimilars(List<myObj> items)
{
var result = from x in items
from y in items
where x != y && x.Similar(y)
select x;
return result.ToList();
}
You could do that all with Linq's GroupBy
and SelectMany
:
var similarGroups = from i in items
group i by new { i.Data1, i.Data2 } into D1D2Group
where D1D2Group.Count() > 1
select D1D2Group;
foreach (var grp in similarGroups)
Console.WriteLine("DataGroup:{0}/{1} Count:{2}"
, grp.Key.Data1
, grp.Key.Data2
, grp.Count());
If you want to flatten the groups to a List<myObj>
like your GetSimilars
:
List <myObj> similars = similarGroups.SelectMany(g => g).ToList();
return
items.Where(w => items.Count(c => c.Similar(w)) > 1)
/* add .Distinct() optional*/
.ToList();
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With