How to dynamically generate Excel column numbers in php? - php

I'm writing an export to excel & have a dynamic number of columns, i.e. I won't know how many columns will be needed before I run the query so I need to dynamically assign the column numbers like below:
$objPHPExcel->setActiveSheetIndex(0)
->setCellValue('A1', 'value')
->setCellValue('A2', 'value')
->setCellValue('B2', 'value')
->setCellValue('C2', 'value')
//...etc...
->setCellValue('AA2', 'value')
->setCellValue('AB2', 'value')
->setCellValue('AC2', 'value')
//...etc...
how can I do this with php?
UPDATE
sorry, the column naming pattern is:
A, B, C, .. AA, AB, AC ... BA, BB, BC... etc, the number suffix is actually the row. and no, the 'value would actually be populated from some query data setting my values would look more like:
$i=1;
while($result= $query->fetch_assoc()){
->setCellValue($col.$i, $result['whatever'])
$i++;
}
I just don't know how to get the column letters to increment in that pattern.

Simplest answer is
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
echo Coordinate::stringFromColumnIndex(5) // return E
echo Coordinate::columnIndexFromString('AA') // returns 27

Optimized Function
function columnFromIndex($number){
if($number === 0)
return "A";
$name='';
while($number>0){
$name=chr(65+$number%26).$name;
$number=intval($number/26)-1;
if($number === 0){
$name="A".$name;
break;
}
}
return $name;
}

If you don't know how many columns/rows you're going to be working with in advance, how can you expect to use the fluent interface in this way?
Following your edit of the question, you might do something like:
$i = 1;
while($result = $query->fetch_assoc()){
$col = 'A';
foreach($result as $value) {
$objPHPExcel->getActiveSheet()->setCellValue($col++.$i, $value);
}
$i++;
}
However, you might consider taking another approach, and using the fromArray() method to populate your data
$i = 1;
while($result = $query->fetch_assoc()){
$objPHPExcel->getActiveSheet()->fromArray('A'.$i++, $result);
}

Maybe so late, and i dont know if there's a better solution but, i make this recursive function to solve this problem
private function getLastColumn($last){ //last represent how mouch X axis spaces
//you want to jump
$max = 26; //Max characters (in number) that you cant jump, ('z´)
$inicial = 'A';
$col = '';
if ($last > $max) //if last is bigger than last you should recall the function
{
$last = $last - $max; //discount the max to the last
$letterAscii = ord($inicial); //tansform the charcter to to number
$letterAscii += ($last - 1); // add last -1(-1 to stay in A to keep the
//base 1)
$col = chr($letterAscii); // int to ascci
$col = $col.$this->getLastColumn($last); // recall the funcion and concat
//the result
}else{
$letterAscii = ord($inicial); // same as adove
$letterAscii += ($last - 1);
$col = chr($letterAscii); //only pass the result to col
}
return $col; //return coll
}
and with this if you need 27 spaces to de right you will get 'AA'
if there is a better solution teld me :)

i think is too late, but for my snippet
i make this function,
function number_to_column($num) {
$column_name = array();
for($i=0;$i<=$num;$i++) {
$numeric = $i % 26;
$letter = chr(65 + $numeric);
$num2 = intval($num / 26);
if ($num2 > 0) {
if($i<26) {
$v_column = $letter;
}
if($i>25 && $i<52) {
$v_column = 'A'.$letter;
}
if($i>51) {
$v_column = 'B'.$letter;
}
$column_name[] = $v_column;
} else {
$v_column = $letter;
$column_name[] = $v_column;
}
}
return $column_name;
}
then we can use it like this
$column_name = number_to_column(count($array[0])-1);
the $column_name will result excel row name,

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.

Check if Variable is smaller than others, and which one its smaller than. PHP

