Check for poker straight - php

I have managed to create an algorithm to check the rank of a poker hand. It works 100% correctly, but it's very slow. I've been analysing the code, and the check straight function is one of the slowest parts of it.
So my question is, is there a better way of calculating whether a hand make a straight?
Here is some details:
7 cards, 2 from holder, 5 from board. A can be high or low.
Each card is assigned a value:
2 = 2
3 = 3
..
9 = 9
T = 10
J = 11
Q = 12
K = 13
A = 14
The script has an array of all 7 cards:
$cards = array(12,5,6,7,4,11,3);
So now I need to be able to sort this into an array where it:
discards duplicates
orders the card from lowest to highest
only returns 5 consecutive cards I.e. (3,4,5,6,7)
It needs to be fast; loops and iterations are very costly. This is what I currently use and when it tries to analyse say 15000 hands, it takes its toll on the script.
For the above, I used:
discard duplicates (use array_unique)
order cards from lowest to highest (use sort())
only return 5 consecutive cards (use a for loop to check the values of cards)
Does anyone have any examples of how I could improve on this? Maybe even in another language that I could perhaps look at and see how it's done?

Instead of working with array deduping and sorting, consider using a bitmask instead, and setting bits to 1 where the card value is set. A bitmask works like a Set datastructure and comes with additional advantages when it comes to detecting contiguous elements.
for ($i = 0; $i < count($cards); $i++) {
$card = $cards[$i];
// For each card value, set the bit
if ($card == 14) {
// If card is an ace, also set bit 1 for wheel
$cardBitmask |= 0x2;
}
$cardBitmask |= (1 << $card);
}
// To compare, you simply write a for loop checking for 5 consecutive bits
for($i = 10; $i > 0; $i--)
{
if ($cardBitmask & (0x1F << $i) == (0x1F << $i)) {
// Straight $i high was found!
}
}

Consider the Java implementation at this link. I've included it here:
public static boolean isStraight( Card[] h )
{
int i, testRank;
if ( h.length != 5 )
return(false);
sortByRank(h); // Sort the poker hand by the rank of each card
/* ===========================
Check if hand has an Ace
=========================== */
if ( h[4].rank() == 14 )
{
/* =================================
Check straight using an Ace
================================= */
boolean a = h[0].rank() == 2 && h[1].rank() == 3 &&
h[2].rank() == 4 && h[3].rank() == 5 ;
boolean b = h[0].rank() == 10 && h[1].rank() == 11 &&
h[2].rank() == 12 && h[3].rank() == 13 ;
return ( a || b );
}
else
{
/* ===========================================
General case: check for increasing values
=========================================== */
testRank = h[0].rank() + 1;
for ( i = 1; i < 5; i++ )
{
if ( h[i].rank() != testRank )
return(false); // Straight failed...
testRank++; // Next card in hand
}
return(true); // Straight found !
}
}
A quick Google search for "check for poker straight (desired_lang)" will give you other implementations.

You could just sort the cards and loop over them in an array - saving always the last card and compare them with the current one.
$cards = array(12,5,6,7,4,11,3);
sort($cards);
$last = 0;
$count = 0;
$wheel = false;
foreach ($cards as $card) {
if ($card == $last) {
continue;
} else if ($card == ++$last) {
$count++;
} else {
if ($last == 6) $wheel = true;
$count = 1;
$last = $card;
}
if ($count == 5 || ($card == 14 && $wheel)) {
echo "straight $last";
$straight = range($last - 4, $last);
break;
}
}

You may go like this, you don't need to sort or anything (assuming that 2 is 2 and 14 is ace):
$cards = [12,5,6,7,4,11,3];
function _inc(&$i) {
if ($i == 14)
$i = 2;
else
$i++;
return $i;
}
$straight = false;
for($i = 2; $i <= 14; $i++) {
$ind = $i;
if (!in_array($ind, $cards)) continue;
$s = [$ind, _inc($ind), _inc($ind), _inc($ind), _inc($ind)];
$straight = count(array_intersect($s, $cards)) == count($s);
if ($straight) break;
}
print $straight;

