Efficient way of Comparing Counts of 3 different lists - c#

I have 3 list objects and I need them to all have the same count.. or all be empty (Count = 0).
If one or more lists has a larger/smaller count than the other list(s) then I need to catch that.
Is there a more efficient way of writing this then doing multiple if statements?
public static bool ThreeListComparison(List<string> lstOne,
List<int> lstTwo, List<decimal> lstThree)
{
var firstLstCount = lstOne.Count;
var secondLstCount = lstTwo.Count;
var thirdLstCount = lstThree.Count;
if ((firstLstCount == 0 || secondLstCount == 0 || thirdLstCount == 0) && (firstLstCount != 0 || secondLstCount == 0) &&
(firstLstCount == 0 || secondLstCount != 0)) return true;
if (firstLstCount == 0 && secondLstCount != 0) return false;
if (firstLstCount != 0 && secondLstCount == 0) return false;
if (firstLstCount == 0 || secondLstCount == 0) return true;
return firstLstCount == secondLstCount;
}
This is what I've started with two lists, but after writing it I am hoping for a better way.
Any help is appreciated.

Since zero is a perfectly valid integer number, comparing all three lists for zero count is redundant. You can rely on transitive property of equality to do the check with a simple && statement:
return lstOne.Count == lstTwo.Count && lstTwo.Count == lstThree.Count;

What about a simple way to check an unlimited number of lists using LINQ?:
public static bool ListComparison(params List<string>[] lists)
{
return lists.All(l => l.Count == lists[0].Count);
}

using System.Linq
Make an array of lists:
List<string>[] lists = new List<string>[] { firstLst, secondLst, thirdLst };
Then calculate the maximum array size:
int maxSize = lists.Max(list => list.Count);
And then respond if any of them is different size:
if(!lists.All(list => list.Count == maxSize))
{
//do some stuff
}

var arr = new[] { firstLstCount , secondLstCount , thirdLstCount};
To check if they are the same count
return arr.Distinct().Count() == 1

Subtract firstListCount from secondListCount and thirdListCount. If all three are zero, then they all match. For example:
return new[] { 0, secondLstCount - firstLstCount, thirdLstCount - firstLstCount }.All(x => x == 0)

Related

Refactor and reduce cyclomatic complexity with LINQ