I have been working on my scoring system and came accros this.
I have 4 vars.
$newscore
$score1
$score2
$score3
I want to see if new score is lower than the 3 others, and if so which ones. The scoring system requires you to have the lowest possible score.
I have the following code:
if($newscore > $score1){
if($newscore > $score2){
if($newscore < $score3){
//has to be score3 to replace.
}
}else{
...
}
}
But what I'm wondering is if I will have to continue on with all these if statements, or is there something a lot shorter and easier? I need to replace the the score that it is smaller than, but not the one its larger than. Score 1 2 and 3 are all the players stats. And if I do have to continue on with all the if statements, how would the code look (its baffling my logic)?
you should probably use an array
$scores = array(8, 15, 10); //previous scores
$new_score = 5;
$new_score_smallest = true;
foreach($scores as $score) {
if($score < $new_score) {
$new_score_smallest = false;
}
}
if($new_score_smallest) {
echo "Best score!";
}
else {
echo "Not the best score :(";
}
If you only want to remember the 3 best scores:
$scores = array(5, 6, 8);
$new_score = 7;
for($i = 0; $i < count($scores); $i++) {
if($new_score < $scores[$i]) {
$scores[$i] = $new_score;
break;
}
}
You could do something like the following:
EDIT: As you're using a database, you would execute the query similar to this:
SELECT scores FROM scores_table replacing the table name and column name with your corresponding data.
$scores = [$score1, $score2] // add as many as you like
$new_score = $scores[0]; // assign a baseline
foreach ($scores as $score) {
if ($score < $new_score) {
$new_score = $score;
}
}
Hope that helps.
You can use array_search and min
$newscore = 2;
$scores = array($score1, $score2, $score3);
if($newscore < min($scores)){
$scores[array_search(min($scores), $scores)] = $newscore;
}
array $score lowest score will be updated if $newscore is inferior
Put your scores in an array or anything iterable (like a query result, whether you use PDO or mysqli).
Let's say your scores are in $scores array (it will be the same if $score is a PDOStatement for example) and ordered (use ORDER ASC in your query):
$scores = [105, 201, 305];
$newscore = '186';
$hiscore = false;
$beaten = [];
foreach ($score as $k => $score) {
if ($newscore > $score) {
$hiscore = true;
$beaten[] = $score;
unset($scores[$k]);
}
}
if ($hiscore) {
echo 'New high score!'.PHP_EOL;
echo 'Better than '.implode(', ', $beaten).PHP_EOL;
echo 'But not better than '.implode(', ', $scores);
} else {
echo 'Try harder!';
}
<?php
$newscore = 78;
$score1 = 23;
$score2 = 201;
$score3 = 107;
$max = max([$score1, $score2, $score3]);
if ($max < $newscore) {
echo "New best score ! ({$newscore})";
} else {
echo "Not the best score !\nCurrent: {$newscore}\nBest: {$max}";
}

Set condition in wordsearch so letters of words will not intersect with each other?

