Choosing coins with least or no change given - php

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);
?>

Related

Get lowest price on sum of combinations in given array

This code is working fine when the array length is 8 or 10 only. When we are checking this same code for more than 10 array length.it get loading not showing the results.
How do reduce my code. If you have algorithm please share. Please help me.
This program working flow:
$allowed_per_room_accommodation =[2,3,6,5,3,5,2,5,4];
$allowed_per_room_price =[10,30,60,40,30,50,20,60,80];
$search_accommodation = 10;
i am get subsets = [5,5],[5,3,2],[6,4],[6,2,2],[5,2,3],[3,2,5]
Show lowest price room and then equal of 10 accommodation; output like as [5,3,2];
<?php
$dp=array(array());
$GLOBALS['final']=[];
$GLOBALS['room_key']=[];
function display($v,$room_key)
{
$GLOBALS['final'][] = $v;
$GLOBALS['room_key'][] = $room_key;
}
function printSubsetsRec($arr, $i, $sum, $p,$dp,$room_key='')
{
// If we reached end and sum is non-zero. We print
// p[] only if arr[0] is equal to sun OR dp[0][sum]
// is true.
if ($i == 0 && $sum != 0 && $dp[0][$sum]) {
array_push($p,$arr[$i]);
array_push($room_key,$i);
display($p,$room_key);
return $p;
}
// If $sum becomes 0
if ($i == 0 && $sum == 0) {
display($p,$room_key);
return $p;
}
// If given sum can be achieved after ignoring
// current element.
if (isset($dp[$i-1][$sum])) {
// Create a new vector to store path
// if(!is_array(#$b))
// $b = array();
$b = $p;
printSubsetsRec($arr, $i-1, $sum, $b,$dp,$room_key);
}
// If given $sum can be achieved after considering
// current element.
if ($sum >= $arr[$i] && isset($dp[$i-1][$sum-$arr[$i]]))
{
if(!is_array($p))
$p = array();
if(!is_array($room_key))
$room_key = array();
array_push($p,$arr[$i]);
array_push($room_key,$i);
printSubsetsRec($arr, $i-1, $sum-$arr[$i], $p,$dp,$room_key);
}
}
// Prints all subsets of arr[0..n-1] with sum 0.
function printAllSubsets($arr, $n, $sum,$get=[])
{
if ($n == 0 || $sum < 0)
return;
// Sum 0 can always be achieved with 0 elements
// $dp = new bool*[$n];
$dp = array();
for ($i=0; $i<$n; ++$i)
{
// $dp[$i][$sum + 1]=true;
$dp[$i][0] = true;
}
// Sum arr[0] can be achieved with single element
if ($arr[0] <= $sum)
$dp[0][$arr[0]] = true;
// Fill rest of the entries in dp[][]
for ($i = 1; $i < $n; ++$i) {
for ($j = 0; $j < $sum + 1; ++$j) {
// echo $i.'d'.$j.'.ds';
$dp[$i][$j] = ($arr[$i] <= $j) ? (isset($dp[$i-1][$j])?$dp[$i-1][$j]:false) | (isset($dp[$i-1][$j-$arr[$i]])?($dp[$i-1][$j-$arr[$i]]):false) : (isset($dp[$i - 1][$j])?($dp[$i - 1][$j]):false);
}
}
if (isset($dp[$n-1][$sum]) == false) {
return "There are no subsets with";
}
$p;
printSubsetsRec($arr, $n-1, $sum, $p='',$dp);
}
$blockSize = array('2','3','6','5','3','5','2','5','4');
$blockvalue = array('10','30','60','40','30','50','20','60','80');
$blockname = array("map","compass","water","sandwich","glucose","tin","banana","apple","cheese");
$processSize = 10;
$m = count($blockSize);
$n = count($processSize);
// sum of sets in array
printAllSubsets($blockSize, $m, $processSize);
$final_subset_room = '';
$final_set_room_keys = '';
$final_set_room =[];
if($GLOBALS['room_key']){
foreach ($GLOBALS['room_key'] as $set_rooms_key => $set_rooms) {
$tot = 0;
foreach ($set_rooms as $set_rooms) {
$tot += $blockvalue[$set_rooms];
}
$final_set_room[$set_rooms_key] = $tot;
}
asort($final_set_room);
$final_set_room_first_key = key($final_set_room);
$final_all_room['set_room_keys'] = $GLOBALS['room_key'][$final_set_room_first_key];
$final_all_room_price['set_room_price'] = $final_set_room[$final_set_room_first_key];
}
if(isset($final_all_room_price)){
asort($final_all_room_price);
$final_all_room_first_key = key($final_all_room_price);
foreach ($final_all_room['set_room_keys'] as $key_room) {
echo $blockname[$key_room].'---'. $blockvalue[$key_room];
echo '<br>';
}
}
else
echo 'No Results';
?>
I'm assuming your task is, given a list rooms, each with the amount of people it can accommodate and the price, to accommodate 10 people (or any other quantity).
This problem is similar to 0-1 knapsack problem which is solvable in polynomial time. In knapsack problem one aims to maximize the price, here we aim to minimize it. Another thing that is different from classic knapsack problem is that full room cost is charged even if the room is not completely occupied. It may reduce the effectiveness of the algorithm proposed at Wikipedia. Anyway, the implementation isn't going to be straightforward if you have never worked with dynamic programming before.
If you want to know more, CLRS book on algorithms discusses dynamic programming in Chapter 15, and knapsack problem in Chapter 16. In the latter chapter they also prove that 0-1 knapsack problem doesn't have trivial greedy solution.