Related

Expanding Round-robin tournament 1v1 to a 1v1v1v1

I am trying to expand and improve the round-robin algorithm from 1v1 group to a 1v1v1v1 group(something like free for all). I've made the function itself to do the schedule, but when I tried to expand it, some teams repetead. For example, I have 16 teams and I want to have 5 rounds, team 1 appears 7 times in 5 rounds and team2 appears 3 times in 5 rounds. I need them to appear 5 times at most.I really can't understand how I can do it. Any advice is welcomed and links.
function make_schedule(array $teams, int $rounds = null, bool $shuffle = true, int $seed = null): array
{
$teamCount = count($teams);
if($teamCount < 4) {
return [];
}
//Account for odd number of teams by adding a bye
if($teamCount % 2 === 1) {
array_push($teams, null);
$teamCount += 1;
}
if($shuffle) {
//Seed shuffle with random_int for better randomness if seed is null
srand($seed ?? random_int(PHP_INT_MIN, PHP_INT_MAX));
shuffle($teams);
} elseif(!is_null($seed)) {
//Generate friendly notice that seed is set but shuffle is set to false
trigger_error('Seed parameter has no effect when shuffle parameter is set to false');
}
$quadTeamCount = $teamCount / 4;
if($rounds === null) {
$rounds = $teamCount - 1;
}
$schedule = [];
for($round = 1; $round <= $rounds; $round += 1) {
$matchupPrev = null;
foreach($teams as $key => $team) {
if($key >= $quadTeamCount ) {
break;
}
$keyCount = $key + $quadTeamCount;
$keyCount2 = $key + $quadTeamCount + 1;
$keyCount3 = $key + $quadTeamCount + 2;
$team1 = $team;
$team2 = $teams[$keyCount];
$team3 = $teams[$keyCount2];
$team4 = $teams[$keyCount3];
//echo "<pre>Round #{$round}: {$team1} - {$team2} - {$team3} - {$team4} == KeyCount: {$keyCount} == KeyCount2: {$keyCount2} == KeyCount3: {$keyCount3}</pre>";
//Home-away swapping
$matchup = $round % 2 === 0 ? [$team1, $team2, $team3, $team4 ] : [$team2, $team1, $team4, $team3];
$schedule[$round][] = $matchup ;
}
rotate($teams);
}
return $schedule;
}
Rotate function:
function rotate(array &$items)
{
$itemCount = count($items);
if($itemCount < 3) {
return;
}
$lastIndex = $itemCount - 1;
/**
* Though not technically part of the round-robin algorithm, odd-even
* factor differentiation included to have intuitive behavior for arrays
* with an odd number of elements
*/
$factor = (int) ($itemCount % 2 === 0 ? $itemCount / 2 : ($itemCount / 2) + 1);
$topRightIndex = $factor - 1;
$topRightItem = $items[$topRightIndex];
$bottomLeftIndex = $factor;
$bottomLeftItem = $items[$bottomLeftIndex];
for($i = $topRightIndex; $i > 0; $i -= 1) {
$items[$i] = $items[$i - 1];
}
for($i = $bottomLeftIndex; $i < $lastIndex; $i += 1) {
$items[$i] = $items[$i + 1];
}
$items[1] = $bottomLeftItem;
$items[$lastIndex] = $topRightItem;
}
For example:
If I set rounds to 5, every team play 5 matches.
Array example Screenshot
Dealing with the 5th round:
Well, after I thought for a bit, maybe there isn't a way for them to play without repeatence, but if it is lowered to minumum, like every team should play 5 times only - this means once a round. That's what I meant. And what I meant under 'they repeat' is that there are like: 16 teams, 5 rounds and some teams are going like 7 times for all these rounds and other teams are going 3 times for these 5 rounds. I want to avoid this and to make every team play 5 rounds at most.
Your foreach() with the selection of the other 3 teams is wrong. One of them have to make steps with a multiple of 4. If you don't, you will select the teams at the beginning more than one and don't select the teams at the end of the array at all. This will result in wrong team matchups like this (teams are letters here):
abcd
bcde
cdef
defg
And then your break; hits.
Instead it should look something like this:
for ($i=0; $i<4; $i++) {
$matchup = array();
for ($j=0; $j<4; $j++) {
$matchup[] = $teams[4*$i+$j];
}
$schedule[$round][] = $matchup ;
}
This way you get the following pairing (again, using letters as teams):
abcd
efgh
ijkl
mnop
This algorithm will split the team list in four groups:
abcd|efgh|ijkl|mnop
Keep in mind that depending on the shuffling of the $teams array for the next round you might get the same opponent twice.
adei|klnf|gjmc|pobh
Here the teams ad, kl and op will face again.