I'm trying to make a wordsearch game using php. First I will create the table/grid and then populate the table with random letters and then I will replace the random letters with the letters of the words as well as determining the direction of the words either HORIZONTAL, VERTICAL or DIAGONAL. The problem is, the letters of words intersect with each other, which messed up the table. The questions are,
How to set condition where in letters of words will not intersect with each other
How to determine if the current position is already occupied by the other words letter?
I'm having problem with the letters of words they keep intersecting with each other.
Any idea?
$row = 5;
$col = 5;
$characters = range('a','z');
$max = count($characters) - 1;
$rc = array();
for ($r=1;$r<=$row;$r++)
{
for ($c=1;$c<=$col;$c++)
{
$rc['r'.$r.'c'.$c] = $characters[mt_rand(0,$max)];
$fill['r'.$r.'c'.$c] = '';
}
}
$directions = array('H', 'V', 'D');
$wrdList =array('four', 'data', 'howl');
foreach ($wrdList as $wrd)
{
$wrdLen = strlen($wrd);
$dir = $directions[mt_rand(0,2)];
if ($dir =="H" or $dir=="D" )
{
$limitRow = $row - $wrdLen+1;
$limitCol = $col - $wrdLen+1;
$startPointRow = 1;
$startPointCol = 1;
}
elseif ($dir=="V")
{
$limitRow = $row - $wrdLen + 1;
$limitCol = $col;
$startPointRow = 1;
$startPointCol = 1;
}
$temprow = mt_rand($startPointRow,$limitRow);
$tempcol = mt_rand($startPointCol,$limitCol);
while($wrdLen >0)
{
$thisChar= substr($wrd,0,1);
$wrd = substr($wrd,1);
$wrdLen--;
$x = 'r'.$temprow.'c'.$tempcol;
$rc[$x] = $thisChar;
$fill[$x] = '#2952f8';
if($dir=="D")
{
$tempcol++;
$temprow++;
}
elseif($dir=="V")
{
$temprow++;
}
elseif($dir=="H")
{
$tempcol++;
}
}
}
#--Display the random letters and the words
echo '<table style="border:1px solid #000">';
for ($r=1;$r<=$row;$r++)
{
echo '<tr style="border:1px solid #000">';
for ($c=1;$c<=$col;$c++)
{
$thisChar=$rc['r'.$r.'c'.$c];
$fills = $fill['r'.$r.'c'.$c];
echo '<td style="border:1px solid #000; background-color: '.$fills.'">';
echo $thisChar;
echo '</td>';
}
echo '</tr>';
}
echo '</table>';
?>
You're doing it backwards. First put in the words you want, then put in the random letters.
Before you put in each successive word, choose the random path for that word, and then along that path, check that there aren't any non-matching letters. (E.g. if 'alphabet' crosses the word 'graph' in a place where they both have a letter 'a', it's okay; otherwise, find a different spot for 'alphabet'). Finally, after all the words are in place, go through the whole thing and put random letters in the spots that don't have letters. Or, if you can't find a place for all the words, start over again.
EDIT:
How to find letters in particular spots. Okay. So, taking a look at your code, you are doing something illogical here:
$rc['r'.$r.'c'.$c] = $characters[mt_rand(0,$max)];
You are creating a one-dimensional array out of, essentially, the product of two keys. Instead, you want to do a two-dimensional array. This makes complete sense because a) you have two keys, and b) word searches have two dimensions.
$rc[$r][$c] = $characters[mt_rand(0,$max)];
So, let's start at the beginning with everything rearranged. Please note that I have rewritten things to start your row/column count at 0 instead of 1 because that's the programming convention for arrays, and I'm not going to try to bend my brain around counting from 1 just for this.
$wrdList =array('four', 'banana', 'howl');
$row = 5; //six rows counting from 0
$col = 5; //six columns counting from 0
$rc = array(); //our tableau array
$directions = array('H', 'V', 'D');
foreach ($wrdList as $wrd)
{
$found = false; // by default, no spot has been found
$tries = $row*$col; // we will try a reasonable number of times to find a spot
while(!$found && $tries > 0) {
$wrdLen = strlen($wrd);
$dir = $directions[mt_rand(0,2)];
if ($dir =="H")
{
$limitRow = $row;
$limitCol = $col - ($wrdLen - 1);
}
elseif($dir=="D")
{
$limitRow = $row - ($wrdLen - 1);
$limitCol = $col - ($wrdLen - 1);
}
elseif ($dir=="V")
{
$limitRow = $row - ($wrdLen - 1);
$limitCol = $col;
}
$temprow = mt_rand(0,$limitRow);
$tempcol = mt_rand(0,$limitCol);
//this is my temporary placement array
$placement = array();
//let's use for loop so we can capitalize on having numeric keys
$r = $temprow;
$c = $tempcol;
for($w = 0; $w < $wrdLen; $w++) {
$thisChar = $wrd{$w};
//find array keys
if($dir == 'V' || $dir == 'D') {
$r = $temprow + $w;
}
if($dir == 'H' || $dir == 'D') {
$c = $tempcol + $w;
}
//look at the current tableau
if(isset($rc[$r][$c])) { //the intended spot has a letter
if($rc[$r][$c] == $thisChar) { //the intended spot's letter is the same
$placement[$r][$c] = $thisChar;
if($w == $wrdLen-1) { // this is the last letter
$found = true; // we have found a path
}
} else {
break; //this path doesn't work
}
} else {
$placement[$r][$c] = $thisChar;
if($w == $wrdLen-1) { // this is the last letter
$found = true; // we have found a path
}
}
}
if($found) {
//put the letters out of the temporary array and into the tableau
foreach($placement as $r=>$set) {
foreach($set as $c=>$letter) {
$rc[$r][$c] = $letter;
}
}
}
$tries--;
}
//handle the error where no spot was found for the word
if(!$found) {
//your error handling here
}
}
//random fillers
$characters = range('a','z');
$max = count($characters) - 1;
for($r = 0; $r <= $row; $r++) {
for($c = 0; $c <= $col; $c++) {
if(!isset($rc[$r][$c])) {
$rc[$r][$c] = $characters[mt_rand(0,$max)];
}
}
}

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