Generate Interpolation Function from points PHP

I need to make a PHP script that generates the interpolation function from the set of points.
I have decided to use the Lagrange Interpolation because it was easiest for me to find the example that generates the function from a list of input points. The issues with other methods is that I couldn't find an example that generates the function -> all other examples for all other interpolations only generate additional points and not the function out of the existing points.
The source that I've used to find the example for the Lagrange Interpolation is: http://www2.lawrence.edu/fast/GREGGJ/Math420/Section_3_1.pdf
I've decided to replicate this example in my PHP code.
/**
* Generate one basis polynomial function
* #param type $points array of points
* #param type $basisPolynomial Each basis polynomial will be stored in the array of values so that it can be appended to the final function
* #param type $allXValues all x values for the point
* #param type $i current index of the basis polynomial
*/
function generateBasisPolynomial(&$basisPolynomial, $allXValues, $i) {
$basisPolynomial[$i] = "(";
$divisor = "(";
for ($j = 0; $j < count($allXValues); $j++) {
if ($j == $i) {
continue;
}
$basisPolynomial[$i] .= "(x-$allXValues[$j])*";
$divisor .="($allXValues[$i]-$allXValues[$j])*";
}
//multiply the divisor by 1, because the previous loop has * at the end of the equation
$divisor .="1)";
$basisPolynomial[$i] .="1)/$divisor";
}
/**
* Function that generates the Lagrange interpolation from the list of points
* #param type $points
* #return string
*/
function generateLagrangeInterpolation($points) {
$numberOfPoints = count($points);
if ($numberOfPoints < 2) {
return "NaN";
} else {
//source http://www2.lawrence.edu/fast/GREGGJ/Math420/Section_3_1.pdf
//for each point, construct the basis polynomial
//for a sequence of x values, we will have n basis polynomials,
//Example:
//if we, for example have a sequence of four points, with their sequence of x values being {x0,x1,x2,x3}
//then we construct the basis polynomial for x0 by doing the following calculation:
//F(x) = ((x-x1)*(x-x2)*(x-x3))/((x0-x1)*(x0-x2)*(x0-x3)) -> where x is an unknown variable.
$basisPolynomial = array();
//get all x values from the array of points so that we can access them by index
$allXValues = array_keys($points);
$allYValues = array_values($points);
//Because the Y values are percentages, we want to divide them by 100.
$allYValues = array_map(function($val) {
return $val / 100;
}, $allYValues);
$returnFunction = "";
for ($i = 0; $i < $numberOfPoints; $i++) {
generateBasisPolynomial($basisPolynomial, $allXValues, $i);
//multiply this basis polynomial by y value
$returnFunction .="$allYValues[$i]*$basisPolynomial[$i]+";
}
//Append 0 to the end of the function because the above loop returns a function with a +
//at the end so we want to make it right
$returnFunction .="0";
echo $returnFunction;
}
}
//$points = array("4.1168" => "0.213631", "4.19236" => "0.214232", "4.20967" => "0.21441", "4.46908" => "0.218788");
$points = array("0.1" => "5", "0.3" => "10", "0.5" => "30", "0.6" => "60", "0.8" => "70");
generateLagrangeInterpolation($points);
What I am getting as a result is the following function:
0.05*((x-0.3)*(x-0.5)*(x-0.6)*(x-0.8)*1)/((0.1-0.3)*(0.1-0.5)*(0.1-0.6)*(0.1-0.8)*1)+0.1*((x-0.1)*(x-0.5)*(x-0.6)*(x-0.8)*1)/((0.3-0.1)*(0.3-0.5)*(0.3-0.6)*(0.3-0.8)*1)+0.3*((x-0.1)*(x-0.3)*(x-0.6)*(x-0.8)*1)/((0.5-0.1)*(0.5-0.3)*(0.5-0.6)*(0.5-0.8)*1)+0.6*((x-0.1)*(x-0.3)*(x-0.5)*(x-0.8)*1)/((0.6-0.1)*(0.6-0.3)*(0.6-0.5)*(0.6-0.8)*1)+0.7*((x-0.1)*(x-0.3)*(x-0.5)*(x-0.6)*1)/((0.8-0.1)*(0.8-0.3)*(0.8-0.5)*(0.8-0.6)*1)+0
I don't care that the expression is simplified and calculated fully (however if you have any advice or code that could do that for me it would be a huge plus).
If I look at the simplified expression it looks something like this:
(47500*x^4-79300*x^3+42245*x^2-8699*x+480)/(-840)
However if I try to paste that function into http://fooplot.com -> I get that the graph is passing through the points defined as the input parameters, however, I'm not sure if the graph for the other points is correct as it looks like it's Y values go into minus values when X <=0 or x>=1.
Do you advise that I use the different function or the existing error in the interpolation can be reduced if I had more input points? I have to be honest that I am a poor mathematician so any real example of a more accurate method or example in the code would be greatly appreciated.
Thanks
Here's what you can try:
function basisPolynomial($points, $j, $x) {
$xj = $points[$j][0]; //Assume a point is an array of 2 numbers
$partialProduct = 1;
//Product loop
for ($m = 0;$i < count($points);$m++) {
if ($m === $j) { continue; }
$partialProduct *= ($x - $points[$m][0])/($xj-$points[$m][0]);
}
return $partialProduct;
}
function lagrangePolynomial($points,$x) {
$partialSum = 0;
for ($j = 0;$j < count($points);$j++) {
$partialSum += $points[$j][1]*basisPolynomial($points,$j,$x);
}
return $partialSum;
}
Now if you need to plot it you can generate a list of points that can be used in a plotting function e.g.
$points = <my points>;
$plotPoints = [];
for ($i = 0;$i < 10;$i+= 0.1) { //for example
$plotPoints[] = [ $i, lagrangePolynomial($points,$i) ];
}
If you want to just use the to directly plot you need to use a plotting tool like gnuplot to define the functions and have it determine how to plot them.
Update: http://www.physics.brocku.ca/Courses/5P10/lectures/lecture_10_handout.pdf seems to have a gnuplot example of exactly what you need but it feels like cheating of sorts
I'm not much familiarized with the object oriented implementation of php, but in java I do this little baby ;)
import java.util.*;
import java.util.Arrays;
import java.util.List;
public class Run{
public static void main(String[] args){
int[] parameters = new int[]{-1, 2, 4, 3};
Binom b = new Binom(1, 1, parameters[1], 0);
Polinom p = new Polinom(b);
for(int i = 2; i < parameters.length; i++)
p.joinBinom(new Binom(1, 1, -1 * parameters[i], 0));
System.out.println(p.toString() + " / (" + getDenominator(parameters) + ")");
}
public static int getDenominator(int[] params){
int result = 1;
for(int i = 1; i < params.length; i++)
result *= params[0] - params[i];
return result;
}
}
class Monomial{
private int constant = 1;
private int pow = 0;
public int getConstant(){
return this.constant;
}
public void sumConstant(int value){
this.constant += value;
}
public boolean hasVariable(){
return this.pow > 0;
}
public int getPow(){
return this.pow;
}
public Monomial(int constant, int pow){
this.constant = constant;
this.pow = pow;
}
public ArrayList<Monomial> multiply(Binom a){
Monomial first = new Monomial(this.constant * a.getFirst().getConstant(), this.pow + a.getFirst().getPow());
Monomial second = new Monomial(this.constant * a.getSecond().getConstant(), this.pow + a.getSecond().getPow());
System.out.print("\t" + this.toString() + "\t* (" + a.getFirst().toString() + " " + a.getSecond().toString() + ")");
System.out.print("\t= " + first.toString() + "\t");
System.out.println(second.toString());
return (new Binom(first, second)).toList();
}
public String toString(){
String result = "";
if(this.constant == 1){
if(!this.hasVariable())
result += this.constant;
}
else
result += this.constant;
if(this.hasVariable()){
result += "X";
if(this.pow > 1)
result += "^" + this.pow;
}
return result;
}
}
class Binom{
private Monomial first;
private Monomial second;
public Monomial getFirst(){
return this.first;
}
public Monomial getSecond(){
return this.second;
}
public Binom(int constant1, int pow1, int constant2, int pow2){
this.first = new Monomial(constant1, pow1);
this.second = new Monomial(constant2, pow2);
}
public Binom(Monomial a, Monomial b){
this.first = a;
this.second = b;
}
public ArrayList<Monomial> toList(){
ArrayList<Monomial> result = new ArrayList<>();
result.add(this.first);
result.add(this.second);
return result;
}
}
class Polinom{
private ArrayList<Monomial> terms = new ArrayList<>();
public Polinom(Binom b){
this.terms.add(b.getFirst());
this.terms.add(b.getSecond());
}
private void compact(){
for(int i = 0; i < this.terms.size(); i++){
Monomial term = this.terms.get(i);
for(int j = i + 1; j < this.terms.size(); j++){
Monomial test = this.terms.get(j);
if(term.getPow() == test.getPow()){
term.sumConstant(test.getConstant());
this.terms.remove(test);
j--;
}
}
}
}
public void joinBinom(Binom b){
ArrayList<Monomial> result = new ArrayList<>();
for(Monomial t : this.terms){
result.addAll(t.multiply(b));
}
this.terms = result;
this.compact();
}
public String toString(){
String result = "";
for(Monomial t : this.terms)
result += (t.getConstant() < 0 ? " " : " +") + t.toString();
return "(" + result + ")";
}
}
which return:
X * (X -4) = X^2 -4X
2 * (X -4) = 2X -8
X^2 * (X -3) = X^3 -3X^2
-2X * (X -3) = -2X^2 6X
-8 * (X -3) = -8X 24
( +X^3 -5X^2 -2X +24) / (-60)
Looks like the current algorithm for Lagrange interpolation method provides the correct results. To correct the errors in calculation, more base points can be provided. Also, to multiply the unknown variables in mathematic function, a function example was left in one of the answers.
Thanks everyone.

