I need a generic class to compare two list instances. I have the code below, but it is quite messy and inefficient. How can I make it better and definitely faster?
CallByName is an extension that uses reflection to get the property.
public class ListObjectComparator
{
#region PROPRIETES
public List<object> NewList {get; private set;}
public List<object> RemovedList { get; private set; }
public List<object> CommunList { get; private set; }
#endregion
#region METHODES PUBLICS
public bool Run<T>(string iCommunTextPropertyName, List<T> iOriginalList, List<T> iNewList)
{
return Run(iCommunTextPropertyName, iOriginalList, iCommunTextPropertyName, iNewList);
}
public bool Run<T>(string iOriginalPropertyName, List<T> iOriginalList,string iNewPropertyName, List<T> iNewList)
{
if (iOriginalPropertyName.IsNotNull() &&
iNewPropertyName.IsNotNull() &&
iOriginalList != null &&
iNewList != null)
{
NewList = new List<object>();
RemovedList = new List<object>();
CommunList = new List<object>();
foreach (object originalItem in iOriginalList)
{
object research = iNewList.Where(a => a.CallByName(iNewPropertyName).ToString() == originalItem.CallByName(iOriginalPropertyName).ToString()).FirstOrDefault();
if (research == null)
{
RemovedList.Add(originalItem);
}
else
{
CommunList.Add(research);
}
}
foreach (object newItem in iNewList)
{
object research = iOriginalList.Where(a => a.CallByName(iOriginalPropertyName).ToString() == newItem.CallByName(iNewPropertyName).ToString()).FirstOrDefault();
if (research == null) { NewList.Add(newItem); };
}
return true;
}
return false;
}
}
Answer:
public class ListComparator<TOriginal,TNew>
{
public ListComparator(IEnumerable<TOriginal> iOriginalList, Func<TOriginal, IComparable> iOriginalProperty, IEnumerable<TNew> iNewList, Func<TNew, IComparable> iNewProperty)
{
if (iOriginalProperty == null ||
iNewProperty == null) { throw new ArgumentNullException(); };
if (iOriginalList.IsNullOrEmpty() )
{
NewList = (iNewList != null) ? iNewList.ToList() : null;
return;
}
if (iNewList.IsNullOrEmpty())
{
RemovedList = (iOriginalList != null) ? iOriginalList.ToList() : null;
return;
}
NewList = (from tnew in iNewList.ToList()
join toriginal in iOriginalList.ToList()
on iNewProperty(tnew)
equals iOriginalProperty(toriginal) into gj
from item in gj.DefaultIfEmpty()
where item == null
select tnew).ToList();
CommunList = (from tnew in iNewList.ToList()
join toriginal in iOriginalList.ToList()
on iNewProperty(tnew)
equals iOriginalProperty(toriginal) into gj
from item in gj.DefaultIfEmpty()
where item != null
select tnew).ToList();
CommunPairList = (from tnew in iNewList.ToList()
join toriginal in iOriginalList.ToList()
on iNewProperty(tnew)
equals iOriginalProperty(toriginal) into gj
from item in gj.DefaultIfEmpty()
where item != null
select new KeyValuePair<TOriginal, TNew>(item, tnew)).ToList();
RemovedList = (from toriginal in iOriginalList.ToList()
join tnew in iNewList.ToList()
on iOriginalProperty(toriginal)
equals iNewProperty(tnew) into gj
from item in gj.DefaultIfEmpty()
where item == null
select toriginal).ToList();
return;
}
#region PROPRIETES
public List<TNew> NewList { get; private set; }
public List<TOriginal> RemovedList { get; private set; }
public List<TNew> CommunList { get; private set; }
/// <summary>
/// Obtient la liste de pair avec l'original en key et le nouveau en value
/// </summary>
public List<KeyValuePair<TOriginal,TNew>> CommunPairList { get; private set; }
#endregion
}
use:
List<Tuple<string, string>> list1 = new List<Tuple<string, string>>();
List<Tuple<string, string>> list2 = new List<Tuple<string, string>>();
list1.Add(new Tuple<string, string>("AA", "zefzef"));
list1.Add(new Tuple<string, string>("A1", "iulyu"));
list2.Add(new Tuple<string, string>("Abb", "szefez"));
list2.Add(new Tuple<string, string>("A1", "zevzez"));
ListComparator<Tuple<string, string>, Tuple<string, string>> comp = new ListComparator<Tuple<string, string>, Tuple<string, string>>(list1, x => x.Item1, list2, a => a.Item1);
OUTPUT :
1 commun, 1 removed, 1 new
thanks
해결책
To get the list of items that the two lists share in common, look at Enumerable.Intersect. That is:
var same = OriginalList.Intersect(NewList, comparer);
If you want to know which items in NewList aren't in OriginalList (i.e. the list of added items), you'd use Enumerable.Except:
var added = NewList.Except(OriginalList, comparer);
If you want to know which items were deleted, you use Except again, but switch the order:
var deleted = OriginalList.Except(NewList, comparer);
If you're comparing different properties, then probably your best bet is to create intermediate lists that contain the properties and references to the objects. For example: (There might be typos in the code, but this shows the general idea.)
class TempObj: IEquatable<TempObj>
{
public string Key { get; set; }
public object Item { get; set; }
public bool Equals(TempObj other)
{
return Key.Equals(other.Key);
}
public override int GetHashCode()
{
return Key.GetHashCode();
}
}
var listOrig = OriginalList.Select(x => new TempObj(
x..CallByName(iOriginalPropertyName).ToString());
var listNew = NewList.Select(x => new TempObj(
x.CallByName(iNewPropertyName).ToString());
Now you can do Intersect or Except on listNew and listOrig, which will compare the property names. You can then pull the Item values from the resulting list.
다른 팁
Your class actually isn't generic here (although it should be), only your methods are.
Try this:
public class ListObjectComparator<T>
{
#region Public Methods
public static IEnumerable<T> Run(IEnumerable<T> originalItems, IEnumerable<T> newItems, Func<T,T,bool> comparer)
{
foreach(var originalItem in originalItems)
{
bool found = false;
foreach (var newItem in newItems)
{
if (comparer(originalItem,newItem))
{
found = true;
itemToRemove = newItem;
break;
}
}
if (found)
{
newItems.Remove(itemToRemove);
yield return originalItem;
}
}
}
#endregion
}
Run compares originalItems with newItems and returns a list of items that are the same using the ComparePredicate you supply.