Program to find X-numbers in an array

in an N numbers of array a number is a X-number if it is divisible by atleast one other number in the array. Program to find how many such numbers exists in an given array
example 1 : in array [1,2,3] , number of x-numbers is 2 ( 2 and 3 as they are divisible by number 1 )
example 2 : in array [2,3,5,8] , number of x-numbers is 1 ( 8 is divisible by 2 )
example 3 : in array [2,3,6,12] , number of x-numbers is 2 ( 6 is divisible by 2 and 3 , 12 is divisible by 2 and 3 and 6 )
I am using the below code, but i want to optimize it in a way if the array size increase the performance should not hamper :
$arr = array(2,3,6,12);
$count = 0;
function check_special_num($tnum, $tarr){
sort($tarr);
for($i=0;$i<sizeof($tarr);$i++){
if( $tnum % $tarr[$i] == 0 ){
return true;
}
}
return false;
}
for($i=0;$i<sizeof($arr);$i++){
$temp = $arr;
$num = $arr[$i];
array_splice($temp, $i, 1);
if( check_special_num( $num, $temp ) ){
$count += 1;
}
}
echo $count;
Coding language is PHP
It might be faster this way:
$arr = array(2,3,6,12);
function x_numbers_count($arr) {
$count = 0;
sort($arr);
foreach ($arr as $i => $number) {
for ($j = 0; $j < $i; $j++) {
if ($number % $arr[$j] == 0) {
$count++;
break;
}
}
}
return $count;
}
echo x_numbers_count($arr);
The main differences, which can be expected to improve performance:
the array is sorted only once, before beginning to work (this could also improve your own method, without changing anything else)
the search loop stops as soon as it is to reach the current investigated number
less variables are used

Divide an amout equally beteen no of user

I want to distribute an amount among some users equally.If amount is not divisible equally then all other member will get equal amount expect the last member who will get rest of the money.Below is what i have tried
$number = $_POST['number'];
$noOfTime = $_POST['no_of_time'];
$perHead = ceil($number / $noOfTime);
for ($i = 1; $i <= $noOfTime; $i++) {
if ($i == $noOfTime) {
echo $perHead * $noOfTime - $number;
} else {
echo $perHead;
}
}
Here if number is 7 and member are 4 the first 3 member will the 2 and the last will get 1. Like 2,2,2,1.
But this logic seems to be not working for all cases.
Please help.Thanks.
I think it should help you.
$no = 22;
$users = 8;
// count from 0 to $users number
for ($i=0;$i<$users;$i++)
// if the counting reaches the last user AND $no/$users rests other than 0...
if ($i == $users-1 && $no % $users !== 0) {
// do the math rounding fractions down with floor and add the rest!
echo floor($no / $users) + ($no % $users);
} else {
// else, just do the math and round it down.
echo floor($no / $users)." ";
}
OUTPUTS:
2 2 2 2 2 2 2 8
EDIT: I nested an if verification so the logic won't fail even if users are 1 or 2. And since it received more upvotes, I commented the code to make it more clear.

Simple PHP program requires less time to execute

