I really thought it would be easier to evaluate a straight but alas I am here after a couple days trying.
I have a deck that is dealt, each player gets two cards, when it comes to evaluation I combine the player cards with the community cards and sort them highest to lowest.
The array would look like ['14d', '13d', '12d', '1d', '10d', '9d', '8d']; eg. Ace Diamonds, King Diamonds, Queen Diamonds, etc..
I've tried multiple methods such as loops and manually evaluating each card, but it needs to be evaluated with a minimum of 5 cards (2 player cards + 3 community cards) up to 7 cards (2 player cards + 5 community cards)
Here is one of my attempts:
$hand = ['14d', '13d', '12d', '11d', '10d', '9d', '8d']; //Debug hand variable
$hand_len = count($hand);
$l_card = (int)$hand[0];
$set = array();
rsort($hand, SORT_NUMERIC);
for ($i=0; $i < $hand_len; $i++) {
//Which values to compare
if ( $i+1 != $hand_len && $i+2 != $hand_len) {
$c1 = (int)$hand[$i]; //14
$c2 = (int)$hand[$i+1]; //13
if ( $c1 == $c2+1 ) { //if 14 == 13(+1)
array_push($set, $hand[$i]);
}
} elseif ($i+1 == $hand_len && $i+2 == $hand_len) {
$c1 = (int)$hand[$i]; //8
$c2 = (int)$hand[$i-1];//9
if ( $c1 == $c2-1 ) { //if 8 == 9(-1)
array_push($set, $hand[$i]);
}
}
}
//Still to add evaluation for 14 (Ace) being used for low straight (eg. 14d, 2d, 3d...).
For each card I have to convert to an integer to rid the suit character then do the calculations. I can get some level of success until it has to evaluate the final card in the set.
For example. ['14d', '13d', '12d', '11d', '10d', '9d', '8d']; <- '8d' would not be evaluated and added to the straight set.
Does anyone have suggestions how to make this operate efficiently and reliably?
Thanks!
Your approach with a "set" is good, but at some point you have to check if you have reached 5 cards. You also have to check if you hit a gap in the straight, where you must start over again. And your code doesn't work for duplicate cards like 12d and 12h.
For sake of simplicity I removed the suit from the input and work on numbers only. First you remove any duplicates and sort them like you did with array_unique() and rsort(). Then you keep a list/set of the cards which are part of the straight. Add new cards to that chain when it fits. This is the case when the last card is only different by the value 1. When the difference is greater than 1, then you know it can't be a straight anymore. At that point you have to check for a new straight from that location with the remaining cards. See the following example source code:
$cards = array(6, 9, 5, 12, 7, 8, 11);
echo "Input cards: ".implode(',', $cards)."\n";
$cards = array_unique($cards);
rsort($cards);
$set = array(array_shift($cards)); // start with the first card
foreach ($cards as $card) {
$lastCard = $set[count($set)-1];
if ($lastCard - 1 != $card) {
// not a chain anymore, "restart" from here
$set = array($card);
} else {
$set[] = $card;
}
if (count($set) == 5) {
break;
}
}
if (count($set) == 5) {
echo "Found a straight with ".implode(',', $set)."\n";
} else {
echo "No straight\n";
}
This will generate the following output:
Input cards: 6,9,5,12,7,8,11
Found a straight with 9,8,7,6,5
The loop iterates as follow over the sorted cards 12, 11, 9, 8, 7, 6, 5:
sorted cards set content
--------------------------------------
12, 11, 9, 8, 7, 6, 5 [12] <-- start set
^ [12, 11]
^ [9] <-- break, restart set from here
^ [9,8]
^ [9,8,7]
^ [9,8,7,6]
^ [9,8,7,6,5]
Related
Pair every two arrays is the task – store it, print it and repeat it until it becomes one value.
input : 1, 2, 3, 4, 5, 6, 8, 9, 9
output: 3 7 11 17 9
10 28 9
38 9
47
My code is working fine in this scenario. Somehow I managed to add 0 at the end for pairless elements. But my main focus is how can I make the logic even more clearer to avoid grumpy offset errors?.
My code:
function sumForTwos($arr)
{
if(count($arr) == 1){
exit;
}
else {
$sum = [];
for ($i = 0; $i < count($arr) -1; $i++)
{
//logic to add last array for odd count to avoid offset error
if(count($arr) % 2 == 1){ $arr[count($arr)] = 0; }
//logic to pair arrays
if($i != 0) { $i++; }
$sum = $arr[$i] + $arr[$i + 1];
$total[] = $sum;
echo $sum . " ";
}
echo "<br>";
$arr = $total;
//Recursion function
sumForTwos($arr);
}
}
sumForTwos([1, 2, 3, 4, 5, 6, 8, 9, 9]);
You can adopt an iterative approach and look at this as processing each level of values with every next level have 1 value less from total values. In other words, you can look at this as a breadth first search going level by level. Hence, you can use a queue data structure processing each level one at a time.
You can use PHP's SplQueue class to implement this. Note that we can advantage of this class as it acts as a double-ended queue with the help of below 4 operations:
enqueue - Enqueues value at the end of the queue.
dequeue - Dequeues value from the top of the queue.
push - Pushes value at the end of the doubly linked list(here, queue is implemented as doubly linked list).
pop - Pops a node from the end of the doubly linked list.
Most certainly, all the above 4 operations can be done in O(1) time.
Algorithm:
Add all array elements to queue.
We will loop till the queue size is greater than 1.
Now, if queue level size is odd, pop the last one and keep it in buffer(in a variable).
Add all pairwise elements by dequeueing 2 at a time and enqueue their addition for next level.
After level iteration, add the last element back if the previous level size was odd.
Print those added elements and echo new lines for each level accordingly.
Snippet:
<?php
function sumForTwos($arr){
if(count($arr) == 1){
echo $arr[0];
return;
}
$queue = new SplQueue();
foreach($arr as $val){
$queue->enqueue($val); // add elements to queue
}
while($queue->count() > 1){
$size = $queue->count();
$last = false;
if($size % 2 == 1){
$last = $queue->pop(); // pop the last odd element from the queue to make queue size even
$size--;
}
for($i = 0; $i < $size; $i += 2){
$first = $queue->dequeue();
$second = $queue->dequeue();
echo $first + $second," ";
$queue->enqueue($first + $second);
}
if($last !== false){// again add the last odd one out element if it exists
echo $last; // echo it too
$queue->push($last);
}
echo PHP_EOL;// new line
}
}
sumForTwos([1, 2, 3, 4, 5, 6, 8, 9, 9]);
Demo: http://sandbox.onlinephpfunctions.com/code/5b9f6d4c9291693ac7cf204af42d1f0ed852bdf9
Does this do what you want?
function pairBySums($inputArray)
{
if (sizeof($inputArray) % 2 == 1) {
$lastEntry = array_pop($inputArray); //$inputArray now has even number of elements
}
$answer = [];
for ($ii = 0; $ii < sizeof($inputArray) / 2; $ii++) {
$firstIndexOfPair = $ii * 2; // 0 maps to 0, 1 maps to 2, 3 maps to 4 etc
$secondIndexOfPair = $firstIndexOfPair + 1; // 0 maps to 1, 1 maps to 3, 2 maps to 5 etc
$answer[$ii] = $inputArray[$firstIndexOfPair] + $inputArray[$secondIndexOfPair];
}
if (isset($lastEntry)) {
array_push($answer, $lastEntry);
}
echo implode(' ', $answer) . "<br>";
if (sizeof($answer) > 1) {
pairBySums($answer);
}
}
The algorithm makes sure it operates on an even array and then appends the odd entry back on the array if there is one.
$input = [1, 2, 3, 4, 5, 6, 8, 9, 9];
pairBySums($input);
produces:
3 7 11 17 9
10 28 9
38 9
47
With an even number of items,
$input = [1, 2, 3, 4, 5, 6, 8, 9];
pairBySums($input);
produces:
3 7 11 17
10 28
38
Example Data
For this question, let's assume the following items:
Items: Apple, Banana, Carrot, Steak, Onion
Values: 2, 2, 4, 5, 3
Weights: 3, 1, 3, 4, 2
Max Weight: 7
Objective:
The MCKP is a type of Knapsack Problem with the additional constraint that "[T]he items are subdivided into k classes... and exactly one item must be taken from each class"
I have written the code to solve the 0/1 KS problem with dynamic programming using recursive calls and memoization. My question is whether it is possible to add this constraint to my current solution? Say my classes are Fruit, Vegetables, Meat (from the example), I would need to include 1 of each type. The classes could just as well be type 1, 2, 3.
Also, I think this can be solved with linear programming and a solver, but if possible, I'd like to understand the answer here.
Current Code:
<?php
$value = array(2, 2, 4, 5, 3);
$weight = array(3, 1, 3, 4, 2);
$maxWeight = 7;
$maxItems = 5;
$seen = array(array()); //2D array for memoization
$picked = array();
//Put a dummy zero at the front to make things easier later.
array_unshift($value, 0);
array_unshift($weight, 0);
//Call our Knapsack Solver and return the sum value of optimal set
$KSResult = KSTest($maxItems, $maxWeight, $value, $weight);
$maxValue = $KSResult; //copy the result so we can recreate the table
//Recreate the decision table from our memo array to determine what items were picked
//Here I am building the table backwards because I know the optimal value will be at the end
for($i=$maxItems; $i > 0; $i--) {
for($j=$maxWeight; $j > 0; $j--) {
if($seen[$i][$j] != $seen[$i-1][$j]
&& $maxValue == $seen[$i][$j]) {
array_push($picked, $i);
$maxValue -= $value[$i];
break;
}
}
}
//Print out picked items and max value
print("<pre>".print_r($picked,true)."</pre>");
echo $KSResult;
// Recursive formula to solve the KS Problem
// $n = number of items to check
// $c = total capacity of bag
function KSTest($n, $c, &$value, &$weight) {
global $seen;
if(isset($seen[$n][$c])) {
//We've seen this subproblem before
return $seen[$n][$c];
}
if($n === 0 || $c === 0){
//No more items to check or no more capacity
$result = 0;
}
elseif($weight[$n] > $c) {
//This item is too heavy, check next item without this one
$result = KSTest($n-1, $c, $value, $weight);
}
else {
//Take the higher result of keeping or not keeping the item
$tempVal1 = KSTest($n-1, $c, $value, $weight);
$tempVal2 = $value[$n] + KSTest($n-1, $c-$weight[$n], $value, $weight);
if($tempVal2 >= $tempVal1) {
$result = $tempVal2;
//some conditions could go here? otherwise use max()
}
else {
$result = $tempVal1;
}
}
//memo the results and return
$seen[$n][$c] = $result;
return $result;
}
?>
What I've Tried:
My first thought was to add a class (k) array, sort the items via class (k), and when we choose to select an item that is the same as the next item, check if it's better to keep the current item or the item without the next item. Seemed promising, but fell apart after a couple of items being checked. Something like this:
$tempVal3 = $value[$n] + KSTest($n-2, $c-$weight[$n]);
max( $tempVal2, $tempVal3);
Another thought is that at the function call, I could call a loop for each class type and solve the KS with only 1 item at a time of that type + the rest of the values. This will definitely be making some assumptions thought because the results of set 1 might still be assuming multiples of set 2, for example.
This looks to be the equation (If you are good at reading all those symbols?) :) and a C++ implementation? but I can't really see where the class constraint is happening?
The c++ implementation looks ok.
Your values and weights which are 1 dimensional array in your current PHP implementation will become 2 dimensional.
So for example,
values[i][j] will be value of j th item in class i. Similarly in case of weights[i][j]. You will be taking only one item for each class i and move forward while maximizing the condition.
The c++ implementation also does an optimization in memo. It only keeps 2 arrays of size respecting the max_weight condition, which are current and previous states. This is because you only need these 2 states at a time to compute present state.
Answers to your doubts:
1)
My first thought was to add a class (k) array, sort the items via
class (k), and when we choose to select an item that is the same as
the next item, check if it's better to keep the current item or the
item without the next item. Seemed promising, but fell apart after a
couple of items being checked. Something like this: $tempVal3 =
$value[$n] + KSTest($n-2, $c-$weight[$n]); max( $tempVal2, $tempVal3);
This won't work because there could be some item in class k+1 where you take a optimal value and to respect constraint you need to take a suboptimal value for class k. So sorting and picking the best won't work when the constraint is hit. If the constraint is not hit you can always pick the best value with best weight.
2)
Another thought is that at the function call, I could call a loop for
each class type and solve the KS with only 1 item at a time of that
type + the rest of the values.
Yes you are on the right track here. You will assume that you had already solved for first k classes. Now you will try extending using the values of k+1 class respecting the weight constraint.
3)
... but I can't really see where the class constraint is happening?
for (int i = 1; i < weight.size(); ++i) {
fill(current.begin(), current.end(), -1);
for (int j = 0; j < weight[i].size(); ++j) {
for (int k = weight[i][j]; k <= max_weight; ++k) {
if (last[k - weight[i][j]] > 0)
current[k] = max(current[k],
last[k - weight[i][j]] + value[i][j]);
}
}
swap(current, last);
}
In the above c++ snippet, the first loop iterates on class, the second loop iterates on values of class and the third loop extends the current state current using the previous state last and only 1 item j with class i at a time. Since you are only using previous state last and 1 item of the current class to extend and maximize, you are following the constraint.
Time complexity:
O( total_items x max_weight) which is equivalent to O( class x max_number_of_items_in_a_class x max_weight)
So I am not a php programmer but I will try to write a pseudocode with good explanation.
In the original problem each cell i, j meaning was: "Value of filling the knapsack with items 1 to i until it reach capacity j", the solution in the link you have provided defines each cell as "Value of filling the knapsack with items from buckets 1 to i until it reach capacity j". Notice that in this variation there is not such this as not taking an element from a class.
So on each step (each call for KSTest with $n, $c), we need to find which element to pick from the n'th class such that the weight of this element is less than c and it's value + KSTest(n - 1, c - w) is the greatest.
So I think you should only change the else if and else statements to something like:
else {
$result = 0
for($i=0; $i < $number_of_items_in_nth_class; $i++) {
if ($weight[$n][$i] > $c) {
//This item is too heavy, check next item
continue;
}
$result = max($result, KSTest($n-1, $c - $weight[$n][$i], $value, $weight));
}
}
Now two disclaimers:
I do not code in php so this code will not run :)
This is not the implementation given in the link you provided, TBH I didn't understood why the time complexity of their algorithm is so small (and what is C) but this implementation should work since it is following the definition of the recursive formula given.
The time complexity of this should be O(max_weight * number_of_classes * size_of_largerst_class).
This is my PHP solution. I've tried to comment the code in a way that it's easy to follow.
Update:
I updated the code because the old script was giving unreliable results. This is cleaner and has been thoroughly tested. Key takeaways are that I use two memo arrays, one at the group level to speed up execution and one at the item level to reconstruct the results. I found any attempts to track which items are being chosen as you go are unreliable and much less efficient. Also, isset() instead of if($var) is essential for checking the memo array because the previous results might have been 0 ;)
<?php
/**
* Multiple Choice Knapsack Solver
*
* #author Michael Cruz
* #version 1.0 - 03/27/2020
**/
class KS_Solve {
public $KS_Items;
public $maxValue;
public $maxWeight;
public $maxItems;
public $finalValue;
public $finalWeight;
public $finalItems;
public $finalGroups;
public $memo1 = array(); //Group memo
public $memo2 = array(); //Item memo for results rebuild
public function __construct() {
//some default variables as an example.
//KS_Items = array(Value, Weight, Group, Item #)
$this->KS_Items = array(
array(2, 3, 1, 1),
array(2, 1, 1, 2),
array(4, 3, 2, 3),
array(5, 4, 2, 4),
array(3, 2, 3, 5)
);
$this->maxWeight = 7;
$this->maxItems = 5;
$this->KS_Wrapper();
}
public function KS_Wrapper() {
$start_time = microtime(true);
//Put a dummy zero at the front to make things easier later.
array_unshift($this->KS_Items, array(0, 0, 0, 0));
//Call our Knapsack Solver
$this->maxValue = $this->KS_Solver($this->maxItems, $this->maxWeight);
//Recreate the decision table from our memo array to determine what items were picked
//ksort($this->memo2); //for debug
for($i=$this->maxItems; $i > 0; $i--) {
//ksort($this->memo2[$i]); //for debug
for($j=$this->maxWeight; $j > 0; $j--) {
if($this->maxValue == 0) {
break 2;
}
if($this->memo2[$i][$j] == $this->maxValue
&& $j == $this->maxWeight) {
$this->maxValue -= $this->KS_Items[$i][0];
$this->maxWeight -= $this->KS_Items[$i][1];
$this->finalValue += $this->KS_Items[$i][0];
$this->finalWeight += $this->KS_Items[$i][1];
$this->finalItems .= " " . $this->KS_Items[$i][3];
$this->finalGroups .= " " . $this->KS_Items[$i][2];
break;
}
}
}
//Print out the picked items and value. (IMPLEMENT Proper View or Return!)
echo "<pre>";
echo "RESULTS: <br>";
echo "Value: " . $this->finalValue . "<br>";
echo "Weight: " . $this->finalWeight . "<br>";
echo "Item's in KS:" . $this->finalItems . "<br>";
echo "Selected Groups:" . $this->finalGroups . "<br><br>";
$end_time = microtime(true);
$execution_time = ($end_time - $start_time);
echo "Results took " . sprintf('%f', $execution_time) . " seconds to execute<br>";
}
/**
* Recursive function to solve the MCKS Problem
* $n = number of items to check
* $c = total capacity of KS
**/
public function KS_Solver($n, $c) {
$group = $this->KS_Items[$n][2];
$groupItems = array();
$count = 0;
$result = 0;
$bestVal = 0;
if(isset($this->memo1[$group][$c])) {
$result = $this->memo1[$group][$c];
}
else {
//Sort out the items for this group
foreach($this->KS_Items as $item) {
if($item[2] == $group) {
$groupItems[] = $item;
$count++;
}
}
//$k adjusts the index for item memoization
$k = $count - 1;
//Find the results of each item + items of other groups
foreach($groupItems as $item) {
if($item[1] > $c) {
//too heavy
$result = 0;
}
elseif($item[1] >= $c && $group != 1) {
//too heavy for next group
$result = 0;
}
elseif($group == 1) {
//Just take the highest value
$result = $item[0];
}
else {
//check this item with following groups
$result = $item[0] + $this->KS_Solver($n - $count, $c - $item[1]);
}
if($result == $item[0] && $group != 1) {
//No solution with the following sets, so don't use this item.
$result = 0;
}
if($result > $bestVal) {
//Best item so far
$bestVal = $result;
}
//memo the results
$this->memo2[$n-$k][$c] = $result;
$k--;
}
$result = $bestVal;
}
//memo and return
$this->memo1[$group][$c] = $result;
return $result;
}
}
new KS_Solve();
?>
Now I know the basic logic behind finding a straight, and I assume that would include a pseudo of
function is_straight(array $cards) {
sort($cards);
if(($cards[4] - $cards[0]) == 5) {
//Code to make sure the cards in between are increment
//is straight.
}
}
would theoretically work for a 5 card check.
But how would one go for eliminating cards from the array of 7 cards to find a straight?
Would I have to individually check all 5 hand combinations within the 7 cards array?
so eliminate two cards from the $cards array and check that combination for a straight?
So I'm a little stuck on the logical side of this, rather than the code side.
In pseudo code
#filter doubles
cards = array_unique(cards)
sort(cards)
foreach cards as key, value:
if not key_exists(cards, key+4):
return false
if cards[key+4] == value + 4:
return true
longer potentially more explicit version
#filter doubles
cards = array_unique(cards)
sort(cards)
straight_counter = 1
foreach cards as key, value:
if not key_exists(cards, key+1):
return false
# is the following card an increment to the current one
if cards[key+1] == value + 1:
straight_counter++
else:
straight_counter = 1
if straight_counter == 5:
return true
function is_straight(array $array) {
$alpha = array_keys(array_count_values($array));
sort($alpha);
if (count($alpha) > 4) {
if (($alpha[4] - $alpha[0]) == 4) {
$result = $alpha[4];
return $result;
}
if (count($alpha) > 5) {
if (($alpha[5] - $alpha[1]) == 4) {
$result = $alpha[5];
return $result;
}
}
if (count($alpha) > 6) {
if (($alpha[6] - $alpha[2]) == 4) {
$result = $alpha[6];
return $result;
}
}
}
}
Assuming that $cards is an array containing cards values from 1 to 13, I think you need to evaluate with a difference of 4, not 5 :
5 - 1 = 4
6 - 2 = 4
7 - 3 = 4
etc.
You also need to add a specific logic for 10, J, Q, K, A
But for your specific question, what about :
function is_straight(array $cards) {
sort($cards);
if((($cards[4] - $cards[0]) == 4) ||
(($cards[5] - $cards[1]) == 4) ||
(($cards[6] - $cards[2]) == 4)) {
//Code to make sure the cards in between are increment
//is straight.
}
}
I'm working on a very small blackjack game in PHP.
I'm currently writing the function to count the cards, but the aces are kicking my butt.
My cards are all in an array like this:
$card_array = array( "ca", "c10", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9",
"cj", "ck", "cq", "da", "d10", "d2", "d3", "d4", "d5", "d6", "d7", "d8",
"d9", "dj", "dk", "dq", "ha", "h2", "h3", "h4", "h5", "h6", "h7", "h8", "h9",
"hj", "hk", "hq", "sa", "s2", "s3", "s4", "s5", "s6", "s7", "s8", "s9",
"s10", "sj", "sk", "sq");`
Where the first character is the suit and everything after that is the card (j for jack, etc)
Here's what I have for counting the values up so far:
function bj_calculate_values( $cards ) {
$card_values = 0;
foreach ($cards as $card) {
if (substr($card, 1) == "k" || substr($card, 1) == "q" || substr($card, 1) == "j") {
$card_values += 10;
}
else if (substr($card, 1) != "a") {
$card_values += substr($card, 1);
}
}
}
Originally, I also had the ace in there valued at 11, but obviously that would cause problems. I feel like I need to keep relooping to make sure the aces don't put us over 21, but I'm not entirely sure of the best way to go about doing that.
I'd appreciate some input, guys.
EDIT:
The best I can think of is adding this to the function
foreach ($cards as $card) {
if (substr($card, 1) == "a") {
if ($card_values + 11 <= 21) {
$card_values += 11;
}
else {
$card_values += 1;
}
}
}
Actually I think this might work. Give me a few minutes to test.
EDIT:
Nope, didn't work. 4, ace, ace, 6 came out to 22 with this.
I do not know much about BlackJack, but this is my try.
function bj_calculate_values( $cards ) {
$card_values = 0;
$ace = 0;
foreach ($cards as $card) {
$val = substr($card, 1);
if (!is_numeric($val))
{
if ('a' == $val)
{
$val = 0;
$ace ++;
}
else
$val = 10;
}
$card_values += $val;
}
while ($ace)
$card_values += ($card_values + 11*$ace-- <= 21) ? 11: 1;
return $card_values;
}
UPDATE
If I understand your other comment, you want 6 4 A A A to come out 13, 6 4 A A to come out 12, and 6 4 A to come out 21. So the number of remaining aces count. Modified the source accordingly.
Explanation
First of all we calculate the value of non-ace cards. These are constant, so we get it over with. And this total is the (preliminary) $card_values.
Then we may have (or not) some aces. It makes sense to have a while($aces) so that if we have no aces, we do nothing.
And now the question is, what do we do with those aces? Here is where your requisite comes in: the sum must fit into 21. This means that I can't just say "I'm at 10, I have an ace, so it fits into 21" because there might be another ace after that. So I have to calculate what would the worst case be if I were to add in all remaining aces; and that's of course the number of aces, times 11. While the worst case is still good (i.e., $card_values + 11*$ace is less than 21) I can add 11. While it is not, I have to add 1.
It is a form of "greedy" algorithm: I fit in all the 1's we can, while ensuring enough 11's are left to reach the closest approach to 21 that does not exceed.
I seem to remember (I might be wrong, heh) that there was a nasty bug in this implementation which will come out whenever NumberOfItems * LowestValue >= HighestValue (I'm not too sure about that "equals" though). But in this case, it would require NumberOfItems to be above 11/1 = 11 aces, and there are not so many aces in the deck, so we're cool. And to be sure, the possible cases are five - from "no" to "four" aces in a hand - and easily tested for all sums of other cards:
// J Q K are worth 10, so we can use 10 instead.
// And a fake card with value of 0 stands for "nothing".
// We use the suit of Klingon, with up to four identical aces :-D
for ($first = 0; $first <= 21; $first++)
{
$hand = array("k$first");
for ($aces = 1; $aces < 5; $aces++)
{
$hand[] = "ka";
$value = bj_calculate_values($hand);
// Let us ponder the possible values of $value.
if ($value <= 11)
{
// This is an error: we could have gotten closer to 21
// by upvaluing an ace. A correct implementation will
// never enter here except in the case of a LONE ACE.
if (($first != 0) || ($aces != 1))
print "ERROR: " . implode(" ", $hand) . " = $value\n";
}
// We have a value from 12 to 21.
// Could we have done better? What cards do we have?
// If no aces, of course no. All card values are then fixed.
// If at least one ace, interpreted as 11, again not:
// cannot interpret it above 11, and interpret as 1 would LESSEN the score.
// If at least one ace, interpreted as 1, were we to interpret it
// as 11, we would be summing 10. And 12 would become 22, and blow.
// So, from 12 to 21, the result MUST be correct.
// Finally, $value more than 21.
if ($value > 21)
{
// This is admissible ONLY if we could have done no better, which
// means $value is given by the fixed cards' value, $first, plus
// ALL ACES COUNTED AS ONES.
if ($value != ($first + $aces))
print "ERROR: " . implode(" ", $hand) . " = $value\n";
}
}
}
The output (version 1)
VERIFY: k0 ka = 11 Correct, we had a lone ace and gave it 11.
VERIFY: k18 ka ka ka ka = 22 Correct, we had 18 in other cards and all A's to 1's.
VERIFY: k19 ka ka ka = 22 Ditto, 19 in cards.
VERIFY: k19 ka ka ka ka = 23 Ditto, 19 in cards.
... ...
Current output (added code to only print errors):
-- nothing :-)
I am building a list of "agent id's" in my database with the following requirements:
The ID must be 9 digits long (numeric only)
The ID may not contain more than 3 of the same number.
The ID may not contain more than 2 of the same number consecutively (i.e. 887766551; cannot have 888..)
So far I have part 1 down solid but am struggling with 2 and 3 above. My code is below.
function createRandomAGTNO() {
srand ((double) microtime( )*1000000);
$random_agtno = rand(100000000,900000000);
return $random_agtno;
}
// Usage
$NEWAGTNO = createRandomAGTNO();
Any ideas?
Do not re-seed the RNG on every call like that, unless you want to completely blow the security of your random numbers.
Unless your PHP is very old, you probably don't need to re-seed the RNG at all, as PHP seeds it for you on startup and there are very few cases where you need to replace the seed with one of your own choosing.
If it's available to you, use mt_rand instead of rand. My example will use mt_rand.
As for the rest -- you could possibly come up with a very clever mapping of numbers from a linear range onto numbers of the form you want, but let's brute-force it instead. This is one of those things where yes, the theoretical upper bound on running time is infinite, but the expected running time is bounded and quite small, so don't worry too hard.
function createRandomAGTNO() {
do {
$agt_no = mt_rand(100000000,900000000);
$valid = true;
if (preg_match('/(\d)\1\1/', $agt_no))
$valid = false; // Same digit three times consecutively
elseif (preg_match('/(\d).*?\1.*?\1.*?\1/', $agt_no))
$valid = false; // Same digit four times in string
} while ($valid === false);
return $agt_no;
}
For second condition, you can create an array like this
$a = array( 0,0,1,1,2,2,3,3.....,9,9 );
and get random elements: array_rand() (see manual) to get digit, append it to your ID and remove value from source array by unsetting at index.
Generally, this solving also third condition, but this solution excludes all ID's with possible and acceptable three digits
The first solution that comes to mind is a recursive function that simply tests your three requirements and restarts if any three of them fail. Not the most efficient solution but it would work. I wrote an untested version of this below. May not run without errors but you should get the basic idea from it.
function createRandomAGTNO(){
srand ((double) microtime( )*1000000);
$random_agtno = rand(100000000,900000000);
$random_agtno_array = explode('', $random_agtno);
foreach($random_agtno_array as $raa_index => $raa){
if($raa == $random_agtno_array[$raa_index + 1] && raa == $random_agtno_array[$raa_index + 2]) createRandomAGTNO();
$dup_match = array_search($raa, $random_agtno_array);
if($dup_match){
unset($random_agtno_array[$dup_match]);
if(array_search($raa, $random_agtno_array)) createRandomAGTNO();
};
}
return $random_agtno;
}
Try this code:
<?php
function createRandomAGTNO() {
//srand ((double) microtime( )*1000000);
$digits = array( 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 ,1, 2, 3, 4, 5, 6, 7, 8, 9, 0 );
shuffle($digits);
$random_agtno = 0;
for($i = 0; $i < 9; $i++)
{
if($i == 0)
{
while($digits[0] == 0)
shuffle($digits);
}
/*if($i >= 2)
{
while(($random_agtno % 100) == $digits[0])
shuffle($digits);
}*/
$random_agtno *= 10;
$random_agtno += $digits[0];
array_splice($digits, 0, 1);
}
return $random_agtno;
}
for($i = 0; $i < 1000; $i++)
{
$NEWAGTNO = createRandomAGTNO();
echo "<p>";
echo $NEWAGTNO;
echo "</p>";
}
?>
Good luck!
Edit:
Removed the call to srand() and commented-out the "if($i >= 2)" code, which is impossible anyway, here.