6 dice yahtzee straight and fullhouse problems - c#

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)

Related

Linq where clause confusion

Good day, everyone!
I've written one query for my Automation test, but it's taking too long to execute, and I'm not sure how to optimize it effectively because I'm new to the Linq where clause.
Could someone please assist me with this?
var order = OrderRepositoryX.GetOrderByStatus(OrderStatusType.Dispatched, 4000)
.Where(x => x.siteId == 1 || x.siteId == 10 || x.siteId == 8 || x.siteId == 16 || x.siteId == 26 || x.siteId == 27)
.Where(x =>
{
var totalPrice = OrderRepository.GetOrderById(shared_parameters.testConfiguration, x.orderId).TotalPrice;
if (totalPrice < 500)
return false;
return true;
})
.Where(x =>
{
var cnt = ReturnOrderRepositoryX.CheckReturnOrderExists(x.orderId);
if (cnt > 0)
return false;
return true;
})
.Where(x =>
{
var cnt = OrderRepositoryX.CheckActiveOrderJobDetailsByOrderId(x.orderId);
if (cnt > 0)
return false;
return true;
})
.FirstOrDefault();
The biggest code smell here is that you are calling other repositories inside the Where clause which (assuming that repositories actually hit database) it will effectively mean that you are hitting database per every queried item. Lets imagine that OrderRepositoryX.GetOrderByStatus(OrderStatusType.Dispatched, 4000) and first Where will result in 1000 items, only second Whereclause will lead to 1000 queries to the database (and you have some more calls to repositories in subsequent Wheres). And all of this to get just one item (i.e. FirstOrDefault).
Usual approach is to avoid calling database in loops (what Where basically does here) and rewrite such code so only single SQL query will be performed against the database returning only what is needed and performing all the filtering on the database side.
Please try this instead
Avoid too many where clauses. It gets a result and then applies another check on the whole set.
var order = OrderRepositoryX.GetOrderByStatus(OrderStatusType.Dispatched, 4000)
.FirstOrDefault(x => x.siteId == 1 || x.siteId == 10 || x.siteId == 8 || x.siteId == 16 ||
x.siteId == 26 || x.siteId == 27) &&
(x =>
{
var totalPrice = OrderRepository.GetOrderById(shared_parameters.testConfiguration, x.orderId)
.TotalPrice;
return totalPrice >= 500;
})
&& (x =>
{
var cnt = ReturnOrderRepositoryX.CheckReturnOrderExists(x.orderId);
return cnt <= 0;
})
&& (x =>
{
var cnt = OrderRepositoryX.CheckActiveOrderJobDetailsByOrderId(x.orderId);
return cnt <= 0;
});

How to check if the sequence of ints has even and odd numbers alternating using LINQ, C#

