Creating a Banner Swapping Algorithm to Rotate Ads - php

I'm working on building an ad banner rotation script based on impressions that displays ads evenly throughout the month. The calculations will be done each time the ad is requested to be displayed. So this will be done on the fly. The ads should appear to rotate through, one after another, and not just display one ad for 1000 impressions, then the other ad for 1000 impressions. It for the most part should display for 1 impression, then switch ads (unless of course one ad has a lot more impressions than the other to use up).
Let's say I have 5 ads and each has a different number of impressions that were purchased, what would be the formula/how do you serve up the ads? I'm looking to do this in PHP.
Ad #1: 1,000 purchased impressions
Ad #2: 12,000 purchased impressions
Ad #3: 3,000 purchased impressions
Ad #4: 20,000 purchased impressions
Ad #5: 10,000 purchased impressions
if there are multiple ads that bought 1000 impressions all for the same time frame, it should show one after another until the impressions are used. Though, I think it might be good that if a person bought 1000 impressions for a short time frame, I should account for that and show them at a faster rate. I'm open to suggestions.

I think you should use the best type of algorithm for the best JOB i'll only show you some few possibility on how to implement such
My current example would show using
shuffle
fisherYatesShuffle
robinShuffle
ratioShuffle
You can also implement
Priory Based shuffle
Time Base Shuffle
Percentage
Click Shuffle
etc
Simple Prove of Concept
// Create Add Infroamtion
$ads = array();
$ads[] = new Ad(10, "A.jpg", 2);
$ads[] = new Ad(12, "B.gif", 3);
$ads[] = new Ad(30, "C.png", 7);
$ads[] = new Ad(20, "D.swf", 5);
// Add ads to banner
$banner = new Banner($ads);
// You can also add addional ads
$banner->add(new Ad(10, "E.swf"));
echo "<pre>";
//Lets Emulate first 100 rotations
for($i = 0; $i < 1000; $i ++) {
// Select Algorithm
$banner->randomise("ratioShuffle");
// Display Add
echo $banner->getDisplay(), PHP_EOL;
}
Simple Shuffle Function that can be used
function fisherYatesShuffle(array &$items) {
for($i = count($items) - 1; $i > 0; $i --) {
$j = #mt_rand(0, $i);
$tmp = $items[$i];
$items[$i] = $items[$j];
$items[$j] = $tmp;
}
}
function robinShuffle(array &$items) {
usort($items, function ($a, $b) {
$a = $a->getDisplay();
$b = $b->getDisplay();
return $a == $b ? 0 : ($a < $b ? - 1 : 1);
});
}
function ratioShuffle(array &$items) {
static $called = false;
if ($called === false) {
$ads = array();
foreach ( $items as &$ad ) {
for($i = 0; $i < $ad->getRatio(); $i ++) {
$ads[] = $ad;
}
}
$called = true;
$items = $ads;
}
shuffle($items);
}
Classes Used
class Ad implements JsonSerializable {
private $impressions;
private $media;
private $ratio = 1;
private $display = 0;
function __construct($impressions, $media = null, $ratio = 1) {
$this->impressions = $impressions;
$this->media = $media;
$this->ratio = $ratio;
}
function torch() {
$this->impressions --;
$this->display ++;
}
public function getImpression() {
return $this->impressions;
}
public function getDisplay() {
return $this->display;
}
public function getRatio() {
return $this->ratio;
}
public function getMeadia() {
return $this->media;
}
public function __toString() {
return json_encode($this->jsonSerialize());
}
public function jsonSerialize() {
return get_object_vars($this);
}
}
class Banner implements Countable, JsonSerializable {
private $totalImpressions;
private $ads = array();
function __construct(array $ads) {
foreach ( $ads as $ad )
$this->add($ad);
}
public function add(Ad $ad) {
$this->ads[] = $ad;
$this->totalImpressions += $ad->getImpression();
}
public function randomise($function = null) {
if (is_callable($function, false, $callable_name)) {
return $callable_name($this->ads);
} else {
return shuffle($this->ads);
}
}
public function getDisplay() {
foreach ( $this->ads as &$ad ) {
if ($ad->getImpression() < 1) {
unset($ad);
continue;
}
$ad->torch();
break;
}
return isset($ad) ? $ad : null;
}
public function jsonSerialize() {
$array = $this->ads;
foreach ( $array as &$ad ) {
$ad = $ad->jsonSerialize();
}
return $array;
}
public function __toString() {
return json_encode($this->jsonSerialize());
}
function count() {
return count($this->ads);
}
}
As you can see this is an example .... Just try and make your solution flexible

Personally, I'd work out what percentage of impressions each ad has received compared to how many are paid for, and use that as the chance that it won't show up. Something like this:
$show = Array();
foreach($ads as $id=>$ad) {
$show[$id] = ceil((1-$ad['impressions']/$ad['paid'])*100);
}
$total = array_sum($show);
$rand = rand(1,$total);
$winner = -1;
do {$rand -= array_shift($show); $winner++;} while($rand && $show);
$ad_to_display = $ads[$winner];
For example, consider four ads, A, B, C and D. All of them have paid for 1,000 impressions, but so far A has been unlucky and gotten zero, while B and C both have had 500 impressions, and D has had 999.
This would mean $show has these values for the ads:
A: ceil((1-0/1000)*100) = 100
B: ceil((1-500/1000)*100) = 50
C: ceil((1-500/1000)*100) = 50
D: ceil((1-999/1000)*100) = 1
$total is therefore equal to 201.
$rand can be any number from 1 to 201 inclusive. Let's say 141.
In this case, we begin our loop:
$rand -= 100, now it's 41. 41 is truthy and we have ads remaining.
$rand -= 50, now it's -9. It has reached zero, so end the loop.
The $winner is 1, which is advert B.

