Best way to find values not in two lists c# - c#

I have two lists which I need to compare (carOptions and custOptions).
Both of these lists are in my Customer class like below:
public class CustomerDTO
{
public int CustomerId { get; set; }
//other props removed for brevity
public List<OptionDTO> SelectedCarOptions { get; set; }
public List<OptionDTO> SelectedCustomerOptions { get; set; }
}
var existingData = _myRepository.GetDataByCustomer(customerId, year);
var existingCarOptions = existingData.Select(f => f.SelectedCarOptions);
var existingCustomerOptions = existingData.Select(f => f.SelectedCustomerOptions);
existingData is an IEnumerable of CustomerDTO and then existingCarOptions and existingCustomerOptions is an IEnumerable<List<OptionDTO>>
In the method, I have a list of IEnumerable<OptionDTO> options that gets passed in. I then break this down into car or customer based on the Enum as below:
var newCarOptions = options.Where(o => o.OptionTypeID == OptionType.CarOptions);
var newCustomerOptions = options.Where(o => o.OptionTypeID == OptionType.CustomerOptions).ToList();
What I need to do is find which options are in one collection but no in the other.
I tried as below but getting an Error on the Except (I maybe need to create my own static method in that class) but I am not sure this is the best approach really?
if (existingCarOptions.Count() != newCarOptions.Count())
{
//var test = newCarOptions.Except(existingCarOptions);
}
if (existingCustomerOptions.Count() != newCustomerOptions.Count())
{
//var test2 = newCustomerOptions.Except(existingCustomerOptions);
}
Is it also quite a bit of code in the method - I could split it out into sperate methods if required but perhaps there is a simpler way I could achieve this?

I'm assuming OptionDTO has a property called Id, which uniquely identifies an option (you have to change the code accordingly if this is not the case), you may use HashSets to quickly find unmatched OptionsDTOs, while keeping the overall time cost O(n) (where n is the max number of combined options).
Create the existing options sets:
var existingCarOptions = existingData.SelectMany(d => d.SelectedCarOptions).Select(o => o.Id);
var existingCustomerOptions = existingData.SelectMany(d => d.SelectedCustomerOptions).Select(o => o.Id);
var existingCarOptionsIds = new HashSet<int>(existingCarOptions);
var existingCustomerOptionsIds = new HashSet<int>(existingCustomerOptions );
Then you extract options missing in existing sets with:
var unmatchedCarOptions = newCarOptions.Where(o => !existingCarOptionsIds.Contains(o.Id));
var unmatchedCustomerOptions = newCustomerOptions.Where(o => !existingCustomerOptionsIds.Contains(o.Id));

