Let the SequenceEqual Work for the list - c#

I have a class named Country. It has the public members, 'CountryName' and 'States'.
I have declared a list of Countries.
Now I want to write a function which accepts a new 'Country' and decides if CountryList already has the 'Country'.
I tried writing a function like
bool CheckCountry(Country c)
{
return CountryList.Exists(p => p.CountryName == c.Name
&& p.States.SequenceEqual(c.States));
}
As I want to compare States using the CountryName property of the States, I want to modify my function so that SequenceEqual works based on the CountryName of the states?

Break it down into many simple queries, and then put those queries back together.
Let's start by making a sequence of items that match by name:
var nameMatches = from item in itemList where item.Name == p.Name select item;
We need to compare those items against the sequence of names in p's subitems. What's that sequence? Write a query:
var pnames = from subitem in p.SubItems select subitem.Name;
Now you want to find all the elements from nameMatches where the sequence of names matches. How are you going to get the sequence of names? Well, we just saw how to do that with pnames, so do the same thing:
var matches = from item in nameMatches
let subitemNames =
(from subitem in item.SubItems select subitem.Name)
where pnames.SequenceEqual(subitemNames)
select item;
And now you want to know, are there any matches?
return matches.Any();
That should work just fine as is. But if you want to be really buff you can write the whole thing in one big query!
return (
from item in itemList
let pnames =
(from psubitem in p.SubItems select psubitem.Name)
let subitemNames =
(from subitem in item.SubItems select subitem.Name)
where item.Name == p.Name
where pnames.SequenceEqual(subitemNames)
select item).Any();
And you're done. Easy as pie! Just remember, break it down into small steps, solve each problem individually, and then put the solution together out of the small results.

Have you looked at implementing IComparer on Item?

If i understand you correctly you want a way to compare two items in which you first check the name of the two items then sequentially check each subitem's name. Here's what you want:
public override bool Equals(object obj)
{
return this.Name == (obj as Item).Name;
}
public override int GetHashCode()
{
return this.Name.GetHashCode();
}
public bool Check(Item obj)
{
if (this.Name != obj.Name)
return false;
//if the lists arent of the same length then they
//obviously dont contain the same items, and besides
//there would be an exception on the next check
if (this.SubItems.Count != obj.SubItems.Count)
return false;
for (int i = 0; i < this.SubItems.Count; i++)
if (this.SubItems[i] != obj.SubItems[i])
return false;
return true;
}

Related

Iterate over an IQueryable without calling ToList()

I have a DB used for a production line. It has an Orders table, and Ordertracker table, an Item table, and an Itemtracker table.
Both Orders and Items have many-to-many relationships with status. The tracker tables resolves these relationships in such a way that an item can have multiple entries in the tracker - each with a particular status.
I tried to upload a picture of the tables to make things clearer but alas, I don't have enough points yet :C
I need to find items whose last status in the Itemtracker table meets a condition, either '3' or '0'.
I then need to get the first one of these items.
The steps I am using to accomplish this are as follows:
Get all the Orders which have a certain status.
Get all the Items in that Order.
Get all the Items whose last status was = 0 or 3.
Get the first of these items.
My code is as follows:
public ITEM GetFirstItemFailedOrNotInProductionFromCurrentOrder()
{
var firstOrder = GetFirstOrderInProductionAndNotCompleted();
var items = ERPContext.ITEM.Where(i => i.OrderID == firstOrder.OrderID) as IQueryable<ITEM>;
if (CheckStatusOfItems(items) != null)
{
var nextItem = CheckStatusOfItems(items);
return nextItem ;
}
else
{
return null;
}
}
private ITEM CheckStatusOfItems(IQueryable<ITEM> items)
{
List<ITEM> listOfItemsToProduce = new List<ITEM>();
foreach (ITEM item in items.ToList())
{
var lastStatusOfItem = ERPContext.ITEMTRACKER.Where(it => it.ItemID == item.ItemID)
.OrderByDescending(it => it.ItemTrackerID).FirstOrDefault();
if (lastStatusOfItem.ItemStatus == (int)ItemStatus.Failed || lastStatusOfItem.ItemStatus == (int)ItemStatus.Confirmed)
{
listOfItemsToProduce.Add(item);
}
}
return listOfItemsToProduce.FirstOrDefault();
}
Now, this all works fine and returns what I need but I'm aware that this might not be the best approach. As it is now my IQueryable collection of items will never contain more than 6 items - but if it could grow larger, then calling ToList() on the IQueryable and iterating over the results in-memory would probably not be a good idea.
Is there a better way to iterate through the IQueryable items to fetch out the items that have a certain status as their latest status without calling ToList() and foreaching through the results?
Any advice would be much appreciated.
Using LINQ query syntax, you can build declaratively a single query pretty much the same way you wrote the imperative iteration. foreach translates to from, var to let and if to where:
private ITEM CheckStatusOfItems(IQueryable<ITEM> items)
{
var query =
from item in items
let lastStatusOfItem = ERPContext.ITEMTRACKER
.Where(it => it.ItemID == item.ItemID)
.OrderByDescending(it => it.ItemTrackerID)
.FirstOrDefault()
where (lastStatusOfItem.ItemStatus == (int)ItemStatus.Failed || lastStatusOfItem.ItemStatus == (int)ItemStatus.Confirmed)
select item;
return query.FirstOrDefault();
}
or alternatively using from instead of let and Take(1) instead of FirstOrDefault():
private ITEM CheckStatusOfItems(IQueryable<ITEM> items)
{
var query =
from item in items
from lastStatusOfItem in ERPContext.ITEMTRACKER
.Where(it => it.ItemID == item.ItemID)
.OrderByDescending(it => it.ItemTrackerID)
.Take(1)
where (lastStatusOfItem.ItemStatus == (int)ItemStatus.Failed || lastStatusOfItem.ItemStatus == (int)ItemStatus.Confirmed)
select item;
return query.FirstOrDefault();
}