Let's say I have a list of ints in c# like this:
List<int> numbers = new List<int>() { 1, 2, 7, 20, 3 };
Is there a way of checking that it has alternating odd and even numbers in it (like in the example above: if one if them is even then the next must be odd or vice versa)?
I know it's simple to check it in a loop, but I'm trying to implement this using LINQ and extension methods only.
Let's analyze the problem. What does it mean alternating parity?
index : value : value + index
------------------------------
0 : 1 : 1 - note, all sums are odd
1 : 2 : 3
2 : 7 : 9
....
Or
index : value : value + index
------------------------------
0 : 2 : 2 - note, all sums are even
1 : 1 : 2
2 : 6 : 8
....
As you can see (and you can easily prove it) alternating parity means that
index + value sums are either all odd or all even. Let's check it with a help of Linq:
List<int> numbers = new List<int>() { 1, 2, 7, 20, 3, 79 };
bool result = numbers
.DefaultIfEmpty()
.Select((item, index) => Math.Abs((long)item + (long)index))
.Aggregate((s, a) => s % 2 == a % 2 ? s % 2 : -1) >= 0;
Notes to the implementation:
DefaultIfEmpty() - empty sequence has all (all zero) values alternating; however, Aggregate has nothing to aggregate and throws exception. Let's turn empty sequence into one element sequence.
(long) in order to prevent integer overflow (int.MaxValue + index can well be out of int range)
Math.Abs: c# can return negative remainder (e.g. -1 % 2); we don't want an additional check for this, so let's work with absolute values
However, we can exploit this effect (-1 % 2 == -1) in the final Aggregate
Extension Method solution I hope is easier to understand:
public static bool IsAlternating(this IEnumerable<int> source) {
if (null == source)
throw new ArgumentNullException(nameof(source));
bool expectedZero = false;
bool first = true;
foreach (int item in source) {
int actual = item % 2;
if (first) {
first = false;
expectedZero = actual == 0;
}
else if (actual == 0 && !expectedZero || actual != 0 && expectedZero)
return false;
expectedZero = !expectedZero;
}
return true;
}
Note, that the loop solution (extension method) is more efficient: it returns false immediately when pattern doesn't meet.
You can do with LINQ in this way. you can check if there is atelast one even item at add place or an odd item at even place.
List<int> numbers = new List<int>() { 1, 2, 7, 20, 3 };
var temp = numbers.Where((x, i) => (i % 2 == 0 && x % 2 == 0) || (i % 2 == 1 && x % 2 == 1)).Take(1);
int count = temp.Count();
if(count == 0)
{
//true
}
else
{
//false
}
Note: Assuming that you are expecting even numbers at even place and odd numbers at an odd place.
You can use Aggregate to determine if a sequence is alternating.
Let's assume that if there's 0 or 1 elements then the result is true.
You can modify this logic however you see fit.
Based on this an extension method can be created:
public static bool IsAlternatingParitySequenceVerbose(this IEnumerable<int> col)
{
// state:
// -1 - sequence is not alternating
// 0 - even
// 1 - odd
if (!col.Any())
return true;
//check if first value is even
var firstState = Math.Abs(col.First() % 2);
var IsAlternating = col.Skip(1).Aggregate(firstState, (state, val) =>
{
if (state == -1)
return -1;
var current = Math.Abs(val % 2);
if (current == state)
return -1;
return current;
});
return IsAlternating != -1;
}
And then make a one-liner out of it:
public static bool IsAlternatingParitySequence(this IEnumerable<int> col) =>
!col.Any()
|| col.Skip(1).Aggregate(Math.Abs(col.First() % 2), (state, val) =>
state == -1
? -1
: Math.Abs(val % 2) is var current && current == state
? -1
: current
) != -1;

Efficient way of Comparing Counts of 3 different lists

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)

Get all repetitions starting the same appearing in a row

Is there any way to translate the following query
select word
from string1
where Left(word, 1) in (
select Left(word, 1) as firstInitial
from string1
group by Left(word , 1)
having count(*) > 1
)
into LINQ so when you run it on "While Kim kept kicking I ate my Dunkin donut with great gusto" it produces something like
(miss,Match,Match,Match,miss,miss,miss,Match,Match,miss,Match,Match)
The following solution shows one possible approach. To use this, be sure to add the MoreLINQ NuGet package to your project.
using System;
using System.Linq;
using MoreLinq;
namespace Test
{
class Program
{
static void Main(string[] args)
{
var input = "While Kim kept kicking I ate my Dunkin donut with great gusto";
var value = input.Split(' ');
var lagged = value.Lag(1, (current, previous) => new { current = current?.ToLowerInvariant(), previous = previous?.ToLowerInvariant() });
var leaded = value.Lead(1, (current, next) => new { next = next?.ToLowerInvariant() });
var results = lagged.Zip(leaded, (x, y) => x.current?.FirstOrDefault() == x.previous?.FirstOrDefault() ||
x.current?.FirstOrDefault() == y.next?.FirstOrDefault());
Console.WriteLine(string.Join(",", results));
Console.ReadLine();
}
}
}
Basically the code splits the string into multiple words, and then looks at each word (current) and the word before (previous) and after (next) it. It then compares the first letter of current vs that of previous and next.
If you want to return 1 / 0 rather than true / false then change just this line of code:
var results = lagged.Zip(leaded, (x, y) => (x.current?.FirstOrDefault() == x.previous?.FirstOrDefault() ||
x.current?.FirstOrDefault() == y.next?.FirstOrDefault()) ? 1 : 0);
The solution for this won't be as simple as a few lines, but I can try:
First, the simplest but not so elegant for loop method:
var words = string1.Split(' ').ToList();
string[] results = new string[words.Count]; //edited: can use .Count instead of .Count()
for (int i = 0; i < words.Count; i++)
{
if (i == words.Count - 1)
results[i] = char.ToLower(words[i - 1][0]) == char.ToLower(words[i][0]) ? "Match" : "miss";
else if (i == 0)
results[i] = char.ToLower(words[i + 1][0]) == char.ToLower(words[i][0]) ? "Match" : "miss";
else
{
bool leftMatch = char.ToLower(words[i - 1][0]) == char.ToLower(words[i][0]);
bool rightMatch = char.ToLower(words[i + 1][0]) == char.ToLower(words[i][0]);
results[i] = (leftMatch || rightMatch) ? "Match" : "miss";
}
}
What this does is go through each element, if the left or right word has a same initial character, it is a "Match", otherwise it is "miss". For the first and last word it just needs to check one neighbor instead of 2.
Using Enumerable.Range Method (Int32, Int32) of LINQ, as well as the ?: Operator, this can be simplified into a few lines:
var words = string1.Split(' ').ToList();
var results = Enumerable.Range(0, words.Count).Select(i => i == words.Count - 1 ?
char.ToLower(words[i - 1][0]) == char.ToLower(words[i][0]) ? "Match" : "miss" :
i == 0 ?
char.ToLower(words[i + 1][0]) == char.ToLower(words[i][0]) ? "Match" : "miss" :
(char.ToLower(words[i - 1][0]) == char.ToLower(words[i][0]) || char.ToLower(words[i + 1][0]) == char.ToLower(words[i][0])) ? "Match" : "miss" ).ToList();
The ToList() at the end is optional, you can convert ToArray() if you wish.