Related

PHP Generate random number without repetition without loop for mt_rand and without using range() and shuffle()

I am looking for a way to generate a specific quantity of unique random numbers (say 10,000), within a specific range of numbers like (000000000,999999999).
I'd like to do this without repeatedly using rand() or mt_rand() under a for loop as this would be computationally inefficient.
Are there any PHP libraries, or solutions which meets these requirements?
One method is to use a Format Preserving Encryption, with the output limited to the range 0 to 999999999. If you encrypt the numbers 0 to 9,999 you will get 10,000 unique outputs in the required range. With an encryption, unique inputs guarantee unique outputs as long as you don't change the key.
1) Create a class that keeps state of generation:
class Randomizer {
private $min;
private $max;
private $maxGeneration;
public function __construct($min = 0, $max = 100) {
if ($min >= $max) {
throw new Exception('Minimal value is more than or equal to Max value');
}
if ($max - $min < 3) {
throw new Exception('Nothing to randomize');
}
$this->min = $min;
$this->max = $max;
$this->maxGeneration = $max - $min - 1;
}
public function pick($quantity = 1) {
$count = 0;
$generated = [];
while($count < $quantity) {
$num = $this->generate();
if (sizeof($generated) === $this->maxGeneration) {
break;
}
if (!in_array($num, $generated)) {
$generated[] = $num;
$count++;
}
}
return ($quantity === 1) ? $generated[0] : $generated;
}
public function generate() {
return mt_rand($this->min, $this->max);
}
}
2) Use it:
$randomizer = new Randomizer(0, 999999999);
$number = $randomizer->pick(); // returns 1 number
$numbers = $randomizer->pick(100); // returns array(A, B, C...) of numbers

Choosing coins with least or no change given