i had applied for a job recently and the requirement was to complete a test and then interview
2 questions were given for test which was very simple and i did it successfully but still i was told that i have failed the test because the script took more than 18 seconds to complete execution. here is the program i dont understand what else i could do to make it fast. although i have failed the test but still wants to know else i could do?
Program language is PHP and i had to do it using command line input
here is the question:
K Difference
Given N numbers , [N<=10^5] we need to count the total pairs of numbers that have a difference of K. [K>0 and K<1e9]
Input Format:
1st line contains N & K (integers).
2nd line contains N numbers of the set. All the N numbers are assured to be distinct.
Output Format:
One integer saying the no of pairs of numbers that have a diff K.
Sample Input #00:
5 2
1 5 3 4 2
Sample Output #00:3
Sample Input #01:
10 1
363374326 364147530 61825163 1073065718 1281246024 1399469912 428047635 491595254 879792181 1069262793
Sample Output #01:
0
Note: Java/C# code should be in a class named "Solution"
Read input from STDIN and write output to STDOUT.
and this is the solution
$fr = fopen("php://stdin", "r");
$fw = fopen("php://stdout", "w");
fscanf($fr, "%d", $total_nums);
fscanf($fr, "%d", $diff);
$ary_nums = array();
for ($i = 0; $i < $total_nums; $i++) {
fscanf($fr, "%d", $ary_nums[$i]);
}
$count = 0;
sort($ary_nums);
for ($i = $total_nums - 1; $i > 0; $i--) {
for ($j = $i - 1; $j >= 0; $j--) {
if ($ary_nums[$i] - $ary_nums[$j] == $diff) {
$count++;
$j = 0;
}
}
}
fprintf($fw, "%d", $count);
Your algorithm's runtime is O(N^2) that is approximately 10^5 * 10^5 = 10^10. With some basic observation it can be reduced to O(NlgN) which is approximately 10^5*16 = 1.6*10^6 only.
Algorithm:
Sort the array ary_nums.
for every i'th integer of the array, make a binary search to find if ary_nums[i]-K, is present in the array or not. If present increase result, skip i'th integer otherwise.
sort($ary_nums);
for ($i = $total_nums - 1; $i > 0; $i--) {
$hi = $i-1;
$low = 0;
while($hi>=$low){
$mid = ($hi+$low)/2;
if($ary_nums[$mid]==$ary_nums[$i]-$diff){
$count++;
break;
}
if($ary_nums[$mid]<$ary_nums[$i]-$diff){
$low = $mid+1;
}
else{
$hi = $mid-1;
}
}
}
}
I got the same question for my technical interview. I wonder if we are interviewing for the same company. :)
Anyway, here is my answer I came up with (after the interview):
// Insert code to get the input here
$count = 0;
sort ($arr);
for ($i = 0, $max = $N - 1; $i < $max; $i++)
{
$lower_limit = $i + 1;
$upper_limit = $max;
while ($lower_limit <= $upper_limit)
{
$midpoint = ceil (($lower_limit + $upper_limit) / 2);
$diff = $arr[$midpoint] - $arr[$i];
if ($diff == $K)
{
// Found it. Increment the count and break the inner loop.
$count++;
$lower_limit = $upper_limit + 1;
}
elseif ($diff < $K)
{
// Search to the right of midpoint
$lower_limit = $midpoint + 1;
}
else
{
// Search to the left of midpoint
$upper_limit = $midpoint - 1;
}
}
}
#Fallen: Your code failed for the following inputs:
Enter the numbers N and K: 10 3
Enter numbers for the set: 1 2 3 4 5 6 7 8 9 10
Result: 6
I think it has to do with your calculation of $mid (not accounting for odd number)

How to define trends according to some values?