Getting counts of two items of same column in single table

I am developing a site, which has both desktop and mobile customers. for an analysis, i want to know whether user accessing my site through desktop or mobile device, for that i am having one table in database which has several columns for storing user's device details. In that i have one column called IsMobile, in that i am storing true/false based on user's device. Now i want to get the count of true and false using linq query.
I am using below code.
public IList<IsMobile> IsMobile(DateTime fromDate, DateTime toDate)
{
var isMobile = (from d in _db.UserDeviceDetail
where ((d.CreatedOn.Month >= fromDate.Month && d.CreatedOn.Day >= fromDate.Day && d.CreatedOn.Year >= fromDate.Year) || (d.CreatedOn.Month <= toDate.Month && d.CreatedOn.Day <= toDate.Day && d.CreatedOn.Year <= toDate.Year))
group d by d.IsMobile into g
select new IsMobile
{
Yes = g.Count(n => n.IsMobile == true),
No = g.Count(n => n.IsMobile == false)
}).ToList();
return isMobile;
}
I am getting proper count details, but with two list item one for counting Yes and another for counting No, instead i want to get single list item, by counting both yes and no at a time
As it is:
{Yes 20; No 0}
{Yes 0; No 10}
How should be:
{Yes 20; No 10}
I am new to linq query, please tell what i am doing wrong?
Grouping is what makes LINQ to create two items instead of one.
It separates whole set of items into certain number of sub-sets each processed separately.
Your query without grouping should do the trick.
UPD: following query should return list with one IsMobile item. It can be simplified if just one item can be returned instead of a list:
var isMobile = (from d in _db.UserDeviceDetail
where ((d.CreatedOn.Month >= fromDate.Month && d.CreatedOn.Day >= fromDate.Day && d.CreatedOn.Year >= fromDate.Year) || (d.CreatedOn.Month <= toDate.Month && d.CreatedOn.Day <= toDate.Day && d.CreatedOn.Year <= toDate.Year))
select d).ToList();
return new List<IsMobile>(){
new IsMobile{
Yes = isMobile.Count(n => n.IsMobile == true),
No = isMobile.Count(n => n.IsMobile == false)}
};
Why do you want to select a IList<IsMobile> at all when you just want to know how many items have IsMobile == true and how many have IsMobile == false?
I would return a Tuple<int, int> instead:
public Tuple<int, int> mobileCounts(DateTime fromDate, DateTime toDate)
{
var inTime = _db.UserDeviceDetail.Where(d=> (d.CreatedOn.Month >= fromDate.Month && d.CreatedOn.Day >= fromDate.Day && d.CreatedOn.Year >= fromDate.Year) || (d.CreatedOn.Month <= toDate.Month && d.CreatedOn.Day <= toDate.Day && d.CreatedOn.Year <= toDate.Year));
int isMobileCount = inTime.Count(d => d.IsMobile);
int isNotMobileCount = inTime.Count(d => !d.IsMobile);
return Tuple.Create(isMobileCount,isNotMobileCount);
}
You can access both informations via Item1 and Item2 property of the tuple.

Categories

Resources