How to organize in chunks a not accurate division?

I have a number of participants and a number of groups, and I have to organize the participants into groups.
Example:
10/3 = 3, 3 and 4.
10/9 = 2,2,2 and 4.
23/3 = 6,6,6 and 5.
I have tried with array_chunk using the size paramether as a rounded result of participants/groups but it Did not work well.
Edit with my problem solved.
$groups = $this->request->data['phases_limit'];
$classified_lmt = $this->request->data['classified_limit'];
$participants = count($game->user_has_game);
$participants_lmt = floor($participants / $groups);
$remainders = $participants % $groups;
if ($groups > $participants) {
throw new \Exception("Há mais grupos que participantes");
}
for ($i=0; $i < $groups; $i++) {
$p = $this->Phase->newEntity();
$p->name = 'Grupo #' . $game->id;
$p->game_id = $game->id;
$p->classified_limit = $classified_lmt;
$this->Phase->save($p);
// add the number of participants per group
for ($j=0; $j < $participants_lmt; $j++) {
$user_has_game = array_pop($game->user_has_game);
$g = $this->Phase->GroupUserHasGame->newEntity();
$g->group_id = $p->id;
$g->user_has_game_id = $user_has_game->id;
$this->Phase->GroupUserHasGame->save($g);
}
// check if it is the last iteration
if (($groups - 1) == $i) {
// add the remainders on the last iteration
for ($k=0; $k < $remainders; $k++) {
$user_has_game = array_pop($game->user_has_game);
$g = $this->Phase->GroupUserHasGame->newEntity();
$g->group_id = $p->id;
$g->user_has_game_id = $user_has_game->id;
$this->Phase->GroupUserHasGame->save($g);
}
}
}
Have you tried the modulus operator? It gives you the remainder after dividing the numerator by the denominator.
For example, if you want to split 10 people into 3 groups:
floor(10 / 3) = 3; // people per group
10 % 3 = 1; // 1 person left over to add to an existing group.
Edit - I included the following function as part of my original answer. This doesn't work for OP, however I want to leave it here, as it may help others.
function group($total, $groups)
{
// Calculate participants per group and remainder
$group = floor($total / $groups);
$remainder = $total % $groups;
// Prepare groupings and append remaining participant to first group
$groupings = array_fill(0, $groups, $group);
$groupings[0] += $remainder;
return $groupings;
}
Not sure there are off-the-shelf libraries for that. I just implemented something similar in Java if you need some ideas:
public List<Integer> createDistribution(int population_size, int groups) {
List<Integer> lst = new LinkedList();
int total = 0;
for (double d : createDistribution(groups)) {
// this makes smaller groups first int i = new Double(population_size * d).intValue();
int i = (int)Math.round(population_size * d);
total += i;
lst.add(i);
}
// Fix rounding errors
while (total < population_size) {
int i = r.nextInt(groups);
lst.set(i, lst.get(i) + 1);
total += 1;
}
while (total > population_size) {
int i = r.nextInt(groups);
if (lst.get(i) > 0) {
lst.set(i, lst.get(i) - 1);
total -= 1;
}
}
return lst;
}

Creating a Banner Swapping Algorithm to Rotate Ads

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.

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);
}

Categories