I am trying to to mark some trends, so I have 1 as the lowest and 5 as the biggest value.
So for example,
I may have the following case:
5,4,5,5 (UP)
3,4, (UP)
4,3,3 (DOWN)
4,4,4,4, (FLAT - this is OK for all same numbers)
I am planning to have unlimited number of ordered values as input, an as an output I will just show an (UP), (DOWN), or (FLAT) image.
Any ideas on how I can achieve this?
Sorry if I am not descriptive enough.
Thank you all for you time.
Use least square fit to calculate the "slope" of the values.
function leastSquareFit(array $values) {
$x_sum = array_sum(array_keys($values));
$y_sum = array_sum($values);
$meanX = $x_sum / count($values);
$meanY = $y_sum / count($values);
// calculate sums
$mBase = $mDivisor = 0.0;
foreach($values as $i => $value) {
$mBase += ($i - $meanX) * ($value - $meanY);
$mDivisor += ($i - $meanX) * ($i - $meanX);
}
// calculate slope
$slope = $mBase / $mDivisor;
return $slope;
} // function leastSquareFit()
$trend = leastSquareFit(array(5,4,5,5));
(Untested)
If the slope is positive, the trend is upwards; if negative, it's downwards. Use your own judgement to decide what margin (positive or negative) is considered flat.
A little bit hard to answer based on the limited info you provide, but assuming that:
if there's no movement at all the trend is FLAT,
otherwise, the trend is the last direction of movement,
then this code should work:
$input = array();
$previousValue = false;
$trend = 'FLAT';
foreach( $input as $currentValue ) {
if( $previousValue !== false ) {
if( $currentValue > $previousValue ) {
$trend = 'UP';
} elseif( $currentValue < $previousValue ) {
$trend = 'DOWN';
}
}
$previousValue = $currentValue;
}
For your examples :
Calculate longest increasing subsequence, A
Calulate longest decreasing subsequence , B
Going by your logic, if length of A is larger than B , its an UP , else DOWN.
You will also need to keep track of all equals using one boolean variable to mark FLAT trend.
Query :
What trend would be :
3,4,5,4,3 ?
3,4,4,4,3 ?
1,2,3,4,4,3,2,2,1 ?
Then the logic might need some alterations depending upon what your requirements are .
I'm not sure if i understand your problem totally but I would put the values in an array and use a code like this (written in pseudocode):
int i = 0;
String trend = "FLAT":
while(i<length(array)) {
if(array(i)<array(i+1)) {
trend = "UP";
}
else if(array(i)>array(i+1) {
trend = "DOWN";
}
i++;
}
EDIT: this would obviously only display the trend of the latest alteration
one would also may count the number of times the trend is up or down and determine the overall trend by that values
echo foo(array(5,4,5,5)); // UP
echo foo(array(3,4)); // UP
echo foo(array(4,3,3)); // DOWN
echo foo(array(4,4,4,4)); // FLAT
function foo($seq)
{
if (count(array_unique($seq)) === 1)
return 'FLAT';
$trend = NULL;
$count = count($seq);
$prev = $seq[0];
for ($i = 1; $i < $count; $i++)
{
if ($prev < $seq[$i])
{
$trend = 'UP';
}
if ($prev > $seq[$i])
{
$trend = 'DOWN';
}
$prev = $seq[$i];
}
return $trend;
}
I used the code from #liquorvicar to determine Google search page rank trends, but added some extra trend values to make it more accurate:
nochange - no change
better (higher google position = lower number)
worse (lower google position = higher number)
I also added extra checks when the last value had no change, but taking in account the previous changes i.e.
worsenochange (no change, previouse was worse - lower number)
betternochange (no change, previouse was better - lower number)
I used these values to display a range of trend icons:
$_trendIndicator="<img title="trend" width="16" src="/include/main/images/trend-'. $this->getTrend($_positions). '-icon.png">";
private function getTrend($_positions)
{
// calculate trend based on last value
//
$_previousValue = false;
$_trend = 'nochange';
foreach( $_positions as $_currentValue ) {
if( $_previousValue !== false ) {
if( $_currentValue > $_previousValue ) {
$_trend = 'better';
} elseif( $_currentValue < $_previousValue ) {
$_trend = 'worse';
}
if ($_trend==='worse' && ($_previousValue == $_currentValue)) {$_trend = 'worsenochange';}
if ($_trend==='better' && ($_previousValue == $_currentValue)) {$_trend = 'betternochange';}
}
$_previousValue = $_currentValue;
}
return $_trend;
}

Categories