If you want to compare two classes you can use IEqualityComparer
public class OptionComparer : IEqualityComparer<OptionDTO>
{
public bool Equals(OptionDTO x, OptionDTO y)
{
if (object.ReferenceEquals(x, y))
{
return true;
}
if (object.ReferenceEquals(x, null) ||
object.ReferenceEquals(y, null))
{
return false;
}
return x.OptionTypeID == y.OptionTypeID ;
}
public int GetHashCode(OptionDTO obj)
{
if (obj == null)
{
return 0;
}
return obj.OptionTypeID.GetHashCode();
}
With using this you can ıdentify that What is the concept of equality for these classes.
Now we can find different values..
public List<OptionDTO>CalculateDiffBetweenLists(List<OptionDTO> left, List<OptionDTO> right){
List<OptionDTO> optionDiff;
optionDiff = left.Except(right, new OptionComparer ()).ToList();
return optionDiff ;
}

Related

Recursion is causing double results

I have 2 methods.
Method 1 calls a google drive service which returns an array of an object of files.
Method 2 calls the first method to get the returned list, which calls the google drive service which activates the method you see below for a "second" time.
I may be wrong but I think the recursion is causing my final list results to be doubled. Or it may be because I have declared files outside of this method. Once the line files.AddRange(....) is called by the second method it technically already contains the files from the first method. I am not quite sure how to solve my issue.
readonly Stack myStack = new Stack();
readonly HashSet<BFile> files = new Hashset<BFile>();
readonly HashSet<BFile> pushedList = new HashSet<BFile>();
public async Task<(BFile[]? files, string? error)> GetFiles(string parentId, bool includePermissions)
{
var service = service..
if (service != null)
{
var listRequest = service.Files.List();
do
{
var response = await listRequest.ExecuteAsync();
var folders = response.Files.Where(f => f.MimeType == "application/vnd.google-apps.folder");
var allOtherFiles = response.Files.Where(f => f.MimeType != "application/vnd.google-apps.folder");
files.AddRange(folders.Where(f => f.Name != "$ExclaimerSignatures").Select(f => mapFile(f)));
files.AddRange(allOtherFiles.Select(f => mapFile(f)));
var missingFiles = files.Where(f => !pushedList.Contains(f)).ToList();
missingFiles.ForEach(myStack.Push);
pushedList.UnionWith(missingFiles);
while (myStack.Count != 0)
{
var temp = (BFile)myStack.Peek();
myStack.Pop();
await GetFiles(temp.Id, true);
}
listRequest.PageToken = response.NextPageToken;
} while (listRequest.PageToken != null);
return (files.ToArray(), null);
}
else
return (null, "Something went wrong");
}
edit: to answer a question from below the only reason why I have stack with recursion tree walking is I use the stack to keep track of stuff that has been visited. I am not sure if that is bad or not, it was just simply what I came up with upon initially writing this code
I think the issue is that HashSet is not determinating BFiles like unique, and it is a reason why HashSet<BFile> files have duplicated values.
To fix it, you can override Equals and GetHashCode in your class BFile. If BFile is not your class, check if maybe it has already it implemented. If not, you can do it with the IEqualityComparer interface. Here is a simple example:
var files = new HashSet<BFile>(new Comparer());
var items = Enumerable.Empty<BFile>(); //Can be any collection
files.UnionWith(items);
class Comparer : EqualityComparer<BFile>
{
public override bool Equals(BFile? x, BFile? y) => x.Id == y.Id;
public override int GetHashCode(BFile obj) => obj.Id.GetHashCode();
}
class BFile
{
public string Id { get; set; }
public string Name { get; set; }
public byte[] Content { get; set; }
}
I hope it will help you.

Using same method on different classes

I'm created a method that separate the data for an SQLite database into 3 categories:
Modified (variables in list_1 that are not equals to the list_2 ones)
Created (variables in list_1 that are not found in list_2)
Deleted (list_2 variables that are not existing anymore in list_1)
sidenote: list_2 is a backup of list_1 before any modification
The problem with this code is that I can use it only on one class. If I want a second class, then I have to write down the same code again with minor changes. I have now 3 classes, but in the future, I probably want more. It'll be pretty time consuming if I try to write down over and over with every single class, so I posted this question for any suggestion. Also because I didn't find any articles where it uses lambda expressions.
public class Stats
{
public int id { get; set; }
public string name { get; set; }
}
public class FactStats : Stats
{
public string tag { get; set; }
public float balance { get; set; }
public FactStats ShallowCopy()
{
return (FactStats)this.MemberwiseClone();
}
}
List<FactStats> Factions = new List<FactStats>();
List<FactStats> SavedFactions = new List<FactStats>();
void SavetoDatabase()
{
//1. Separate Data
List<FactStats> F_JoinedList = new List<FactStats>();
List<int> F_Modify = new List<int>();
List<int> F_Create = new List<int>();
List<int> F_Delete = new List<int>();
//Modified Objects
F_JoinedList = Factions.Where(n => SavedFactions.Any(o => o.id == n.id)).ToList();
foreach (FactStats f in F_JoinedList)
{
FactStats fs = SavedFactions.Single(x => x.id == f.id);
if (!f.CompareEquals(fs))
F_Modify.Add(f.id);
}
//Created Objects
foreach (FactStats f in Factions)
{
bool vane = Convert.ToBoolean(SavedFactions.Where(f2 => f2.id == f.id).Count());
if (!vane)
F_Create.Add(f.id);
}
//Deleted Objects
foreach (FactStats f in SavedFactions)
{
bool vane = Convert.ToBoolean(Factions.Where(f2 => f2.id == f.id).Count());
if (!vane)
F_Delete.Add(f.id);
}
...
}
I've tried to do it with reflection, not much success. Probably because of my lack of experience.
CompareEquals extensive method (at the Modified Objects) is a third party code that compare two objects of the same class using reflection.
The best way to use one method on different classes is to use Generic method. Since classes are different they should conform to the common interface, for example IUniqueIdentifiable should have "id" property.
You need to create method:
void Save<T>(List<T> saved, List<T> modified) where T: IUniqueIdentifiable
{
List<T> F_JoinedList = new List<T>();
List<int> F_Modify = new List<int>();
List<int> F_Create = new List<int>();
List<int> F_Delete = new List<int>();
//Modified Objects
F_JoinedList = modified.Where(n => saved.Any(o => o.id == n.id)).ToList();
foreach (T f in F_JoinedList)
{
T fs = saved.Single(x => x.id == f.id);
if (!f.CompareEquals(fs))
F_Modify.Add(f.id);
}
//Created Objects
foreach (T f in modified)
{
bool vane = Convert.ToBoolean(saved.Where(f2 => f2.id == f.id).Count());
if (!vane)
F_Create.Add(f.id);
}
//Deleted Objects
foreach (T f in saved)
{
bool vane = Convert.ToBoolean(modified.Where(f2 => f2.id == f.id).Count());
if (!vane)
F_Delete.Add(f.id);
}
...
}
public interface IUniqueIdentifiable
{
id {get;}
}
There are tons of articles how to create Generic method, you can find one sample below
http://www.informit.com/articles/article.aspx?p=605369&seqNum=4

LINQ - Merge two queries and exclude items from the first query

I'm pretty sure this falls under a "UNION" scenario but I'm really just looking for the best approach to solve my problem (even if it's not a UNION).
I have a query that looks like this:
var query = context.AffiliateConfigurations.Where(x => x.AffiliateId == affiliateId).Select(config => new ViewModels.ConfigurationItem
{
ConfigurationId = config.AffiliateConfigurationId,
ConfigKey = config.ConfigKey,
ConfigValue = config.ConfigValue,
UpdatedDate = config.UpdatedDate,
ConfigurationType = ViewModels.ConfigurationType.Affiliate
});
What I want to do is add some more results to that query. I have another table called SiteConfiguration that has the EXACT same schema but I want to add only rows from that table where the ConfigKey does not already exist in my original query.
I have something like the following currently (and it works), but I'm looking for a "pure" LINQ way to do it:
var items = context.AffiliateConfigurations.Where(x => x.AffiliateId == affiliateId).Select(config => new ViewModels.ConfigurationItem
{
ConfigurationId = config.AffiliateConfigurationId,
ConfigKey = config.ConfigKey,
ConfigValue = config.ConfigValue,
UpdatedDate = config.UpdatedDate,
ConfigurationType = ViewModels.ConfigurationType.Affiliate
}).ToList();
var query = context.SiteConfigurations.Select(config => new ViewModels.ConfigurationItem
{
ConfigurationId = config.SiteConfigurationId,
ConfigKey = config.ConfigKey,
ConfigValue = config.ConfigValue,
UpdatedDate = config.UpdatedDate
});
foreach (var item in query)
{
if (items.All(x => x.ConfigKey != item.ConfigKey))
{
items.Add(item);
}
}
So your Question is "I have two collections and i want to merge them.how do i exclude items from the second collection,if the item's property is matching with another item's property on the first list."
Yes LINQ's UNION is what you need in such scenarios
All you need to do is a Write a simple Comparer class(Implementing the IEqualityComparer interface)for your ConfigurationItem
class ConfigEqualityComparer : IEqualityComparer<ConfigurationItem>
{
public bool Equals(ConfigurationItem a, ConfigurationItem b)
{
if (a.ConfigKey == b.ConfigKey)
{
return true;
}
else
{
return false;
}
}
public int GetHashCode(ConfigurationItem a)
{
//do some hashing here
//int hCode = IntegerField1 ^ IntegerField2;
return hCode.GetHashCode();
}
}
That is all you need.You can now run the UNION query and get your expected output.
var comparer = new ConfigEqualityComparer();
var result = Enumerable.Union<ConfigurationItem>(items, query, comparer);
I think the LINQ Union operator is what you want. You just need to create an class that implements the IEqualityComparer<T> interface for your item. Here is some demo code. Try the foreach without the comparer to see the dupes included, or as is to have them removed. I was running the code in LINQPad, if you're using Visual Studio you'll need to move the Main method into a class and call it.
void Main()
{
List<DataObject> set1 = new List<DataObject>();
List<DataObject> set2 = new List<DataObject>();
set1.Add(new DataObject("a"));
set1.Add(new DataObject("b"));
set1.Add(new DataObject("c"));
set1.Add(new DataObject("d"));
set1.Add(new DataObject("e"));
set2.Add(new DataObject("c"));
set2.Add(new DataObject("d"));
set2.Add(new DataObject("e"));
set2.Add(new DataObject("f"));
set2.Add(new DataObject("g"));
set2.Add(new DataObject("h"));
// try as
// foreach (DataObject d in set1.Union(set2)) {
// and dupes will be present
foreach (DataObject d in set1.Union(set2, new DOComparer())) {
Console.WriteLine(d);
}
}
// Define other methods and classes here
public class DataObject {
public DataObject(string value) {
Value = value;
}
public string Value {get;private set;}
public override string ToString() {
return Value;
}
}
public class DOComparer:IEqualityComparer<DataObject> {
public bool Equals(DataObject do1, DataObject do2) {
return do1.Value.Equals(do2.Value);
}
public int GetHashCode(DataObject d) {
return d.Value.GetHashCode();
}
}

MSTest: CollectionAssert.AreEquivalent failed. The expected collection contains 1 occurrence(s) of

Question:
Can anyone tell me why my unit test is failing with this error message?
CollectionAssert.AreEquivalent failed. The expected collection contains 1
occurrence(s) of . The actual
collection contains 0 occurrence(s).
Goal:
I'd like to check if two lists are identical. They are identical if both contain the same elements with the same property values. The order is irrelevant.
Code example:
This is the code which produces the error. list1 and list2 are identical, i.e. a copy-paste of each other.
[TestMethod]
public void TestListOfT()
{
var list1 = new List<MyPerson>()
{
new MyPerson()
{
Name = "A",
Age = 20
},
new MyPerson()
{
Name = "B",
Age = 30
}
};
var list2 = new List<MyPerson>()
{
new MyPerson()
{
Name = "A",
Age = 20
},
new MyPerson()
{
Name = "B",
Age = 30
}
};
CollectionAssert.AreEquivalent(list1.ToList(), list2.ToList());
}
public class MyPerson
{
public string Name { get; set; }
public int Age { get; set; }
}
I've also tried this line (source)
CollectionAssert.AreEquivalent(list1.ToList(), list2.ToList());
and this line (source)
CollectionAssert.AreEquivalent(list1.ToArray(), list2.ToArray());
P.S.
Related Stack Overflow questions:
I've seen both these questions, but the answers didn't help.
CollectionAssert use with generics?
Unit-testing IList with CollectionAssert
You are absolutely right. Unless you provide something like an IEqualityComparer<MyPerson> or implement MyPerson.Equals(), the two MyPerson objects will be compared with object.Equals, just like any other object. Since the objects are different, the Assert will fail.
It works if I add an IEqualityComparer<T> as described on MSDN and if I use Enumerable.SequenceEqual. Note however, that now the order of the elements is relevant.
In the unit test
//CollectionAssert.AreEquivalent(list1, list2); // Does not work
Assert.IsTrue(list1.SequenceEqual(list2, new MyPersonEqualityComparer())); // Works
IEqualityComparer
public class MyPersonEqualityComparer : IEqualityComparer<MyPerson>
{
public bool Equals(MyPerson x, MyPerson y)
{
if (object.ReferenceEquals(x, y)) return true;
if (object.ReferenceEquals(x, null) || object.ReferenceEquals(y, null)) return false;
return x.Name == y.Name && x.Age == y.Age;
}
public int GetHashCode(MyPerson obj)
{
if (object.ReferenceEquals(obj, null)) return 0;
int hashCodeName = obj.Name == null ? 0 : obj.Name.GetHashCode();
int hasCodeAge = obj.Age.GetHashCode();
return hashCodeName ^ hasCodeAge;
}
}
I was getting this same error when testing a collection persisted by nHibernate. I was able to get this to work by overriding both the Equals and GetHashCode methods. If I didn't override both I still got the same error you mentioned:
CollectionAssert.AreEquivalent failed. The expected collection contains 1 occurrence(s) of .
The actual collection contains 0 occurrence(s).
I had the following object:
public class EVProjectLedger
{
public virtual long Id { get; protected set; }
public virtual string ProjId { get; set; }
public virtual string Ledger { get; set; }
public virtual AccountRule AccountRule { get; set; }
public virtual int AccountLength { get; set; }
public virtual string AccountSubstrMethod { get; set; }
private Iesi.Collections.Generic.ISet<Contract> myContracts = new HashedSet<Contract>();
public virtual Iesi.Collections.Generic.ISet<Contract> Contracts
{
get { return myContracts; }
set { myContracts = value; }
}
public override bool Equals(object obj)
{
EVProjectLedger evProjectLedger = (EVProjectLedger)obj;
return ProjId == evProjectLedger.ProjId && Ledger == evProjectLedger.Ledger;
}
public override int GetHashCode()
{
return new { ProjId, Ledger }.GetHashCode();
}
}
Which I tested using the following:
using (ITransaction tx = session.BeginTransaction())
{
var evProject = session.Get<EVProject>("C0G");
CollectionAssert.AreEquivalent(TestData._evProjectLedgers.ToList(), evProject.EVProjectLedgers.ToList());
tx.Commit();
}
I'm using nHibernate which encourages overriding these methods anyways. The one drawback I can see is that my Equals method is based on the business key of the object and therefore tests equality using the business key and no other fields. You could override Equals however you want but beware of equality pollution mentioned in this post:
CollectionAssert.AreEquivalent failing... can't figure out why
If you would like to achieve this without having to write an equality comaparer, there is a unit testing library that you can use, called FluentAssertions,
https://fluentassertions.com/documentation/
It has many built in equality extension functions including ones for the Collections. You can install it through Nuget and its really easy to use.
Taking the example in the question above all you have to write in the end is
list1.Should().BeEquivalentTo(list2);
By default, the order matters in the two collections, however it can be changed as well.
I wrote this to test collections where the order is not important:
public static bool AreCollectionsEquivalent<T>(ICollection<T> collectionA, ICollection<T> collectionB, IEqualityComparer<T> comparer)
{
if (collectionA.Count != collectionB.Count)
return false;
foreach (var a in collectionA)
{
if (!collectionB.Any(b => comparer.Equals(a, b)))
return false;
}
return true;
}
Not as elegant as using SequenceEquals, but it works.
Of course to use it you simply do:
Assert.IsTrue(AreCollectionsEquivalent<MyType>(collectionA, collectionB, comparer));

Variable initalisation in while loop

I have a function that reads a file in chunks.
public static DataObject ReadNextFile(){ ...}
And dataobject looks like this:
public DataObject
{
public string Category { get; set; }
// And other members ...
}
What I want to do is the following basically
List<DataObject> dataObjects = new List<DataObject>();
while(ReadNextFile().Category == "category")
{
dataObjects.Add(^^^^^ the thingy in the while);
}
I know it's probably not how it's done, because how do I access the object I've just read.
I think what you're looking for is:
List<DataObject> dataObjects = new List<DataObject>();
DataObject nextObject;
while((nextObject = ReadNextFile()).Category == "category")
{
dataObjects.Add(nextObject);
}
But I wouldn't do that. I'd write:
List<DataObject> dataObject = source.ReadItems()
.TakeWhile(x => x.Category == "Category")
.ToList();
where ReadItems() was a method returning an IEnumerable<DataObject>, reading and yielding one item at a time. You may well want to implement it with an iterator block (yield return etc).
This is assuming you really want to stop reading as soon as you find the first object which has a different category. If you actually want to include all the matching DataObjects,
change TakeWhile to Where in the above LINQ query.
(EDIT: Saeed has since deleted his objections to the answer, but I guess I might as well leave the example up...)
EDIT: Proof that this will work, as Saeed doesn't seem to believe me:
using System;
using System.Collections.Generic;
public class DataObject
{
public string Category { get; set; }
public int Id { get; set; }
}
class Test
{
static int count = 0;
static DataObject ReadNextFile()
{
count++;
return new DataObject
{
Category = count <= 5 ? "yes" : "no",
Id = count
};
}
static void Main()
{
List<DataObject> dataObjects = new List<DataObject>();
DataObject nextObject;
while((nextObject = ReadNextFile()).Category == "yes")
{
dataObjects.Add(nextObject);
}
foreach (DataObject x in dataObjects)
{
Console.WriteLine("{0}: {1}", x.Id, x.Category);
}
}
}
Output:
1: yes
2: yes
3: yes
4: yes
5: yes
In other words, the list has retained references to the 5 distinct objects which have been returned from ReadNextFile.
This is subjective, but I hate this pattern (and I fully recognize that I am in the very small minority here). Here is how I do it when I need something like this.
var dataObjects = new List<DataObject>();
while(true) {
DataObject obj = ReadNextFile();
if(obj.Category != "category") {
break;
}
dataObjects.Add(obj);
}
But these days, it is better to say
List<DataObject> dataObjects = GetItemsFromFile(path)
.TakeWhile(x => x.Category == "category")
.ToList();
Here, of course, GetItemsFromFile reads the items from the file pointed to by path and returns an IEnumerable<DataObject>.
List<DataObject> dataObjects = new List<DataObject>();
string category = "";
while((category=ReadNextFile().Category) == "category")
{
dataObjects.Add(new DataObject{Category = category});
}
And if you have more complicated object you can do this (like jon):
List<DataObject> dataObjects = new List<DataObject>();
var category = new DataObject();
while((category=ReadNextFile()).Category == "category")
{
dataObjects.Add(category);
}
You should look into implementing IEnumerator on the class container the call to ReadNextFile(). Then you would always have reference to the current object with IEnumerator.Current, and MoveNext() will return the bool you are looking for to check for advancement. Something like this:
public class ObjectReader : IEnumerator<DataObject>
{
public bool MoveNext()
{
// try to read next file, return false if you can't
// if you can, set the Current to the returned DataObject
}
public DataObject Current
{
get;
private set;
}
}

Categories

Resources