I am making a game which consists of coin denominations of $10, $5, $3, and $1. The player may have 0 or more of each type of currency in his inventory with a maximum of 15 coins in total. I am trying to figure out how to properly select coins so that the least amount of change is given in return. At first I thought this was going to be easy to solve, but now I'm having trouble wrapping my head around it.
Here are two examples that explain the situation further:
Example 1:
The user is carrying these coins: $5, $3, $3, $3, $1, $1, $1, $1 and want to buy an item for $12. The solution would be to pay with $5, $3, $3, $1 and give no change.
Example 2:
The user does not have any $1 coins, and is carrying $5, $3, $3, $3, $3. An item is bought for $12 so they pay with $5, $3, $3, and $3 and change of $2 is given back.
Since we select the larger coins first, what I can't figure out is how to know if there are enough lower valued coins ($1 in this case) in the player's inventory to accommodate example 1, and if there aren't enough to use more of the higher valued coins as in example 2.
A further issue is seen in the following example, though I'd be happy just getting the above two examples working:
Example 3:
The user is carrying these coins: $5, $3, $3, $3. The player buys something for $6. It would be better to use $3 and $3 and return no change rather than using $5 and $3 and give $2 in change.
I believe the first two examples can be solved using recursion and a variation of the greedy algorithm.
For the bounty award:
I have added my own answer below as a temporary solution for now. However, I like the approach of Mr. Llama's below (see the link he references) and would like to find a PHP example to satisfy this. I believe this approach does not need recursion and uses memoization.
If there are multiple options for the least amount of change then I would like the tie to be given to the one that pays with the least amount of coins.
The problem can be defined as:
Return a subset of items where the sum is closest to x, but >= x.
This problem is called the subset sum problem. It is NP-complete. You won't find a perfect algorithm that runs in pseudo-polynomial time, only imperfect heuristics.
However, if the number of coins is very small, then an exhaustive search of the solution space will certainly work.
If the number of coins is larger, then you should look at Wikipedia for an overview: https://en.wikipedia.org/wiki/Subset_sum_problem#Polynomial_time_approximate_algorithm
I had a similar problem except instead of being allowed to go over, the combination had to stay under the target amount. In the end, I used the dynamic approach presented in this answer. You should be able to use it too.
It goes something like this:
Start with a list consisting of a single empty element.
For each entry in the list...
Copy the entry and add to it the first coin (not coin value!) that it doesn't contain.
Store the new element in the original list if and only if* its new sum value doesn't already exist in the list.
Repeat step 2 until you make a pass where no new elements are added to the list
Iterate the result list and keep the best combination (using your criteria)
*: We can make this optimization because we don't particularly care which coins are used in the combination, only the sum value of the collection of coins.
The above algorithm can be optimized a bit if you use the sum value as the key.
I have come up with the following solution. If others can critique it for me I would appreciate it.
<?php
$coin_value = array(10,5,3,1);
$inventory = array(1,2,0,2);
$price = 17;
for ($i = 3; $i >= 0; $i--){
$btotal = 0;
$barray = array();
for ($j = 0; $j < 4; $j++){
$remaining = $price - $btotal;
$to_add = floor($remaining / $coin_value[$j]);
if ($i != 3 && $i == $j){
$to_add++;
}
if ($inventory[$j] < $to_add){
$to_add = $inventory[$j];
}
$btotal += $to_add * $coin_value[$j];
for ($k = 0; $k < $to_add; $k++){
$barray[] = $coin_value[$j];
}
if ($btotal >= $price)
break 2; //warning: breaks out of outer loop
}
}
$change_due = $btotal - $price;
print_r($barray);
echo "Change due: \$$change_due\n";
?>
It covers examples 1 and 2 in my original question, but does not cover example 3. However, I think it will do for now unless someone can come up with a better solution. I decided not to use recursion as it would seem to take too much time.
You can use a stack to enumerate valid combinations. The version below uses a small optimization, calculating if a minimum of the current denomination is needed. More than one least change combinations are returned if there are any, which could be restricted with memoization; one could also add an early exit if the current denomination could complete the combination with zero change. I hope the laconically commented code is self-explanatory (let me know if you'd like further explanation):
function leastChange($coin_value,$inventory,$price){
$n = count($inventory);
$have = 0;
for ($i=0; $i<$n; $i++){
$have += $inventory[$i] * $coin_value[$i];
}
$stack = [[0,$price,$have,[]]];
$best = [-max($coin_value),[]];
while (!empty($stack)){
// each stack call traverses a different set of parameters
$parameters = array_pop($stack);
$i = $parameters[0];
$owed = $parameters[1];
$have = $parameters[2];
$result = $parameters[3];
// base case
if ($owed <= 0){
if ($owed > $best[0]){
$best = [$owed,$result];
} else if ($owed == $best[0]){
// here you can add a test for a smaller number of coins
$best[] = $result;
}
continue;
}
// skip if we have none of this coin
if ($inventory[$i] == 0){
$result[] = 0;
$stack[] = [$i + 1,$owed,$have,$result];
continue;
}
// minimum needed of this coin
$need = $owed - $have + $inventory[$i] * $coin_value[$i];
if ($need < 0){
$min = 0;
} else {
$min = ceil($need / $coin_value[$i]);
}
// add to stack
for ($j=$min; $j<=$inventory[$i]; $j++){
$stack[] = [$i + 1,$owed - $j * $coin_value[$i],$have - $inventory[$i] * $coin_value[$i],array_merge($result,[$j])];
if ($owed - $j * $coin_value[$i] < 0){
break;
}
}
}
return $best;
}
Output:
$coin_value = [10,5,3,1];
$inventory = [0,1,3,4];
$price = 12;
echo json_encode(leastChange($coin_value,$inventory,$price)); // [0,[0,1,2,1],[0,1,1,4],[0,0,3,3]]
$coin_value = [10,5,3,1];
$inventory = [0,1,4,0];
$price = 12;
echo json_encode(leastChange($coin_value,$inventory,$price)); // [0,[0,0,4]]
$coin_value = [10,5,3,1];
$inventory = [0,1,3,0];
$price = 6;
echo json_encode(leastChange($coin_value,$inventory,$price)); // [0,[0,0,2]]
$coin_value = [10,5,3,1];
$inventory = [0,1,3,0];
$price = 7;
echo json_encode(leastChange($coin_value,$inventory,$price)); // [-1,[0,1,1]]
Update:
Since you are also interested in the lowest number of coins, I think memoization could only work if we can guarantee that a better possibility won't be skipped. I think this can be done if we conduct our depth-first-search using the most large coins we can first. If we already achieved the same sum using larger coins, there's no point in continuing the current thread. Make sure the input inventory is presenting coins sorted in descending order of denomination size and add/change the following:
// maximum needed of this coin
$max = min($inventory[$i],ceil($owed / $inventory[$i]));
// add to stack
for ($j=$max; $j>=$min; $j--){
The solution I was able to made covers the 3 examples posted in your question. And always gives the change with as few coins as possible.
The tests I made seemed to be executed very fast.
Here I post the code:
<?php
//Example values
$coin_value = array(10,5,3,1);
$inventory = array(5,4,3,0);
$price = 29;
//Initialize counters
$btotal = 0;
$barray = array(0,0,0,0);
//Get the sum of coins
$total_coins = array_sum($inventory);
function check_availability($i) {
global $inventory, $barray;
$a = $inventory[$i];
$b = $barray[$i];
$the_diff = $a - $b;
return $the_diff != 0;
}
/*
* Checks the lower currency available
* Returns index for arrays, or -1 if none available
*/
function check_lower_available() {
for ($i = 3; $i >= 0; $i--) {
if (check_availability($i)) {
return $i;
}
}
return -1;
}
for($i=0;$i<4;$i++) {
while(check_availability($i) && ($btotal + $coin_value[$i]) <= $price) {
$btotal += $coin_value[$i];
$barray[$i]++;
}
}
if($price != $btotal) {
$buf = check_lower_available();
for ($i = $buf; $i >= 0; $i--) {
if (check_availability($i) && ($btotal + $coin_value[$i]) > $price) {
$btotal += $coin_value[$i];
$barray[$i]++;
break;
}
}
}
// Time to pay
$bchange = 0;
$barray_change = array(0,0,0,0);
if ($price > $btotal) {
echo "You have not enough money.";
}
else {
$pay_msg = "You paid $".$btotal."\n\n";
$pay_msg.= "You used ".$barray[0]." coins of $10\n";
$pay_msg.= "You used ".$barray[1]." coins of $5\n";
$pay_msg.= "You used ".$barray[2]." coins of $3\n";
$pay_msg.= "You used ".$barray[3]." coins of $1\n\n\n";
// Time to give change
$the_diff = $btotal - $price;
if (!empty($the_diff)) {
for ($i = 0; $i < 4; $i++) {
while($the_diff >= $coin_value[$i]) {
$bchange += $coin_value[$i];
$barray_change[$i]++;
$the_diff -= $coin_value[$i];
}
}
$check_sum = array_sum($inventory) - array_sum($barray);
$check_sum+= array_sum($barray_change);
$msg = "";
if ($check_sum < 15) {
$change_msg = "Your change: $".$bchange."\n\n";
$change_msg.= "You received ".$barray_change[0]." coins of $10\n";
$change_msg.= "You received ".$barray_change[1]." coins of $5\n";
$change_msg.= "You received ".$barray_change[2]." coins of $3\n";
$change_msg.= "You received ".$barray_change[3]." coins of $1\n\n";
$msg = $pay_msg.$change_msg;
}
else {
$msg = "You have not enough space to hold the change.\n";
$msg.= "Buy cancelled.\n";
}
}
else {
$msg = $pay_msg."You do not need change\n";
}
if ($check_sum < 15) {
for ($i = 0; $i < 4; $i++) {
$inventory[$i] -= $barray[$i];
$total_coins-= $barray[$i];
}
for ($i = 0; $i < 4; $i++) {
$inventory[$i] += $barray_change[$i];
$total_coins+= $barray[$i];
}
}
echo $msg;
echo "Now you have:\n";
echo $inventory[0]." coins of $10\n";
echo $inventory[1]." coins of $5\n";
echo $inventory[2]." coins of $3\n";
echo $inventory[3]." coins of $1\n";
}
I don't know PHP so I've tried it in Java. I hope that is ok as its the algorithm that is important.
My code is as follows:
package stackoverflow.changecalculator;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ChangeCalculator
{
List<Integer> coinsInTil = new ArrayList<>();
public void setCoinsInTil(List<Integer> coinsInTil)
{
this.coinsInTil = coinsInTil;
}
public Map<String, List> getPaymentDetailsFromCoinsAvailable(final int amountOwed, List<Integer> inPocketCoins)
{
List<Integer> paid = new ArrayList<>();
int remaining = amountOwed;
// Check starting with the largest coin.
for (Integer coin : inPocketCoins)
if (remaining > 0 && (remaining - coin) >= 0) {
paid.add(coin);
remaining = remaining - coin;
}
ProcessAlternative processAlternative = new ProcessAlternative(amountOwed, inPocketCoins, paid, remaining).invoke();
paid = processAlternative.getPaid();
remaining = processAlternative.getRemaining();
removeUsedCoinsFromPocket(inPocketCoins, paid);
int changeOwed = payTheRestWithNonExactAmount(inPocketCoins, paid, remaining);
List<Integer> change = calculateChangeOwed(changeOwed);
Map<String, List> result = new HashMap<>();
result.put("paid", paid);
result.put("change", change);
return result;
}
private void removeUsedCoinsFromPocket(List<Integer> inPocketCoins, List<Integer> paid)
{
for (int i = 0; i < inPocketCoins.size(); i++) {
Integer coin = inPocketCoins.get(i);
if (paid.contains(coin))
inPocketCoins.remove(i);
}
}
private List<Integer> calculateChangeOwed(int changeOwed)
{
List<Integer> change = new ArrayList<>();
if (changeOwed < 0) {
for (Integer coin : coinsInTil) {
if (coin + changeOwed == 0) {
change.add(coin);
changeOwed = changeOwed + coin;
}
}
}
return change;
}
private int payTheRestWithNonExactAmount(List<Integer> inPocketCoins, List<Integer> paid, int remaining)
{
if (remaining > 0) {
for (int coin : inPocketCoins) {
while (remaining > 0) {
paid.add(coin);
remaining = remaining - coin;
}
}
}
return remaining;
}
}
The ProcessAlternative class handles cases where the largest coin doesn't allow us to get a case where there is no change to be returned so we try an alternative.
package stackoverflow.changecalculator;
import java.util.ArrayList;
import java.util.List;
// if any remaining, check if we can pay with smaller coins first.
class ProcessAlternative
{
private int amountOwed;
private List<Integer> inPocketCoins;
private List<Integer> paid;
private int remaining;
public ProcessAlternative(int amountOwed, List<Integer> inPocketCoins, List<Integer> paid, int remaining)
{
this.amountOwed = amountOwed;
this.inPocketCoins = inPocketCoins;
this.paid = paid;
this.remaining = remaining;
}
public List<Integer> getPaid()
{
return paid;
}
public int getRemaining()
{
return remaining;
}
public ProcessAlternative invoke()
{
List<Integer> alternative = new ArrayList<>();
int altRemaining = amountOwed;
if (remaining > 0) {
for (Integer coin : inPocketCoins)
if (altRemaining > 0 && factorsOfAmountOwed(amountOwed).contains(coin)) {
alternative.add(coin);
altRemaining = altRemaining - coin;
}
// if alternative doesn't require change, use it.
if (altRemaining == 0) {
paid = alternative;
remaining = altRemaining;
}
}
return this;
}
private ArrayList<Integer> factorsOfAmountOwed(int num)
{
ArrayList<Integer> aux = new ArrayList<>();
for (int i = 1; i <= num / 2; i++)
if ((num % i) == 0)
aux.add(i);
return aux;
}
}
I worked in it by doing a test for example 1, then for example 2, and lastly moved on to example 3. The process alternative bit was added here and the alternative for the original test coins returned 0 change required so I updated to the amount input to 15 instead of 12 so it would calculate the change required.
Tests are as follows:
package stackoverflow.changecalculator;
import org.junit.Before;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class ChangeCalculatorTest
{
public static final int FIFTY_PENCE = 0;
public static final int TWENTY_PENCE = 1;
public static final int TEN_PENCE = 2;
public static final int FIVE_PENCE = 3;
public static final int TWO_PENCE = 4;
public static final int PENNY = 5;
public ChangeCalculator calculator;
#Before
public void setUp() throws Exception
{
calculator = new ChangeCalculator();
List<Integer> inTil = new ArrayList<>();
inTil.add(FIFTY_PENCE);
inTil.add(TWENTY_PENCE);
inTil.add(TEN_PENCE);
inTil.add(FIVE_PENCE);
inTil.add(TWO_PENCE);
inTil.add(PENNY);
calculator.setCoinsInTil(inTil);
}
#Test
public void whenHaveExactAmount_thenNoChange() throws Exception
{
// $5, $3, $3, $3, $1, $1, $1, $1
List<Integer> inPocket = new ArrayList<>();
inPocket.add(5);
inPocket.add(3);
inPocket.add(3);
inPocket.add(3);
inPocket.add(1);
inPocket.add(1);
inPocket.add(1);
inPocket.add(1);
Map<String, List> result = calculator.getPaymentDetailsFromCoinsAvailable(12, inPocket);
List change = result.get("change");
assertTrue(change.size() == 0);
List paid = result.get("paid");
List<Integer> expected = new ArrayList<>();
expected.add(5);
expected.add(3);
expected.add(3);
expected.add(1);
assertEquals(expected, paid);
}
#Test
public void whenDoNotHaveExactAmount_thenChangeReturned() throws Exception {
// $5, $3, $3, $3, $3
List<Integer> inPocket = new ArrayList<>();
inPocket.add(5);
inPocket.add(3);
inPocket.add(3);
inPocket.add(3);
inPocket.add(3);
Map<String, List> result = calculator.getPaymentDetailsFromCoinsAvailable(15, inPocket);
List change = result.get("change");
Object actual = change.get(0);
assertEquals(2, actual);
List paid = result.get("paid");
List<Integer> expected = new ArrayList<>();
expected.add(5);
expected.add(3);
expected.add(3);
expected.add(3);
expected.add(3);
assertEquals(expected, paid);
}
#Test
public void whenWeHaveExactAmountButItDoesNotIncludeBiggestCoin_thenPayWithSmallerCoins() throws Exception {
// $5, $3, $3, $3
List<Integer> inPocket = new ArrayList<>();
inPocket.add(5);
inPocket.add(3);
inPocket.add(3);
inPocket.add(3);
Map<String, List> result = calculator.getPaymentDetailsFromCoinsAvailable(6, inPocket);
List change = result.get("change");
assertTrue(change.size() == 0);
List paid = result.get("paid");
List<Integer> expected = new ArrayList<>();
expected.add(3);
expected.add(3);
assertEquals(expected, paid);
}
}
The tests are not the cleanest yet but they are all passing thus far. I may go back and add some more test cases later to see if I can break it but don't have time right now.
This answer is based off of גלעד-ברקן's answer. I am posting it here as per his request. While none of the answers were quite the one that I was looking for I found that this was the best option posted. Here is the modified algorithm that I am currently using:
<?php
function leastChange($inventory, $price){
//NOTE: Hard coded these in the function for my purposes, but $coin value can be passed as a parameter for a more general-purpose algorithm
$num_coin_types = 4;
$coin_value = [10,5,3,1];
$have = 0;
for ($i=0; $i < $num_coin_types; $i++){
$have += $inventory[$i] * $coin_value[$i];
}
//NOTE: Check to see if you have enough money to make this purchase
if ($price > $have){
$error = ["error", "Insufficient Funds"];
return $error;
}
$stack = [[0,$price,$have,[]]];
$best = [-max($coin_value),[]];
while (!empty($stack)){
// each stack call traverses a different set of parameters
$parameters = array_pop($stack);
$i = $parameters[0];
$owed = $parameters[1];
$have = $parameters[2];
$result = $parameters[3];
if ($owed <= 0){
//NOTE: check for new option with least change OR if same as previous option check which uses the least coins paid
if ($owed > $best[0] || ($owed == $best[0] && (array_sum($result) < array_sum($best[1])))){
//NOTE: add extra zeros to end if needed
while (count($result) < 4){
$result[] = 0;
}
$best = [$owed,$result];
}
continue;
}
// skip if we have none of this coin
if ($inventory[$i] == 0){
$result[] = 0;
$stack[] = [$i + 1,$owed,$have,$result];
continue;
}
// minimum needed of this coin
$need = $owed - $have + $inventory[$i] * $coin_value[$i];
if ($need < 0){
$min = 0;
} else {
$min = ceil($need / $coin_value[$i]);
}
// add to stack
for ($j=$min; $j<=$inventory[$i]; $j++){
$stack[] = [$i + 1,$owed - $j * $coin_value[$i],$have - $inventory[$i] * $coin_value[$i],array_merge($result,[$j])];
if ($owed - $j * $coin_value[$i] < 0){
break;
}
}
}
return $best;
}
Here is my test code:
$start = microtime(true);
$inventory = [0,1,3,4];
$price = 12;
echo "\n";
echo json_encode(leastChange($inventory,$price));
echo "\n";
$inventory = [0,1,4,0];
$price = 12;
echo "\n";
echo json_encode(leastChange($inventory,$price));
echo "\n";
$inventory = [0,1,4,0];
$price = 6;
echo "\n";
echo json_encode(leastChange($inventory,$price));
echo "\n";
$inventory = [0,1,4,0];
$price = 7;
echo "\n";
echo json_encode(leastChange($inventory,$price));
echo "\n";
$inventory = [1,3,3,10];
$price=39;
echo "\n";
echo json_encode(leastChange($inventory,$price));
echo "\n";
$inventory = [1,3,3,10];
$price=45;
echo "\n";
echo json_encode(leastChange($inventory,$price));
echo "\n";
//stress test
$inventory = [25,25,25,1];
$price=449;
echo "\n";
echo json_encode(leastChange($inventory,$price));
echo "\n";
$time_elapsed = microtime(true) - $start;
echo "\n Time taken: $time_elapsed \n";
The result:
[0,[0,1,2,1]]
[0,[0,0,4,0]]
[0,[0,0,2,0]]
[-1,[0,1,1,0]]
[0,[1,3,3,5]]
["error","Insufficient Funds"]
[-1,[25,25,25,0]]
Time taken: 0.0046839714050293
Of course that time is in microseconds and therefore it executed in a fraction of a second!
This is my solution i do not know how efficient is it but it works,i am open for suggestion.
<?php
$player=array(0,3,1,0);//how much coins you have
$player_copy=$player;
$coin_count=array(0,0,0,0);//memorize which coins you gave
$coin_value=array(1,3,5,10);
$price=6; //price of item
$price_copy=$price;
$z=3;
$change=array(-1,-1,-1,-1,-1); //memorise possible changes you can get
$k=0;
$flag=0;
label1: for($j=3;$j>=0;$j--){
$coin_count[$j]=0;
$player[$j]=$player_copy[$j];
}
for($j=$z;$j>=0;$j--){
while(($price>0) && 1<=$player[$j]){
$price-=$coin_value[$j];
$player[$j]--;
$coin_count[$j]++;
}
}
$change[$k++]=$price;
if($price!=0){
for($j=$z;$j>=0;$j--)
if($price_copy>$coin_value[$j]){
$z=$j-1;
$price=$price_copy;
goto label1;
}
$flag=1;
}
//find minimum change
$minv=$change[0];
for($i=1;$change[$i]>=0 and $i<4;$i++)
if($change[$i]>$minv)
$minv=$change[$i];
$i;
//when you find minimum change find which coins you have used
for($i=0;$i<4;$i++)
if($change[$i]==$minv && $flag==1){
$flag=2;
for($j=3;$j>=0;$j--){//reset coin_count and player budget
$coin_count[$j]=0;
$player[$j]=$player_copy[$j];
}
for($j=3-($i%2)-1;$j>=0;$j--){
while(($price>0) && 1<=$player[$j]){
$price-=$coin_value[$j];
$player[$j]--;
$coin_count[$j]++;
}
}
}
//prints result
for($j=0;$j<4;$j++)
printf("%d x %d\n",$coin_count[$j],$coin_value[$j]);
printf("change: %d\n",$minv);
?>

Longest Common Substring with wrong character tolerance

I have a script I found on here that works well when looking for the Lowest Common Substring.
However, I need it to tolerate some incorrect/missing characters. I would like be able to either input a percentage of similarity required, or perhaps specify the number of missing/wrong characters allowable.
For example, I want to find this string:
big yellow school bus
inside of this string:
they rode the bigyellow schook bus that afternoon
This is the code i'm currently using:
function longest_common_substring($words) {
$words = array_map('strtolower', array_map('trim', $words));
$sort_by_strlen = create_function('$a, $b', 'if (strlen($a) == strlen($b)) { return strcmp($a, $b); } return (strlen($a) < strlen($b)) ? -1 : 1;');
usort($words, $sort_by_strlen);
// We have to assume that each string has something in common with the first
// string (post sort), we just need to figure out what the longest common
// string is. If any string DOES NOT have something in common with the first
// string, return false.
$longest_common_substring = array();
$shortest_string = str_split(array_shift($words));
while (sizeof($shortest_string)) {
array_unshift($longest_common_substring, '');
foreach ($shortest_string as $ci => $char) {
foreach ($words as $wi => $word) {
if (!strstr($word, $longest_common_substring[0] . $char)) {
// No match
break 2;
}
}
// we found the current char in each word, so add it to the first longest_common_substring element,
// then start checking again using the next char as well
$longest_common_substring[0].= $char;
}
// We've finished looping through the entire shortest_string.
// Remove the first char and start all over. Do this until there are no more
// chars to search on.
array_shift($shortest_string);
}
// If we made it here then we've run through everything
usort($longest_common_substring, $sort_by_strlen);
return array_pop($longest_common_substring);
}
Any help is much appreciated.
UPDATE
The PHP levenshtein function is limited to 255 characters, and some of the haystacks i'm searching are 1000+ characters.
Writing this as a second answer because it's not based on my previous (bad) one at all.
This code is based on http://en.wikipedia.org/wiki/Wagner%E2%80%93Fischer_algorithm and http://en.wikipedia.org/wiki/Approximate_string_matching#Problem_formulation_and_algorithms
It returns one (of potentially several) minimum-levenshtein substrings of $haystack, given $needle. Now, levenshtein distance is just one measure of edit distance and it may not actually suit your needs. 'hte' is closer on this metric to 'he' than it is to 'the'. Some of the examples I put in show the limitations of this technique. I believe this to be considerably more reliable than the previous answer I gave, but let me know how it works for you.
// utility function - returns the key of the array minimum
function array_min_key($arr)
{
$min_key = null;
$min = PHP_INT_MAX;
foreach($arr as $k => $v) {
if ($v < $min) {
$min = $v;
$min_key = $k;
}
}
return $min_key;
}
// Calculate the edit distance between two strings
function edit_distance($string1, $string2)
{
$m = strlen($string1);
$n = strlen($string2);
$d = array();
// the distance from '' to substr(string,$i)
for($i=0;$i<=$m;$i++) $d[$i][0] = $i;
for($i=0;$i<=$n;$i++) $d[0][$i] = $i;
// fill-in the edit distance matrix
for($j=1; $j<=$n; $j++)
{
for($i=1; $i<=$m; $i++)
{
// Using, for example, the levenshtein distance as edit distance
list($p_i,$p_j,$cost) = levenshtein_weighting($i,$j,$d,$string1,$string2);
$d[$i][$j] = $d[$p_i][$p_j]+$cost;
}
}
return $d[$m][$n];
}
// Helper function for edit_distance()
function levenshtein_weighting($i,$j,$d,$string1,$string2)
{
// if the two letters are equal, cost is 0
if($string1[$i-1] === $string2[$j-1]) {
return array($i-1,$j-1,0);
}
// cost we assign each operation
$cost['delete'] = 1;
$cost['insert'] = 1;
$cost['substitute'] = 1;
// cost of operation + cost to get to the substring we perform it on
$total_cost['delete'] = $d[$i-1][$j] + $cost['delete'];
$total_cost['insert'] = $d[$i][$j-1] + $cost['insert'];
$total_cost['substitute'] = $d[$i-1][$j-1] + $cost['substitute'];
// return the parent array keys of $d and the operation's cost
$min_key = array_min_key($total_cost);
if ($min_key == 'delete') {
return array($i-1,$j,$cost['delete']);
} elseif($min_key == 'insert') {
return array($i,$j-1,$cost['insert']);
} else {
return array($i-1,$j-1,$cost['substitute']);
}
}
// attempt to find the substring of $haystack most closely matching $needle
function shortest_edit_substring($needle, $haystack)
{
// initialize edit distance matrix
$m = strlen($needle);
$n = strlen($haystack);
$d = array();
for($i=0;$i<=$m;$i++) {
$d[$i][0] = $i;
$backtrace[$i][0] = null;
}
// instead of strlen, we initialize the top row to all 0's
for($i=0;$i<=$n;$i++) {
$d[0][$i] = 0;
$backtrace[0][$i] = null;
}
// same as the edit_distance calculation, but keep track of how we got there
for($j=1; $j<=$n; $j++)
{
for($i=1; $i<=$m; $i++)
{
list($p_i,$p_j,$cost) = levenshtein_weighting($i,$j,$d,$needle,$haystack);
$d[$i][$j] = $d[$p_i][$p_j]+$cost;
$backtrace[$i][$j] = array($p_i,$p_j);
}
}
// now find the minimum at the bottom row
$min_key = array_min_key($d[$m]);
$current = array($m,$min_key);
$parent = $backtrace[$m][$min_key];
// trace up path to the top row
while(! is_null($parent)) {
$current = $parent;
$parent = $backtrace[$current[0]][$current[1]];
}
// and take a substring based on those results
$start = $current[1];
$end = $min_key;
return substr($haystack,$start,$end-$start);
}
// some testing
$data = array( array('foo',' foo'), array('fat','far'), array('dat burn','rugburn'));
$data[] = array('big yellow school bus','they rode the bigyellow schook bus that afternoon');
$data[] = array('bus','they rode the bigyellow schook bus that afternoon');
$data[] = array('big','they rode the bigyellow schook bus that afternoon');
$data[] = array('nook','they rode the bigyellow schook bus that afternoon');
$data[] = array('they','console, controller and games are all in very good condition, only played occasionally. includes power cable, controller charge cable and audio cable. smoke free house. pes 2011 super street fighter');
$data[] = array('controker','console, controller and games are all in very good condition, only played occasionally. includes power cable, controller charge cable and audio cable. smoke free house. pes 2011 super street fighter');
foreach($data as $dat) {
$substring = shortest_edit_substring($dat[0],$dat[1]);
$dist = edit_distance($dat[0],$substring);
printf("Found |%s| in |%s|, matching |%s| with edit distance %d\n",$substring,$dat[1],$dat[0],$dist);
}

How can I optimize this 'lottery' function in PHP?

Earlier I wrote a code in Matlab for this sort of lottery function, just to test if it was possible. However, I actually needed it in PHP so I've just rewritten the code and it does seem to work, but as it involves a lot of looping I want to make sure I'm doing it as efficiently as possible.
What the code does:
You can call the function $lotto -> type($users,$difficulty) and it will return two numbers. Here's the explanation, $users is the number of users registered on the website, i.e the people who will potentially buy a ticket. $difficulty is a number between 1 and 10, where 5 is normal, 1 is easy and 10 is hard. Difficulty here means how hard it is to match all numbers on a lottery ticket.
So what are the numbers that the function returns? That would be $n and $r. $n is the amount of numbers there will be on the lottery ticket, and $r is the amount of numbers you can choose from the lottery ticket. For example, in the UK a national lottery ticket has 49 numbers if which you choose 6. I.e $n = 49 and $r = 6.
How does the function calculate these two numbers? In the UK national lottery there are 13,983,816 different possible ticket combinations. If I were to run $lotto -> type(13983816,1) it would return array(49,6). Basically it tried to make it so there are as many combinations of tickets as there are registered users.
tl;dr, here's the code:
<?php
class lotto {
public function type($users,$difficulty){
$current_r = $r = 2;
$current_n = 0;
$difficulty = ($difficulty + 5) / 10; // sliding scale from 1 - 10
$last_tickets_sold = 200; // tickets sold in last lotto
$last_users = 100; // how many users there were in the last lotto
$last_factor = $last_tickets_sold / $last_users; // tickets per user
$factor = $last_factor * $difficulty;
$users *= $factor;
while($r <= 10){
$u = 0;
$n = $r;
while($u < $users && $n < 50){
$u = $this -> nCr(++$n,$r);
}
if($r == 2){
$current_n = $n;
} elseif(abs($this -> nCr($n,$r) - $users) < abs($this -> nCr($current_n,$current_r) - $users)){
// this is a better match so update current n and r
$current_r = $r;
$current_n = $n;
}
$r++;
}
return array($current_n,$current_r);
}
private function nCr($n,$r){
return $this -> factorial($n) / (
$this -> factorial($r) * $this -> factorial($n - $r)
);
}
private function factorial($x){
$f = $x;
while(--$x){
$f *= $x;
}
return $f;
}
}
$lotto = new lotto;
print_r($lotto -> type(1000,5));
?>
I did a quick scan and spotted a few places that can be further optimized.
Combination
Your algorithm is a brute force one and can be further optimized
private function nCr($n,$r){
return $this -> factorial($n) / (
$this->factorial($r) * $this->factorial($n - $r)
);
}
to
function nCr($n,$r) {
$top = 1;
$sub = 1;
for($i = $r+1; $i <= $n; $i++)
$top *= $i;
$n -= $r;
for($i = 2; $i <= $n; $i++)
$sub *= $i;
return $top / $sub;
}
Too Much Combination Calculation
Calculate combination is expensive.
$u = 0;
$n = $r;
while($u < $users && $n < 50){
$u = $this -> nCr(++$n,$r);
}
to
$n = $r + 1;
$u = nCr($n, $r);
while ($u < $users && $n < 50) {
$n++;
$u *= $n;
$u /= ($n - $r);
}
An immediate observation is that you have the possibility of a divide by 0 error
$last_factor = $last_tickets_sold / $last_users;
Could be solved by putting a simple if statement around it
$last_factor = ($last_users == 0) ? 0 : $last_tickets_sold / $last_users;
Regardless detailed examination of your code, are you sure that your loops does not need continue or break?
The range of factorial() in your algo is [0,50], so why not just precompute this statically?
private static $factorial=array(1);
private static genFactorial($max) {
if( count( self::$factorial ) > $max ) return;
foreach ( range(count(self::$factorial), $max) as $n ) {
self::$factorial[$n] = $i*self::$factorial[$n-1];
}
}
Now add a self::genFactorial(50); to __construct() or to type() and replace references to $this -> factorial($n) by self::$factorial[$n].
This is just a quick code dump; not even compile checked so forgive any typos, etc. but what this does is to replace a function call (which includes a while loop) by an array element fetch.

how to pageinator link creation

I am trying to create a dynamic page links created based on the number of rows in a mysql table. I would like to display 10 results per page and wish to have the php script create links to additional pages.
So I was thinking of using the num_rows and dividing it by 10 however if I have 53 rows the return would be 5.3 where as I would need 6 pages and not 5. I am thinking of using the round function and looping it through a for I statement until $pages > $rows_rounded. And every 10 rows add a link to pages($i) Is this the best method to acheive this or there an alternative simpler route to take?
pagenator class I made. getCurrentPages() returns all the pages you should be displaying in an array. so if you are on page one, and you want to display a total of 9 pages, you would get an array 1-9. if you were on page 10 however, your would get back an array 6-14. if there are 20 total pages and you are on page 20, you would get back an array 11-20.
<?php
class Lev_Pagenator {
private $recordsPerPage;
private $currentPage;
private $numberOfTotalRecords;
private $lastPage = null;
public function __construct($current_page, $number_of_total_records, $records_per_page = 25) {
$this->currentPage = $current_page;
$this->numberOfTotalRecords = $number_of_total_records;
$this->recordsPerPage = $records_per_page;
}
public function getCurrentStartIndex() {
return ($this->currentPage - 1) * $this->recordsPerPage;
}
public function getCurrentPages($number_of_pages_to_display = 9) {
$start_page = $this->currentPage - floor($number_of_pages_to_display / 2);
if ($start_page < 1) $start_page = 1;
$last_page = $this->getLastPage();
$pages = array($start_page);
for ($i = 1; $i < $number_of_pages_to_display; $i++) {
$temp_page = $start_page + $i;
if ($temp_page <= $last_page) {
$pages[] = $temp_page;
} else {
break;
}
}
return $pages;
}
public function getPreviousPage() {
if ($this->currentPage === 1) return false;
return $this->currentPage - 1;
}
public function getNextPage() {
if ($this->currentPage === $this->getLastPage) return false;
return $this->currentPage + 1;
}
public function getLastPage() {
if ($this->lastPage === null) $this->lastPage = ceil($this->numberOfTotalRecords / $this->recordsPerPage);
return $this->lastPage;
}
}
?>
EDIT (USAGE):
<?php
$pagenator = new Lev_Pagenator($current_page, $number_of_total_records, $records_per_page);
$pages_array = $pagenator->getCurrentPages($number_of_pages_to_display);
?>
The idea of a for loop sounds like a good one, you would use something like:
$rows_rounded = ceil(mysql_num_rows($result) / 10);
for($x = 1; $x <= $rows_rounded; $x++){
echo 'Page '.$x.'';
}
But you need to consider detecting the current page, so if, for example, the current page was 3, it might be a good idea to test for that in your for loop and if echoing the 3rd link maybe add some extra class to enable you to style it.

Categories