How does Equals implicitly compare lists if it is intended to compare individual elements?

I have a Vendor object with 3 properties: VendorId, CompanyName and Email:
I have found that I can override the Equals method in order to compare two Vendors:
public override bool Equals(object obj)
{
if (obj == null || this.GetType() != obj.GetType())
return false;
Vendor v = (Vendor)obj;
if (v != null
&& v.CompanyName == this.CompanyName
&& v.Email == this.Email
&& v.VendorId == this.VendorId)
return true;
return base.Equals(obj);
}
...which otherwise C# can't do as it doesn't know when two vendor objects are equal, unless I instruct it specifically to compare every object property of two vendors to each other.
Now, in my unit test I found that I can actually compare not only vendors, but list of vendors:
CollectionAssert.AreEqual(listOfVendors1, listOfVendors2);
and I obtained the lists as follows:
var listOfVendors1 = new List<Vendor>() {
new Vendor() { VendorId = 1, CompanyName="comp1", Email="e#yahoo.com" },
new Vendor() { VendorId = 2, CompanyName="comp2", Email="f#yahoo.com" }
};
and for the second list:
var listOfVendors2 = new List<Vendor>() {
new Vendor() { VendorId = 1, CompanyName="comp1", Email="e#yahoo.com" },
new Vendor() { VendorId = 2, CompanyName="comp2", Email="f#yahoo.com" }
};
I noticed that when CollectionAssert.AreEqual(listOfVendors1, listOfVendors2); is processed, my custom Equals method is called, first for listOfVendors1.First() and listOfVendors2.First() and if the properties of the two vendors are equal, the Equals method is called again, comparing the second elements from each of the two lists.
I would like to understand the mechanism that makes this possible, since I don't iterate specifically through the elements of the two lists. How, when is the iteration done? I'm confused.
How does Equals compare implicitly(?) lists, if it is intended to compare individual elements? e.g. Vendor1.Equals(Vendor2)
I would like to understand the mechanism that makes this possible, since I don't iterate specifically through the elements of the two list. How, when is the iteration done? I'm confused.
The SequenceEqual method does pretty much the same thing as your CollectionAssert.AreEqual method. If you want to know how it works, you can read the source code here:
https://github.com/Microsoft/referencesource/blob/master/System.Core/System/Linq/Enumerable.cs
In summary: it iterates both sequences in the same loop and calls Equal on each pair of elements.
If you need to do something other than compare equality to pairs of elements drawn from two sequences, you can use the Zip sequence operator; if you look at its implementation you'll find it is unsurprisingly similar to SequenceEqual.
CollectionAssert.AreEqual(listOfVendors1, listOfVendors2);
Will internally iterate over your lists and check if all the items match value and order. To determine the equality of the objects it will use your custom Equals methods if available, else it would use the default comparison.

How can I remove a list from another list using a field value of the element's type if i can't modify the class directly?

