The idea behind my CheckOrder() method is to compare two Lists of string Lists by seeing if the first List contains a List from the second List. If so, it removes the List from the second List, so that it cannot be compared against again. It repeats this until there are no more Lists in the second List to compare against, or it determines that the Lists of Lists are inequal.
To illustrate what my desired outcome is, here is a sample of my code. Given the two Lists of string Lists at the top, I would expect CheckOrder() to return true.
private List<List<string>> testOrder = new List<List<string>> { new List<string> { "Burger", "Cheese" }, new List<string> { "Hot Dog", "Ketchup" } };
private List<List<string>> testOrder2 = new List<List<string>> { new List<string> { "Hot Dog", "Ketchup" }, new List<string> { "Burger", "Cheese" } };
public bool CheckOrder() {
if (testOrder2.Count != testOrder.Count) {
return false;
}
for (int i = 0; i < testOrder.Count; i ) {
if (!testOrder2.Contains(testOrder[i])) {
return false; //here
} else {
testOrder2.Remove(testOrder[i]);
}
}
return true;
}
No matter what I try, I can't get CheckOrder() to return anything but false from the commented line. I see other suggestions to use something like SequenceEqual() from the Linq library, but order being unimportant is the whole point of this method I'm trying to write.
CodePudding user response:
So you'll want to make an IEqualityComparer<List<string>>
. I'll generalise this to IEqualityComparer<IList<T>>
so that any kind of list can be used. I've also optionally allowed an equality comparer for the individual T
item values, but it's not necessary here unless you want to make it case insensitive.
The comparer:
public class ListComparer<TItem> : IEqualityComparer<IList<TItem>>
{
private readonly IEqualityComparer<TItem> _itemComparer;
public ListComparer()
: this(EqualityComparer<TItem>.Default)
{
}
public ListComparer(IEqualityComparer<TItem> itemComparer)
{
_itemComparer = itemComparer;
}
// Determine if the two lists have the same content
public bool Equals(IList<TItem> x, IList<TItem> y)
{
if (Object.ReferenceEquals(x, y))
{
return true;
}
else if (x is null != y is null)
{
return false;
}
else if (x is null && y is null)
{
return true;
}
return x.SequenceEqual(y, _itemComparer);
}
// Generate a hashcode from the individual items of the list
// this is dependent on the order of the items
// ({A,B} will produce a different value to {B,A})
public int GetHashCode(IList<TItem> obj)
{
unchecked
{
int hashCode = 63949;
foreach (var item in obj)
{
hashCode = _itemComparer.GetHashCode(item);
hashCode *= 13;
}
return hashCode;
}
}
}
Then we can simplify your method to this:
public bool CheckOrder()
{
// build the comparer for comparing the inner lists
var innerComparer = new ListComparer<string>();
// ensure the lists have the same number of items
if (testOrder2.Count != testOrder.Count)
{
return false;
}
foreach (var order in testOrder)
{
// if an inner list doesn't also exist in testOrder2
// then return false
if (!testOrder2.Contains(order, innerComparer))
{
return false;
}
}
// all lists were found, so return true
return true;
}
As mentioned in the code comments, this assumes that { { "Burger", "Hot Dog" } }
vs { { "Hot Dog", "Burger" } }
should return false
as the inner lists are different.
This doesn't entirely deal with duplicates as you could have { { "Burger", "Hot Dog" }, { "Burger", "Hot Dog" } }
in testOrder
and { { "Burger", "Hot Dog" }, { "Chicken", "Coke" } }
in testOrder2
and it would still work.
Alternatively, you could group and join the data using LINQ, which will check the counts too:
public bool CheckOrder()
{
var innerComparer = new ListComparer<string>();
if (testOrder2.Count != testOrder.Count)
{
return false;
}
return testOrder
// group using the list as the key
.GroupBy(o => o, innerComparer)
// join the other list (which has been grouped by its lists)
.Join(
testOrder2.GroupBy(o => o, innerComparer),
// use the group key from the first list's groups
g1 => g1.Key,
// use the group key from the second list's groups
g2 => g2.Key,
// ensure that the counts are equal and produce a bool as a result
(g1, g2) => g1.Count() == g2.Count(),
// provide the comparer again so that the keys can be checked for equality
innerComparer
)
// ensure that all returned values are true
.All(r => r);
}
If you want to ensure that there's only ever 1 pair, you could change this line:
(g1, g2) => g1.Count() == g2.Count()
to this:
(g1, g2) => g1.Count() == 1 && g2.Count() == 1
CodePudding user response:
I think DiplomacyNotWar has the more efficient way of doing it, as well as some good points you should try to research, but I decided to take a crack at it and here is a rudimentary version that will also get the job done. Please let me know if you have any questions. Hope this helps :)
public class Class1
{
//Renamed testOrder
public List<List<string>> testOrder1 = new List<List<string>> { new List<string> { "Burger", "Cheese" }, new List<string> { "Hot Dog", "Ketchup" } };
public List<List<string>> testOrder2 = new List<List<string>> { new List<string> { "Hot Dog", "Ketchup" }, new List<string> { "Burger", "Cheese" } };
public bool CheckOrder()
{
//Disqualify if counts vary
if (testOrder2.Count != testOrder1.Count)
{
return false;
}
else
{
//This is the janky part, converting the lists to strings ☺
//Create string only working copies of each primary list
List<string> testOrder1c = new List<string>();
List<string> testOrder2c = new List<string>();
//Convert original lists to strings
foreach (var secondaryList in testOrder1)
{
testOrder1c.Add(string.Join(",", secondaryList.ToArray()));
}
foreach (var secondaryList in testOrder2)
{
testOrder2c.Add(string.Join(",", secondaryList.ToArray()));
}
//Create a list of distinct values for each
List<string> testOrder1d = testOrder1c.Distinct().ToList();
List<string> testOrder2d = testOrder2c.Distinct().ToList();
//Disqualify if counts vary
if(testOrder1d.Count != testOrder2d.Count) { return false; }
//Disqualify if distinct lists do not match
testOrder1d.Sort();
testOrder2d.Sort();
if(!testOrder1d.SequenceEqual(testOrder2d))
{
return false;
}
//Create another array to detail the count of each distinct value
List<int> testOrder1dn = new List<int>();
List<int> testOrder2dn = new List<int>();
foreach (var distinctValue in testOrder1d)
{
testOrder1dn.Add(testOrder1c.Where(x => x == distinctValue).Count());
}
foreach (var distinctValue in testOrder2d)
{
testOrder2dn.Add(testOrder2c.Where(x => x == distinctValue).Count());
}
//Finally, we will compare these distinct values and their counts to see if they are equivalent.
//Disqualify if distinct counts do not match
for (int i = 0; i < testOrder1d.Count; i )
{
if(testOrder1dn[i] != testOrder2dn[i])
{
return false;
}
}
//All distinct lists appear the same number of times in each list:
return true;
}
}
}