I have a method that I feel like could be refactored more efficiently with LINQ.
The purpose of the function is to use some logic to determine which phone number to return. The logic is: Any returned number must be sms_capable. If a number was last used for an rx, use it, otherwise return the first valid number by type in this order: Other, Home, Office
string GetDefaultSMSPhoneNumber(IEnumerable<PhoneNumbers> patientNumbers)
{
const int PHONE_TYPE_HOME = 1;
const int PHONE_TYPE_OFFICE = 3;
const int PHONE_TYPE_OTHER = 9;
var phoneNumberByType = patientNumbers.Where(p => p.sms_capable == 1).GroupBy(p => p.phone_type_id);
// Select the phone number last used in creating a prescription
if (patientNumbers.Where(p => p.sms_capable == 1 && p.last_used_for_rx == 1).Count() > 0)
{
return patientNumbers.Where(p => p.sms_capable == 1 && p.last_used_for_rx == 1).FirstOrDefault().phone_number;
}
// If no number has been used, select a configured SMS number in the following order (Other, Home, Office)
if (patientNumbers.Where(p => p.sms_capable == 1 && p.phone_type_id == PHONE_TYPE_OTHER).Count() > 0)
{
return patientNumbers.Where(p => p.sms_capable == 1 && p.phone_type_id == PHONE_TYPE_OTHER).FirstOrDefault().phone_number;
}
// If no number has been used, select a configured SMS number in the following order (Other, Home, Office)
if (patientNumbers.Where(p => p.sms_capable == 1 && p.phone_type_id == PHONE_TYPE_HOME).Count() > 0)
{
return patientNumbers.Where(p => p.sms_capable == 1 && p.phone_type_id == PHONE_TYPE_HOME).FirstOrDefault().phone_number;
}
// If no number has been used, select a configured SMS number in the following order (Other, Home, Office)
if (patientNumbers.Where(p => p.sms_capable == 1 && p.phone_type_id == PHONE_TYPE_OFFICE).Count() > 0)
{
return patientNumbers.Where(p => p.sms_capable == 1 && p.phone_type_id == PHONE_TYPE_OFFICE).FirstOrDefault().phone_number;
}
return string.Empty;
}
I know the first thing I can do is filter the list to only sms_capable numbers. I feel like I should be able to use .GroupBy to group the numbers by there type, but after they're grouped I'm not sure how to return the first non empty value? I feel like I'm looking for a way to coalesce in linq?
string GetDefaultSMSPhoneNumber(IEnumerable<PhoneNumbers> patientNumbers)
{
const int PHONE_TYPE_HOME = 1;
const int PHONE_TYPE_OFFICE = 3;
const int PHONE_TYPE_OTHER = 9;
var phoneNumberByType = patientNumbers.Where(p => p.sms_capable == 1).GroupBy(p => p.phone_type_id);
var phoneNumber = patientNumbers.FirstOrDefault(p => p.sms_capable == 1 && p.last_used_for_rx == 1)?.phone_number;
// Doesn't work
if (string.IsNullOrEmpty(phoneNumber))
{
var number = phoneNumberByType.FirstOrDefault(p => p.Key == PHONE_TYPE_OTHER && p.Where(x => !string.IsNullOrEmpty(x.phone_number)) ||
(p.Key == PHONE_TYPE_HOME && p.Where(x => !string.IsNullOrEmpty(x.phone_number)) ||
(p.Key == PHONE_TYPE_OFFICE && p.Where(x => !string.IsNullOrEmpty(x.phone_number))));
}
If you need matching against predicates in specific order you can create a collection of Func<PhoneNumbers, bool> and iterate it (also if PhoneNumbers is a class or record then you don't need Count, if it is not, better use Any instead of count):
string GetDefaultSMSPhoneNumber(IEnumerable<PhoneNumbers> patientNumbers)
{
const int PHONE_TYPE_HOME = 1;
const int PHONE_TYPE_OFFICE = 3;
const int PHONE_TYPE_OTHER = 9;
var predicates = new List<Func<PhoneNumbers, bool>>()
{
p => p.sms_capable == 1 && p.last_used_for_rx == 1,
p => p.sms_capable == 1 && p.phone_type_id == PHONE_TYPE_OTHER,
p => p.sms_capable == 1 && p.phone_type_id == PHONE_TYPE_HOME,
p => p.sms_capable == 1 && p.phone_type_id == PHONE_TYPE_OFFICE
}; // Can be moved to static field
// prevent potential multiple materialization of the source
var enumerated = patientNumbers as ICollection<PhoneNumbers> ?? patientNumbers.ToArray();
foreach (var predicate in predicates)
{
var firstOrDefault = enumerated.FirstOrDefault(predicate);
if (firstOrDefault is not null)
{
return firstOrDefault.phone_number;
}
}
return string.Empty;
}
Also in this particular case you can "prefilter" the enumerated with .Where(p => p.sms_capable == 1) to improve performance a bit:
// ...
var enumerated = patientNumbers
.Where(p => p.sms_capable == 1)
.ToArray();
var predicates = new List<Func<PhoneNumbers, bool>>()
{
p => p.last_used_for_rx == 1,
p => p.phone_type_id == PHONE_TYPE_OTHER,
p => p.phone_type_id == PHONE_TYPE_HOME,
p => p.phone_type_id == PHONE_TYPE_OFFICE
};
// ...
This isnt using linq, but you can refactor this by putting some of the complexity into their own methods
private IEnumerable<IGrouping<int, PhoneNumbers>> GetSmsCapablePhoneNumbersByType(IEnumerable<PhoneNumbers> patientNumbers)
{
return patientNumbers.Where(p => p.sms_capable == 1).GroupBy(p => p.phone_type_id);
}
private PhoneNumbers GetLastUsedSmsNumber(IEnumerable<PhoneNumbers> patientNumbers)
{
return patientNumbers.FirstOrDefault(p => p.sms_capable == 1 && p.last_used_for_rx == 1);
}
private PhoneNumbers GetFirstSmsNumberByType(IEnumerable<PhoneNumbers> patientNumbers, int phoneTypeId)
{
return patientNumbers.FirstOrDefault(p => p.sms_capable == 1 && p.phone_type_id == phoneTypeId);
}
public string GetDefaultSMSPhoneNumber(IEnumerable<PhoneNumbers> patientNumbers)
{
var phoneNumberByType = GetSmsCapablePhoneNumbersByType(patientNumbers);
var lastUsedSmsNumber = GetLastUsedSmsNumber(patientNumbers);
if (lastUsedSmsNumber != null)
{
return lastUsedSmsNumber.phone_number;
}
var defaultSmsNumber = GetFirstSmsNumberByType(patientNumbers, PHONE_TYPE_OTHER)
?? GetFirstSmsNumberByType(patientNumbers, PHONE_TYPE_HOME)
?? GetFirstSmsNumberByType(patientNumbers, PHONE_TYPE_OFFICE);
if (defaultSmsNumber != null)
{
return defaultSmsNumber.phone_number;
}
return string.Empty;
}
If you do it correctly, your method names should describe exactly whats happening, so when somone else reads your code they should be able to follow whats happening by reading the method names (This also means there is less need for comments)

Top K Numbers from Un Ordered Array Without using Sort or Linq OrderBy methods

I was just binging/googling about inbuilt functions in .Net to get Top K numbers/elements from un-sorted array without using linq order by or sort methods.
Another way I can write my own TopK method by implementing selection algorithm.
But my main intension is "Is there any inbuilt function which uses selection algorithm like quick select or heap."
If there is no inbuilt TopK method I would like to know how to do with Linq or .Net without using Sort and OrderBy methods.
Reason for avoiding Sort and OrderBy is "they follow sorting algorithms internally not selection algorithm". Please correct me if I'm wrong on this.
How about this:
void Main()
{
var kth = 4;
var arr = new int [] {5, 1, 3, 4, 2, 1};
var val = getItem(arr, kth);
}
int getItem(int[] items, int kth)
{
if (kth == 0 || kth > items.Length)
{
throw new IndexOutOfRangeException();
}
else if (items.Length == 1 && kth == 1)
{
// only 1 item so return it
return items[0];
}
else if (items.Length == 2)
{
if (kth ==1)
{
// two items, 1st required so return smallest
return (items[0] <= items[1] ? items[0] : items[1]);
}
else
{
// two items, 2nd required so return smallest
return (items[0] <= items[1] ? items[1] : items[0]);
}
}
var middleItem = (int)((items.Length)/2);
var pivot = items[middleItem];
var leftarr = items.Where(s => s < pivot).ToArray();
if (leftarr.Length + 1 == kth)
{
//our pivot is the item we want
return pivot;
}
else if (leftarr.Length >= kth)
{
// our item is in this array
return getItem(leftarr, kth);
}
else
{
// need to look in the right array
var rightarr = items.Where(s => s >= pivot).ToArray();
return getItem(rightarr, kth - leftarr.Length);
}
}

Faster way to filter a list of objects in to another list based on a condition

I have a list ( ios contacts ) which i need to filter based on the firstname , last name and email starting with the match string. I have about 5000 contacts and currently it takes about 2 seconds to filter results. Here is my code.
var personList = people.FindAll (p =>
(p.LastName != null && p.LastName.IndexOf (findstr, StringComparison.OrdinalIgnoreCase) == 0) || (p.FirstName != null && p.FirstName.IndexOf (findstr, StringComparison.OrdinalIgnoreCase) == 0
|| (p.GetEmails ().ToList ().Count > 0 && (p.GetEmails ().ToList ().FindAll (e => e.Value.IndexOf (findstr, StringComparison.OrdinalIgnoreCase) == 0).Count > 0)))
);
Can anyone suggest a faster way to do this? Thanks
Using IndexOf(..) == 0 to compare is the same as asking if that string (Name, LastName or Email) starts with the given findstr. If instead you want to know if the string contains de findstr use the String.Contains or IndexOf(..) != -1
I would also separate those predicates for code clarity like this:
Func<string, bool> filter = s => !String.IsNullOrWhiteSpace(s) && s.StartsWith(findstr, StringComparison.OrdinalIgnoreCase);
var personList = people.Where(p => filter(p.FistName)
|| filter(p.LastName)
|| p.GetEmails().Select(e => e.Value).Any(filter));
Now if you want you can do that in parallel:
var personList = people.AsParallel().
Where(p => filter(p.FistName)
|| filter(p.LastName)
|| p.GetEmails().Select(e => e.Value).Any(filter));
Instead of FindAll, and calls to ToList and then Count , use:
var personList = people.Where(p => (p.LastName != null
&& p.LastName.IndexOf(findstr, StringComparison.OrdinalIgnoreCase) == 0)
|| (
p.FirstName != null && p.FirstName.IndexOf(findstr, StringComparison.OrdinalIgnoreCase) == 0
|| (p.GetEmails().Count > 0 && (p.GetEmails()
.Where(e =>
e.Value.IndexOf(findstr, StringComparison.OrdinalIgnoreCase) == 0).Count > 0))
));
You can use:
Any() instead of ToList().Count > 0
First() and then check if it matches the condition instead of IndexOf (findstr, StringComparison.OrdinalIgnoreCase) == 0
You can find full list of queues here.

conditional where [duplicate]

This question already has an answer here:
Conditional WHERE in LINQ
(1 answer)
Closed 9 years ago.
I need to write a conditional query in linq:
if field.val1 == 0:
var q = from field in cw.fields
select field
if field.val1 != 0 and field.val2 == 0:
var q = from field in cw.fields
where field.val1 == 1
select field
if field.val1 != 0 and field.val2 != 0:
var q = from field in cw.fields
where field.val1 == 1 and field.val2 == 1
select field
How can I do this?
Because your condition depends on the values your querying, you have to just extend your where clause with additional cases using ||:
var q = from field in cw.fields
where
(field.val1 == 0) ||
(field.val1 != 0 and field.val2 == 0 && field.val1 == 1) ||
(field.val1 != 0 and field.val2 != 0 && field.val1 == 1 and field.val2 == 1)
select field
#MarcinJuraszek's answer will work but let me give a different perspective.
First of all, ditch the sql style syntax. It gets in the way, obscures what is actually going on and is generally just a feel-good abstraction rather than one that will result in more communicative code. Let's rewrite things using lambda syntax
IEnumerable<string> getFields(SomeField field, YourDatabase cw) {
if(field.val1 == 0:)
return cw.fields.ToList();
if(field.val1 != 0 and field.val2 == 0)
return cw.fields.Where(f => f.val1 ==1).ToList();
if(field.val1 != 0 and field.val2 != 0)
return cw.fields.Where(f => f.val1 == 1 and f.val2 == 1).ToList();
}
note that the only way things differ is the lambda, and a lambda is an object, in this case, an Expression object! So you can use a very standard abstract factory pattern here
return cw.fields.Where(matchingCondition(field)).ToList();
//elsewhere...
Expression<Func<SomeField, bool>> matchingCondition(SomeField field) {
if(field.val1 == 0:) return f => true;
if(field.val1 != 0 and field.val2 == 0) return f => f.val1 == 1
if(field.val1 != 0 and field.val2 != 0) return f => f.val1 == 1 and f.val2 == 1;
throw new InvalidOperationException("No match to condition");
}

6 dice yahtzee straight and fullhouse problems

I have made a 5 dice yahtzee game, and i am trying to make it work for 6 dice aswell, can you help this two functions more universal my code as of this moment :)
i have prepared dice values in list int[] i
and I have detected fullHouse with this very simple method:
Array.Sort(i);
if( ((i[0] == i[1]) && (i[1] == i[2]) && (i[3] == i[4]))
{
... sum it up
}
else if((i[0] == i[1]) && (i[2] == i[3]) && (i[3] == i[4]))
{
... sum it up
}
I have detected straight with this very simple method
Array.Sort(i);
if( ((i[0] == 1) &&
(i[1] == 2) &&
(i[2] == 3) &&
(i[3] == 4) &&
(i[4] == 5)) ||
((i[0] == 2) &&
(i[1] == 3) &&
(i[2] == 4) &&
(i[3] == 5) &&
(i[4] == 6)) )
{
... sum it up
}
Thx in advance
Writing the logic completely manually like that produces code that is unwieldy and hard to extend. Abstracting things a little with LINQ will help a lot.
To detect a full house, group rolls by value and check the cardinality of each group (I am using i as the array of rolls to follow the original code, but that's a bad name for an array):
var groups = i.GroupBy(i => i);
Then you can check for a full house very easily:
var hasFullHouse = groups.Any(g1 =>
g1.Count >= 3 && groups.Except(g1).Any(g2 => g2.Count >= 2)
);
"If there is any group with at least 3 dice and there is also a
different group with at least 2 dice, you have a full house."
To check for a straight, iterate over the groups in order. For each one, check if the group representing the previous die roll exists. If it does increment a counter, otherwise reset the counter. If the counter ever reaches 5 there is a straight:
var runLength = 0;
var map = groups.ToDictionary(g => g.Key, g => g.Count);
foreach (var roll in map.Keys.OrderBy(k => k))
{
var runLength = map.Contains(roll - 1) ? runLength + 1 : 0;
if (runLength == 5)
{
// straight detected
}
}
Both of these methods will work regardless of the number of dice in the game.
if you have list of ints you can check if they are consecutive values (straight)
bool isfullhouse = !list.Select((i,j) => i-j).Distinct().Skip(1).Any();
return isfullhouse;
you can make your array a list by:
var list = yourArray.ToList();
second one can be modified to look like:
var list = yourArray.ToList().Skip(2);
if(yourArray[0]==yourArray[1]) // checks XX
{
var distincted = list.Distinct();
if(distincted.Count()==1) // checks if other values are equal
{
if(distincted[0]!=yourArray[0]) // checks if not XXXXXX
return true;
}
}
return false;
it will check if there is a full house like XX YYYY (it can have any number of Y's)

Categories

Resources