PHP: Caching ordered integer partition algorithm

First: The problem's name in Wikipedia is "ordered partition of a set".
I have an algorithm which counts possible partitions. To speed it up, I use a cache:
function partition($intervalSize, $pieces) {
// special case of integer partitions: ordered integer partitions
// in Wikipedia it is: ordered partition of a set
global $partition_cache;
// CACHE START
$cacheId = $intervalSize.'-'.$pieces;
if (isset($partition_cache[$cacheId])) { return $partition_cache[$cacheId]; }
// CACHE END
if ($pieces == 1) { return 1; }
else {
$sum = 0;
for ($i = 1; $i < $intervalSize; $i++) {
$sum += partition(($intervalSize-$i), ($pieces-1));
}
$partition_cache[$cacheId] = $sum; // insert into cache
return $sum;
}
}
$result = partition(8, 4);
Furthermore, I have another algorithm which shows a list of these possible partitions. But it doesn't use a cache yet and so it's quite slow:
function showPartitions($prefix, $start, $finish, $numLeft) {
global $partitions;
if ($numLeft == 0 && $start == $finish) { // wenn eine Partition fertig ist dann in Array schreiben
$gruppen = split('\|', $prefix);
$partitions[] = $gruppen;
}
else {
if (strlen($prefix) > 0) { // nicht | an Anfang setzen sondern nur zwischen Gruppen
$prefix .= '|';
}
for ($i = $start + 1; $i <= $finish; $i++) {
$prefix .= chr($i+64);
showPartitions($prefix, $i, $finish, $numLeft - 1);
}
}
}
$result = showPartitions('', 0, 8, 4);
So I have two questions:
1) Is it possible to implement a cache in the second algorithm, too? If yes, could you please help me to do this?
2) Is it possible to write the results of the second algorithm into an structured array instead of a string?
I hope you can help me. Thank you very much in advance!
PS: Thanks for the two functions, simonn and Dan Dyer!
No, I don't think a cache will help you here because you're never actually performing the same calculation twice. Each call to showPartitions() has different parameters and generates a different result.
Yes, of course. You're basically using another level of nested arrays pointing to integers to replace a string of characters separated by pipe characters. (Instead of "A|B|C" you'll have array(array(1), array(2), array(3)).)
Try changing showPartitions() as such:
if ($numLeft == 0 && $start == $finish) { // wenn eine Partition fertig ist dann in Array schreiben
$partitions[] = $prefix;
}
else {
$prefix[] = array();
for ($i = $start + 1; $i <= $finish; $i++) {
$prefix[count($prefix) - 1][] = $i;
showPartitions($prefix, $i, $finish, $numLeft - 1);
}
}
and instead of calling it with an empty string for $prefix, call it with an empty array:
showPartitions(array(), 0, 8, 4);
Off topic: I rewrote the first function to be a little bit faster.
function partition($intervalSize, $pieces) {
// special case of integer partitions: ordered integer partitions
// in Wikipedia it is: ordered partition of a set
// CACHE START
static $partition_cache = array();
if (isset($partition_cache[$intervalSize][$pieces])) {
return $partition_cache[$intervalSize][$pieces];
}
// CACHE END
if ($pieces === 1) {
return 1;
}
if ($intervalSize === 1) {
return 0;
}
$sum = 0;
$subPieces = $pieces - 1;
$i = $intervalSize;
while (--$i) {
$sum += partition($i, $subPieces);
}
$partition_cache[$intervalSize][$pieces] = $sum; // insert into cache
return $sum;
}
Although this is a bit old, nevertheless,
a PHP Class which implements various combinatorics/simulation methods including partitions/permutations/combinations etc.. in an efficient way
https://github.com/foo123/Simulacra/blob/master/Simulacra.php
PS: i am the author

Categories