Suppose I have a List<Product> named big of some object Product which has 2 fields, ID and Name. The list is fully populated, meaning that each element has both fields not null, although they can be.
I have another List<Product>, smaller than the first, named small, but this time the field Name is null for some elements, while ID is always present.
I want to remove small from big where ID is the same.
Example:
List<Product> big = { {1,A},{2,B},{3,C},{4,D} };
List<Product> small = { {1,null},{3,C} };
List<Product> result = { {2,B},{4,D}};
I can't modify the Product object, i.e. I can't implement IEquatable<Product>, and such interface isn't implemented, meaning that big.Except(small) or big.RemoveAll(small) or big.Contains(anElementOfSmall) won't work (for the sake of the question, I've tried them).
I want to avoid the double loop with iterator removal or similar, I'm searching for built-in functions with specific predicates.
Implement an IEqualityComparer for your Product that compares two Product based on ID. Then just use Except and pass the comparer:
var result = big.Except(small, new ProductComparer()).ToList();
Using a simple predicate you can easily achieve it:
big.Where(p => !small.Any(o => o.id == p.id)).ToList();
which translates in: select from big where the element (p) doesn't suffice the case where an element o in small has the same ID.
You need to tell Except how to compare instances of Product.
public class ProductEqualityComparer : IEqualityComparer<Product>
{
public bool Equals(Product x, Product y)
{
//they're both the same instance or they're both null
if(ReferanceEquals(x, y))
return true;
//only one of them is null
if(x == null || y == null)
return false;
return x.Id == y.Id;
}
public int GetHashCode(Product prod)
{
return prod == null? 0 : prod.Id.GetHashCode();
}
}
big.Except(small, new ProductEqualityComparer())

First match in a collection

I want to convert this code to a linq solution. What it does it looks into a collection of customers and see if at least one of the has a middle name. This code works fine, I'm just trying to learn linq, so looking for an alternative solution.:
//Customers - List<Customer>
private bool checkMiddleName()
{
foreach (Customer i in Customers)
{
if (i.HasMiddleName == true)
{
return true;
}
}
return false;
}
I tried to write something like: (Customers.Foreach(x=>x.HasMiddleName==true)...
but looks line it's not the method I'm looking for.
If you just want to know if theres at least one, you can use Enumerable.Any:
bool atLeastOneCustomerWithMiddleName = Customers.Any(c => c.HasMiddleName);
If you want to know the first matching customer, you can use Enumerable.First or Enumerable.FirstOrDefault to find the first customer with MiddleName==true:
var customer = Customers.FirstOrDefault(c => c.HasMiddleName);
if(customer != null)
{
// there is at least one customer with HasMiddleName == true
}
First throws an InvalidOperationException if the source sequence is empty, whereas FirstOrDefault returns null if there's no match.
var result = Customers.Where(x=>x.HasMiddleName == true).FirstOrDefault();
Based on this:
What it does it looks into a collection of customers and see if at least one of the has a middle name.
Try
return Customers.Where(x => x.HasMiddleName).Any();
This query return true if at least one custmer has the property HasMiddleName = true
You may use the following to achieve what you need:
Customers.Where(cust=> cust.HasMiddleName).Count > 0
So , if Count is more than zero means you have some customers who have Middle name.
Or for better performance you may say:
bool customerWithMiddleName;
foreach(Customer cust in Customers)
{
if(cust.HasMiddleName)
{
customerWithMiddleName = true;
break;
}
}

Get one unique List<T> from three non-unique List<T>'s

I have three lists of User:
ICollection<User> listOne = _somewhere.GetUsers(1);
ICollection<User> listTwo = _somewhere.GetUsers(2);
ICollection<User> listThree = _somewhere.GetUsers(3);
The "unique" identifier to compare on is a string field called "Email".
How do i get a unique list from the three (e.g no dupes).
I've got a unique list from two lists before using Except, but not sure how to do it with three? Do i have to use Except on the first two, then do it again on the result of the first two and the third?
Also, i should mention that the list of User's comes from external Web API calls, and there is no guarantee that each list has a unique list of email addresses.
So it's like i need two steps:
In each list, get rid of dupes
Combine the three unique lists to get one unique list.
You can just union the lists and do a dedupe (using Distinct()) once on the combined list.
var uniqueList = listOne.Union(listTwo)
.Union(listThree)
.Distinct(new EmailComparer())
.ToList();
For the comparer could be as simple as this:
class EmailComparer : IEqualityComparer<User>
{
public bool Equals(User x, User y)
{
return x.Email == y.Email;
}
public int GetHashCode(User obj)
{
return obj.Email.GetHashCode();
}
}
Edit:
As pointed out in comments Distinct() is not needed if we apply the custom email comparer to Union():
var emailComparer = new EmailComparer();
var uniqueList = listOne.Union(listTwo, emailComparer)
.Union(listThree, emailComparer)
.ToList();
If it does not matter which user you pick from the list of users with the same e-mail, you can do this:
var res = listOne.Concat(listTwo).Concat(listThree)
.GroupBy(u => u.Email)
.Select(g => g.First());
Again, this assumes that when e-mail addresses are the same, it does not matter which user you'd pick.
First define how we want to define uniqueness:
private class EmailEqComparer : IEqualityComparer<User>
{
public bool Equals(User x, User y)
{
//don't bother shortcutting on reference equality, since if they come from
//separate web-calls it's unlikely to happen, though it could
//with some optimisations on the web-client code.
if(x == null)
return y == null;
if(y == null)
return false;
return x.Email == y.Email;
}
public int GetHashCode(User obj)
{
return obj == null ? 0 : obj.Email.GetHashCode();
}
}
Now call Distinct on the items of each, and put the results into a list:
var distinctUnion = listOne.Concat(listTwo).Concat(listThree).Distinct(new EmailEqComparer());

Categories

Resources