Primary Key from 1 to 01, 2 to 02, etc - php

I have a primary key field (news_id) in the news table
it start from 1, 2, 3, 4 and so on..
However, I like to change to 01, 02, 03, 04,e tc ... is that possible?
If not, how can that be done in PHP?

Manipulating the keys directly is a bad idea in 99% of cases.
The best way to go is probably to change the format when outputting the keys like shown in this question:
$key = 4;
echo sprintf('%02d', $key); // outputs 04

If it's for output, just add a 0 if the primary key is less than 10:
if($result['id'] < 10){
echo '0' . $result['id'];
}

Okay... Another way of doing this is... (though overly logical and mathematical...)
$id = 5;
$noOfZeros = 2; // i.e. you are converting 2 to 002.
$divider = pow(10,$noOfZeros); // Now you are creating a divider (/100 in this case)
$id = $id / $divider; // dividing the id by 100. i.e. 5 gets converted to 0.05
$zeroedId = str_replace(".","",$id); // finally replace the "." with nothing... so 0.05 becomes 005 :).

Related

auto Increment the last four digit value from the varchar field in php codeigniter

In my database field i have varchar field like this format HPDO1209180000.What i am doing is if i dont have any data i am adding four digits at last it is working fine.And if if it is there i am incrementing one each time.but the problem is after 0001 also incrementing all the 0001 only not 0002 like that.I am doing like this please help me.thanks in Advance.
$this->db->select('grmno');
$this->db->from('procurement_receive_material');
$this->db->where('DATE(createdon)',$currentdate);
$this->db->where('SUBSTRING(grmno,1,10)',$grn_code);
$this->db->order_by('prmid','desc');
$query = $this->db->get();
if($query->num_rows()>0){
$output = $query->row();
$grmNumber = $output->grmno;
//after 0001 also it incrementing 0001 not incrementing;
$inrno=str_pad((int)$grmNumber+1, 4, 0, STR_PAD_LEFT);
$grmNumber=$vendor_result.$branch_result.$currentDate.$currentmonth.$currentyear.$inrno;
}else{
$grmNumber = $vendor_result.$branch_result.$currentDate.$currentmonth.$currentyear.'0000';
}
If I'm reading this correctly, it looks like this line:
$inrno=str_pad((int)$grmNumber+1, 4, 0, STR_PAD_LEFT);
Is taking the int value of $output->grmno. And this field, again if I'm interpreting your problem directly, can be evaluated as something like this:
HPDO1209180001
The integer value of such a string would be 0. Hence, why they always end up as 0001.
To fix this, you need to grab the last 4 characters of the string, and increment only that. For example:
$suffix = substr($grmNumber, -4);
$newsuffix = intval($suffix) + 1;
$irno = str_pad($newsuffix, 4, 0, STR_PAD_LEFT);
This would properly grab the last 4 digits, add one to the value, and again pad it with zeros to be added to the new string.

PHP: Finding a set of numbers in a database that sums up to a particular number

Firstly, i am a php newbie... so i still code and understand php procedurally. that said,
i have a collection of numbers (amount) stored in a database.
Question: Using PHP and mySQL,
Whats the best way to spool this info out of a database such that the amount will be associated with its transaction ID
Most importantly, i need to find a matching set of numbers in the db that equals a sum of 29.
Below is the Transaction Table , Transaction_tlb, for my Database mydb
Transaction_ID | Name | Date | Amount
---------------|------------------|-----------------|------------
11012 | Jonathan May | 6/12/2016 | 84
21012 | John Pedesta | 6/12/2016 | 38
31012 | Mary Johnson | 1/01/2017 | 12
41012 | John Johnson | 8/01/2017 | 13
51012 | Keith Jayron | 8/01/2017 | 17
61012 | Brenda Goldson | 8/01/2017 | 2
71012 | Joshua Traveen | 8/01/2017 | 78
81012 | Remy ma Goldstein| 8/01/2017 | 1
91012 | Barbie Traveen | 8/01/2017 | 1
Now, i have an idea..but its not efficient. I am going to try every possible case. meaning if i have n values to check, the time complexity is going to be about 2^n. this is highly inefficient (plus, i dont even know if my code makes any sense. (see below)
I saw a similar example in this YouTube video: https://www.youtube.com/watch?v=XKu_SEDAykw&t
but, Im not sure exactly how to write the code in php.
The code:
<?php
if (!mysql_connect("localhost", "mysql_user", "mysql_password") || !mysql_select_db("mydb")) {
die("Could not connect: " . mysql_error()); } //End DB Connect
$capacity = 29; //Knapsack Capacity or Sum
//Select Transact ID and Value from the Database where Amount is <= Capacity
$fetchQuery = "SELECT 'Transaction_ID', 'Amount' FROM 'Transaction_tlb' WHERE 'Amount' <= $capacity";
$components = array(); //new array to hold components
if ($queryResults = mysql_query($fetchQuery)) {
//check if data was pulled
if (mysql_num_row($queryResults) != NULL) {
while ($row = mysqli_fetch_assoc($queryResults) {
$components[$row['Transaction_ID']] = $row['Amount'];
}
}
}
/* Correct me if i am wrong, but, Components associative array Should be something like
$components = array('11012'=> 84, '21012'=> 38, '31012'=> 12, '41012'=> 13, '51012'=> 17,
'61012'=> 2, '71012'=> 78, '81012'=> 1, '91012'=> 1);
*/
$components = asort($components) // sort array in ascending order
$componentCount = count($component)
function match ($componentCount, $capacity) {
$temp = match (($componentCount - 1), $capacity);
$temp1 = $component[$componentCount] + match (($componentCount - 1), ($capacity - $component[$componentCount]));
$result = max($temp, $temp1);
return $result;
}
}?>
can anyone please point me in the right direction? this code doesn work... and even if it works... the method is not efficient at all. what happens when Ive got 3 million records to work with? i need help please.
You can formulate your problem in terms of the 0/1 Knapsack problem. Ready-to-use implementation in PHP is available.
Using the function knapSolveFast2 defined in the linked page, one could proceed as in the example below. The idea here is that you set the "weights" entering the Knapsack algorithm equal to the values themselves.
$components = array(84, 38, 12, 13, 17, 2, 78, 1, 1);
$m = array();
list($m4, $pickedItems) = knapSolveFast2($components, $components, sizeof($components)-1, 29, $m);
echo "sum: $m4\n";
echo "selected components:\n";
foreach($pickedItems as $idx){
echo "\t$idx --> $components[$idx]\n";
}
which yields:
sum: 29
selected components:
2 --> 12
4 --> 17
Notes:
you could modify your SQL query in order to skip rows with amount larger than the required sum (29)
the function above will pick one solution (assuming that it exists), it won't provide all of them
one should check whether the return value $m4 is indeed equal to the specified sum (29) - as the algorithm works, the specified amount is only the upper limit which is not guaranteed to be attained (for example for 37 instead of 29, the return value is only 34 since there is no combination of the input numbers the sum of which would yield 37)
This is really a knapsack problem, but I will try to give a full solution which isn't optimal, but illustrates a full strategy for solving your problem.
First of all, you can do this with just one iteration over the array of numbers with no recursion and no pre-sorting needed. Dynamic programming is all you need, keeping track of all previously possible partial-sum 'paths'. The idea is somewhat similar to your described recursive method, but we can do it iteratively and without presorting.
Assuming an input array of [84, 38, 12, 13, 17, 2, 78, 1, 1] and a target of 29, we loop over the numbers like so:
* 84 - too big, move on
* 38 - too big, move on
* 12 - gives us a subtarget of 29-12 = 17
subtargets:
17 (paths: 12)
* 13 - gives us a subtarget of 29-13=16
subtargets:
16 (paths: 13)
17 (paths: 12)
* 17 - is a subtarget, fulfilling the '12' path;
and gives us a subtarget of 29-17=12
subtargets:
12 (paths: 17)
16 (paths: 13)
17 (paths: 12)
solutions:
12+17
etc.
The trick here is that while looping over the numbers, we keep a lookup table of subTargets, which are the numbers which would give us a solution using one or more combinations ('paths') of previously seen numbers. If a new number is a subTarget, we add to our list of solutions; if not then we append to existing paths where num<subTarget and move on.
A quick and dirty PHP function to do this:
// Note: only positive non-zero integer values are supported
// Also, we may return duplicate addend sets where the only difference is the order
function findAddends($components, $target)
{
// A structure to hold our partial result paths
// The integer key is the sub-target and the value is an array of string representations
// of the 'paths' to get to that sub-target. E.g. for target=29
// subTargets = {
// 26: { '=3':true },
// 15: { '=12+2':true, '=13+1':true }
// }
// We are (mis)using associative arrays as HashSets
$subTargets = array();
// And our found solutions, stored as string keys to avoid duplicates (again using associative array as a HashSet)
$solutions = array();
// One loop to Rule Them All
echo 'Looping over the array of values...' . PHP_EOL;
foreach ($components as $num) {
echo 'Processing number ' . $num . '...' . PHP_EOL;
if ($num > $target) {
echo $num . ' is too large, so we skip it' . PHP_EOL;
continue;
}
if ($num == $target) {
echo $num . ' is an exact match. Adding to solutions..' . PHP_EOL;
$solutions['='.$num] = true;
continue;
}
// For every subtarget that is larger than $num we get a new 'sub-subtarget' as well
foreach ($subTargets as $subTarget => $paths) {
if ($num > $subTarget) { continue; }
if ($num == $subTarget) {
echo 'Solution(s) found for ' . $num . ' with previous sub-target. Adding to solutions..' . PHP_EOL;
foreach ($paths as $path => $bool) {
$solutions[$path . '+' . $num] = true;
}
continue;
}
// Our new 'sub-sub-target' is:
$subRemainder = $subTarget-$num;
// Add the new sub-sub-target including the 'path' of addends to get there
if ( ! isset($subTargets[$subRemainder])) { $subTargets[$subRemainder] = array(); }
// For each path to the original sub-target, we add the $num which creates a new path to the subRemainder
foreach ($paths as $path => $bool) {
$subTargets[$subRemainder][$path.'+'.$num] = true;
}
}
// Subtracting the number from our original target gives us a new sub-target
$remainder = $target - $num;
// Add the new sub-target including the 'path' of addends to get there
if ( ! isset($subTargets[$remainder])) { $subTargets[$remainder] = array(); }
$subTargets[$remainder]['='.$num] = true;
}
return $solutions;
}
Run the code like so:
$componentArr = array(84, 38, 12, 13, 17, 2, 78, 1, 1);
$addends = findAddends($componentArr, 29);
echo 'Result:'.PHP_EOL;
foreach ($addends as $addendSet => $bool) {
echo $addendSet . PHP_EOL;
}
which outputs:
Looping over the array of values...
Processing number 84...
84 is too large, so we skip it
Processing number 38...
38 is too large, so we skip it
Processing number 12...
Processing number 13...
Processing number 17...
Solution(s) found for 17 with previous sub-target. Adding to solutions..
Processing number 2...
Processing number 78...
78 is too large, so we skip it
Processing number 1...
Processing number 1...
Solution(s) found for 1 with previous sub-target. Adding to solutions..
Result:
=12+17
=12+13+2+1+1

Php getting ten's value

If I have values
2014 I need to get the 14,
2022 I need to get the 22,
2122 I need to get 122,
I could do:
$value = 2000 - $value;
But what if it's
1014? where I need to get the 14?
Is there any way to get the ten's value of any number? or 100's value? Thanks
Yes there is. Use modulus:
<?php
echo 2014%1000;
echo 2022%1000;
echo 2122%1000;
echo 1014%1000;
?>
Yes, there is:
$result = $value % 1000;
If they aren't necessarily numbers and you just want the two left values, you could also use:
substr()
$result = substr($value, -2); // returns last two characters
Yes, If you use % , you can get the reminder value , if your value = 1234 , then 1234 % 1000 will return 234 , similarly it goes on.
$value = 1022 ; // your value
$tens value = $value % 1000;
echo $tens value ;

Can the for loop be eliminated from this piece of PHP code?

I have a range of whole numbers that might or might not have some numbers missing. Is it possible to find the smallest missing number without using a loop structure? If there are no missing numbers, the function should return the maximum value of the range plus one.
This is how I solved it using a for loop:
$range = [0,1,2,3,4,6,7];
// sort just in case the range is not in order
asort($range);
$range = array_values($range);
$first = true;
for ($x = 0; $x < count($range); $x++)
{
// don't check the first element
if ( ! $first )
{
if ( $range[$x - 1] + 1 !== $range[$x])
{
echo $range[$x - 1] + 1;
break;
}
}
// if we're on the last element, there are no missing numbers
if ($x + 1 === count($range))
{
echo $range[$x] + 1;
}
$first = false;
}
Ideally, I'd like to avoid looping completely, as the range can be massive. Any suggestions?
Algo solution
There is a way to check if there is a missing number using an algorithm. It's explained here. Basically if we need to add numbers from 1 to 100. We don't need to calculate by summing them we just need to do the following: (100 * (100 + 1)) / 2. So how is this going to solve our issue ?
We're going to get the first element of the array and the last one. We calculate the sum with this algo. We then use array_sum() to calculate the actual sum. If the results are the same, then there is no missing number. We could then "backtrack" the missing number by substracting the actual sum from the calculated one. This of course only works if there is only one number missing and will fail if there are several missing. So let's put this in code:
$range = range(0,7); // Creating an array
echo check($range) . "\r\n"; // check
unset($range[3]); // unset offset 3
echo check($range); // check
function check($array){
if($array[0] == 0){
unset($array[0]); // get ride of the zero
}
sort($array); // sorting
$first = reset($array); // get the first value
$last = end($array); // get the last value
$sum = ($last * ($first + $last)) / 2; // the algo
$actual_sum = array_sum($array); // the actual sum
if($sum == $actual_sum){
return $last + 1; // no missing number
}else{
return $sum - $actual_sum; // missing number
}
}
Output
8
3
Online demo
If there are several numbers missing, then just use array_map() or something similar to do an internal loop.
Regex solution
Let's take this to a new level and use regex ! I know it's nonsense, and it shouldn't be used in real world application. The goal is to show the true power of regex :)
So first let's make a string out of our range in the following format: I,II,III,IIII for range 1,3.
$range = range(0,7);
if($range[0] === 0){ // get ride of 0
unset($range[0]);
}
$str = implode(',', array_map(function($val){return str_repeat('I', $val);}, $range));
echo $str;
The output should be something like: I,II,III,IIII,IIIII,IIIIII,IIIIIII.
I've come up with the following regex: ^(?=(I+))(^\1|,\2I|\2I)+$. So what does this mean ?
^ # match begin of string
(?= # positive lookahead, we use this to not "eat" the match
(I+) # match I one or more times and put it in group 1
) # end of lookahead
( # start matching group 2
^\1 # match begin of string followed by what's matched in group 1
| # or
,\2I # match a comma, with what's matched in group 2 (recursive !) and an I
| # or
\2I # match what's matched in group 2 and an I
)+ # repeat one or more times
$ # match end of line
Let's see what's actually happening ....
I,II,III,IIII,IIIII,IIIIII,IIIIIII
^
(I+) do not eat but match I and put it in group 1
I,II,III,IIII,IIIII,IIIIII,IIIIIII
^
^\1 match what was matched in group 1, which means I gets matched
I,II,III,IIII,IIIII,IIIIII,IIIIIII
^^^ ,\2I match what was matched in group 1 (one I in thise case) and add an I to it
I,II,III,IIII,IIIII,IIIIII,IIIIIII
^^^^ \2I match what was matched previously in group 2 (,II in this case) and add an I to it
I,II,III,IIII,IIIII,IIIIII,IIIIIII
^^^^^ \2I match what was matched previously in group 2 (,III in this case) and add an I to it
We're moving forward since there is a + sign which means match one or more times,
this is actually a recursive regex.
We put the $ to make sure it's the end of string
If the number of I's don't correspond, then the regex will fail.
See it working and failing. And Let's put it in PHP code:
$range = range(0,7);
if($range[0] === 0){
unset($range[0]);
}
$str = implode(',', array_map(function($val){return str_repeat('I', $val);}, $range));
if(preg_match('#^(?=(I*))(^\1|,\2I|\2I)+$#', $str)){
echo 'works !';
}else{
echo 'fails !';
}
Now let's take in account to return the number that's missing, we will remove the $ end character to make our regex not fail, and we use group 2 to return the missed number:
$range = range(0,7);
if($range[0] === 0){
unset($range[0]);
}
unset($range[2]); // remove 2
$str = implode(',', array_map(function($val){return str_repeat('I', $val);}, $range));
preg_match('#^(?=(I*))(^\1|,\2I|\2I)+#', $str, $m); // REGEEEEEX !!!
$n = strlen($m[2]); //get the length ie the number
$sum = array_sum($range); // array sum
if($n == $sum){
echo $n + 1; // no missing number
}else{
echo $n - 1; // missing number
}
Online demo
EDIT: NOTE
This question is about performance. Functions like array_diff and array_filter are not magically fast. They can add a huge time penalty. Replacing a loop in your code with a call to array_diff will not magically make things fast, and will probably make things slower. You need to understand how these functions work if you intend to use them to speed up your code.
This answer uses the assumption that no items are duplicated and no invalid elements exist to allow us to use the position of the element to infer its expected value.
This answer is theoretically the fastest possible solution if you start with a sorted list. The solution posted by Jack is theoretically the fastest if sorting is required.
In the series [0,1,2,3,4,...], the n'th element has the value n if no elements before it are missing. So we can spot-check at any point to see if our missing element is before or after the element in question.
So you start by cutting the list in half and checking to see if the item at position x = x
[ 0 | 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 ]
^
Yup, list[4] == 4. So move halfway from your current point the end of the list.
[ 0 | 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 ]
^
Uh-oh, list[6] == 7. So somewhere between our last checkpoint and the current one, one element was missing. Divide the difference in half and check that element:
[ 0 | 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 ]
^
In this case, list[5] == 5
So we're good there. So we take half the distance between our current check and the last one that was abnormal. And oh.. it looks like cell n+1 is one we already checked. We know that list[6]==7 and list[5]==5, so the element number 6 is the one that's missing.
Since each step divides the number of elements to consider in half, you know that your worst-case performance is going to check no more than log2 of the total list size. That is, this is an O(log(n)) solution.
If this whole arrangement looks familiar, It's because you learned it back in your second year of college in a Computer Science class. It's a minor variation on the binary search algorithm--one of the most widely used index schemes in the industry. Indeed this question appears to be a perfectly-contrived application for this searching technique.
You can of course repeat the operation to find additional missing elements, but since you've already tested the values at key elements in the list, you can avoid re-checking most of the list and go straight to the interesting ones left to test.
Also note that this solution assumes a sorted list. If the list isn't sorted then obviously you sort it first. Except, binary searching has some notable properties in common with quicksort. It's quite possible that you can combine the process of sorting with the process of finding the missing element and do both in a single operation, saving yourself some time.
Finally, to sum up the list, that's just a stupid math trick thrown in for good measure. The sum of a list of numbers from 1 to N is just N*(N+1)/2. And if you've already determined that any elements are missing, then obvously just subtract the missing ones.
Technically, you can't really do without the loop (unless you only want to know if there's a missing number). However, you can accomplish this without first sorting the array.
The following algorithm uses O(n) time with O(n) space:
$range = [0, 1, 2, 3, 4, 6, 7];
$N = count($range);
$temp = str_repeat('0', $N); // assume all values are out of place
foreach ($range as $value) {
if ($value < $N) {
$temp[$value] = 1; // value is in the right place
}
}
// count number of leading ones
echo strspn($temp, '1'), PHP_EOL;
It builds an ordered identity map of N entries, marking each value against its position as "1"; in the end all entries must be "1", and the first "0" entry is the smallest value that's missing.
Btw, I'm using a temporary string instead of an array to reduce physical memory requirements.
I honestly don't get why you wouldn't want to use a loop. There's nothing wrong with loops. They're fast, and you simply can't do without them. However, in your case, there is a way to avoid having to write your own loops, using PHP core functions. They do loop over the array, though, but you simply can't avoid that.
Anyway, I gather what you're after, can easily be written in 3 lines:
function highestPlus(array $in)
{
$compare = range(min($in), max($in));
$diff = array_diff($compare, $in);
return empty($diff) ? max($in) +1 : $diff[0];
}
Tested with:
echo highestPlus(range(0,11));//echoes 12
$arr = array(9,3,4,1,2,5);
echo highestPlus($arr);//echoes 6
And now, to shamelessly steal Pé de Leão's answer (but "augment" it to do exactly what you want):
function highestPlus(array $range)
{//an unreadable one-liner... horrid, so don't, but know that you can...
return min(array_diff(range(0, max($range)+1), $range)) ?: max($range) +1;
}
How it works:
$compare = range(min($in), max($in));//range(lowest value in array, highest value in array)
$diff = array_diff($compare, $in);//get all values present in $compare, that aren't in $in
return empty($diff) ? max($in) +1 : $diff[0];
//-------------------------------------------------
// read as:
if (empty($diff))
{//every number in min-max range was found in $in, return highest value +1
return max($in) + 1;
}
//there were numbers in min-max range, not present in $in, return first missing number:
return $diff[0];
That's it, really.
Of course, if the supplied array might contain null or falsy values, or even strings, and duplicate values, it might be useful to "clean" the input a bit:
function highestPlus(array $in)
{
$clean = array_filter(
$in,
'is_numeric'//or even is_int
);
$compare = range(min($clean), max($clean));
$diff = array_diff($compare, $clean);//duplicates aren't an issue here
return empty($diff) ? max($clean) + 1; $diff[0];
}
Useful links:
The array_diff man page
The max and min functions
Good Ol' range, of course...
The array_filter function
The array_map function might be worth a look
Just as array_sum might be
$range = array(0,1,2,3,4,6,7);
// sort just in case the range is not in order
asort($range);
$range = array_values($range);
$indexes = array_keys($range);
$diff = array_diff($indexes,$range);
echo $diff[0]; // >> will print: 5
// if $diff is an empty array - you can print
// the "maximum value of the range plus one": $range[count($range)-1]+1
echo min(array_diff(range(0, max($range)+1), $range));
Simple
$array1 = array(0,1,2,3,4,5,6,7);// array with actual number series
$array2 = array(0,1,2,4,6,7); // array with your custom number series
$missing = array_diff($array1,$array2);
sort($missing);
echo $missing[0];
$range = array(0,1,2,3,4,6,7);
$max=max($range);
$expected_total=($max*($max+1))/2; // sum if no number was missing.
$actual_total=array_sum($range); // sum of the input array.
if($expected_total==$actual_total){
echo $max+1; // no difference so no missing number, then echo 1+ missing number.
}else{
echo $expected_total-$actual_total; // the difference will be the missing number.
}
you can use array_diff() like this
<?php
$range = array("0","1","2","3","4","6","7","9");
asort($range);
$len=count($range);
if($range[$len-1]==$len-1){
$r=$range[$len-1];
}
else{
$ref= range(0,$len-1);
$result = array_diff($ref,$range);
$r=implode($result);
}
echo $r;
?>
function missing( $v ) {
static $p = -1;
$d = $v - $p - 1;
$p = $v;
return $d?1:0;
}
$result = array_search( 1, array_map( "missing", $ARRAY_TO_TEST ) );

PHP: find two or more numbers from a list of numbers that add up towards a given amount

I am trying to create a little php script that can make my life a bit easier.
Basically, I am going to have 21 text fields on a page where I am going to input 20 different numbers. In the last field I will enter a number let's call it the TOTAL AMOUNT. All I want the script to do is to point out which numbers from the 20 fields added up will come up to TOTAL AMOUNT.
Example:
field1 = 25.23
field2 = 34.45
field3 = 56.67
field4 = 63.54
field5 = 87.54
....
field20 = 4.2
Total Amount = 81.90
Output: field1 + fields3 = 81.90
Some of the fields might have 0 as value because sometimes I only need to enter 5-15 fields and the maximum will be 20.
If someone can help me out with the php code for this, will be greatly appreciated.
If you look at oezis algorithm one drawback is immediately clear: It spends very much time summing up numbers which are already known not to work. (For example if 1 + 2 is already too big, it doesn't make any sense to try 1 + 2 + 3, 1 + 2 + 3 + 4, 1 + 2 + 3 + 4 + 5, ..., too.)
Thus I have written an improved version. It does not use bit magic, it makes everything manual. A drawback is, that it requires the input values to be sorted (use rsort). But that shouldn't be a big problem ;)
function array_sum_parts($vals, $sum){
$solutions = array();
$pos = array(0 => count($vals) - 1);
$lastPosIndex = 0;
$currentPos = $pos[0];
$currentSum = 0;
while (true) {
$currentSum += $vals[$currentPos];
if ($currentSum < $sum && $currentPos != 0) {
$pos[++$lastPosIndex] = --$currentPos;
} else {
if ($currentSum == $sum) {
$solutions[] = array_slice($pos, 0, $lastPosIndex + 1);
}
if ($lastPosIndex == 0) {
break;
}
$currentSum -= $vals[$currentPos] + $vals[1 + $currentPos = --$pos[--$lastPosIndex]];
}
}
return $solutions;
}
A modified version of oezis testing program (see end) outputs:
possibilities: 540
took: 3.0897309780121
So it took only 3.1 seconds to execute, whereas oezis code executed 65 seconds on my machine (yes, my machine is very slow). That's more than 20 times faster!
Furthermore you may notice, that my code found 540 instead of 338 possibilities. This is because I adjusted the testing program to use integers instead of floats. Direct floating point comparison is rarely the right thing to do, this is a great example why: You sometimes get 59.959999999999 instead of 59.96 and thus the match will not be counted. So, if I run oezis code with integers it finds 540 possibilities, too ;)
Testing program:
// Inputs
$n = array();
$n[0] = 6.56;
$n[1] = 8.99;
$n[2] = 1.45;
$n[3] = 4.83;
$n[4] = 8.16;
$n[5] = 2.53;
$n[6] = 0.28;
$n[7] = 9.37;
$n[8] = 0.34;
$n[9] = 5.82;
$n[10] = 8.24;
$n[11] = 4.35;
$n[12] = 9.67;
$n[13] = 1.69;
$n[14] = 5.64;
$n[15] = 0.27;
$n[16] = 2.73;
$n[17] = 1.63;
$n[18] = 4.07;
$n[19] = 9.04;
$n[20] = 6.32;
// Convert to Integers
foreach ($n as &$num) {
$num *= 100;
}
$sum = 57.96 * 100;
// Sort from High to Low
rsort($n);
// Measure time
$start = microtime(true);
echo 'possibilities: ', count($result = array_sum_parts($n, $sum)), '<br />';
echo 'took: ', microtime(true) - $start;
// Check that the result is correct
foreach ($result as $element) {
$s = 0;
foreach ($element as $i) {
$s += $n[$i];
}
if ($s != $sum) echo '<br />FAIL!';
}
var_dump($result);
sorry for adding a new answer, but this is a complete new solution to solve all problems of life, universe and everything...:
function array_sum_parts($n,$t,$all=false){
$count_n = count($n); // how much fields are in that array?
$count = pow(2,$count_n); // we need to do 2^fields calculations to test all possibilities
# now i want to look at every number from 1 to $count, where the number is representing
# the array and add up all array-elements which are at positions where my actual number
# has a 1-bit
# EXAMPLE:
# $i = 1 in binary mode 1 = 01 i'll use ony the first array-element
# $i = 10 in binary mode 10 = 1010 ill use the secont and the fourth array-element
# and so on... the number of 1-bits is the amount of numbers used in that try
for($i=1;$i<=$count;$i++){ // start calculating all possibilities
$total=0; // sum of this try
$anzahl=0; // counter for 1-bits in this try
$k = $i; // store $i to another variable which can be changed during the loop
for($j=0;$j<$count_n;$j++){ // loop trough array-elemnts
$total+=($k%2)*$n[$j]; // add up if the corresponding bit of $i is 1
$anzahl+=($k%2); // add up the number of 1-bits
$k=$k>>1; //bit-shift to the left for looking at the next bit in the next loop
}
if($total==$t){
$loesung[$i] = $anzahl; // if sum of this try is the sum we are looking for, save this to an array (whith the number of 1-bits for sorting)
if(!$all){
break; // if we're not looking for all solutions, make a break because the first one was found
}
}
}
asort($loesung); // sort all solutions by the amount of numbers used
// formating the solutions to getting back the original array-keys (which shoud be the return-value)
foreach($loesung as $val=>$anzahl){
$bit = strrev(decbin($val));
$total=0;
$ret_this = array();
for($j=0;$j<=strlen($bit);$j++){
if($bit[$j]=='1'){
$ret_this[] = $j;
}
}
$ret[]=$ret_this;
}
return $ret;
}
// Inputs
$n[0]=6.56;
$n[1]=8.99;
$n[2]=1.45;
$n[3]=4.83;
$n[4]=8.16;
$n[5]=2.53;
$n[6]=0.28;
$n[7]=9.37;
$n[8]=0.34;
$n[9]=5.82;
$n[10]=8.24;
$n[11]=4.35;
$n[12]=9.67;
$n[13]=1.69;
$n[14]=5.64;
$n[15]=0.27;
$n[16]=2.73;
$n[17]=1.63;
$n[18]=4.07;
$n[19]=9.04;
$n[20]=6.32;
// Output
$t=57.96;
var_dump(array_sum_parts($n,$t)); //returns one possible solution (fuc*** fast)
var_dump(array_sum_parts($n,$t,true)); // returns all possible solution (relatively fast when you think of all the needet calculations)
if you don't use the third parameter, it returns the best (whith the least amount numbers used) solution as array (whith keys of the input-array) - if you set the third parameter to true, ALL solutions are returned (for testing, i used the same numbers as zaf in his post - there are 338 solutions in this case, found in ~10sec on my machine).
EDIT:
if you get all, you get the results ordered by which is "best" - whithout this, you only get the first found solution (which isn't necessarily the best).
EDIT2:
to forfil the desire of some explanation, i commented the essential parts of the code . if anyone needs more explanation, please ask
1. Check and eliminate fields values more than 21st field
2. Check highest of the remaining, Add smallest,
3. if its greater than 21st eliminate highest (iterate this process)
4. If lower: Highest + second Lowest, if equal show result.
5. if higher go to step 7
6. if lower go to step 4
7. if its lower than add second lowest, go to step 3.
8. if its equal show result
This is efficient and will take less execution time.
Following method will give you an answer... almost all of the time. Increase the iterations variable to your taste.
<?php
// Inputs
$n[1]=8.99;
$n[2]=1.45;
$n[3]=4.83;
$n[4]=8.16;
$n[5]=2.53;
$n[6]=0.28;
$n[7]=9.37;
$n[8]=0.34;
$n[9]=5.82;
$n[10]=8.24;
$n[11]=4.35;
$n[12]=9.67;
$n[13]=1.69;
$n[14]=5.64;
$n[15]=0.27;
$n[16]=2.73;
$n[17]=1.63;
$n[18]=4.07;
$n[19]=9.04;
$n[20]=6.32;
// Output
$t=57.96;
// Let's try to do this a million times randomly
// Relax, thats less than a blink
$iterations=1000000;
while($iterations-->0){
$z=array_rand($n, mt_rand(2,20));
$total=0;
foreach($z as $x) $total+=$n[$x];
if($total==$t)break;
}
// If we did less than a million times we have an answer
if($iterations>0){
$total=0;
foreach($z as $x){
$total+=$n[$x];
print("[$x] + ". $n[$x] . " = $total<br/>");
}
}
?>
One solution:
[1] + 8.99 = 8.99
[4] + 8.16 = 17.15
[5] + 2.53 = 19.68
[6] + 0.28 = 19.96
[8] + 0.34 = 20.3
[10] + 8.24 = 28.54
[11] + 4.35 = 32.89
[13] + 1.69 = 34.58
[14] + 5.64 = 40.22
[15] + 0.27 = 40.49
[16] + 2.73 = 43.22
[17] + 1.63 = 44.85
[18] + 4.07 = 48.92
[19] + 9.04 = 57.96
A probably inefficient but simple solution with backtracking
function subset_sums($a, $val, $i = 0) {
$r = array();
while($i < count($a)) {
$v = $a[$i];
if($v == $val)
$r[] = $v;
if($v < $val)
foreach(subset_sums($a, $val - $v, $i + 1) as $s)
$r[] = "$v $s";
$i++;
}
return $r;
}
example
$ns = array(1, 2, 6, 7, 11, 5, 8, 9, 3);
print_r(subset_sums($ns, 11));
result
Array
(
[0] => 1 2 5 3
[1] => 1 2 8
[2] => 1 7 3
[3] => 2 6 3
[4] => 2 9
[5] => 6 5
[6] => 11
[7] => 8 3
)
i don't think the answer isn't as easy as nik mentioned. let's ay you have the following numbers:
1 2 3 6 8
looking for an amount of 10
niks solution would do this (if i understand it right):
1*8 = 9 = too low
adding next lowest (2) = 11 = too high
now he would delete the high number and start again taking the new highest
1*6 = 7 = too low
adding next lowest (2) = 9 = too low
adding next lowest (3) = 12 = too high
... and so on, where the perfect answer would simply
be 8+2 = 10... i think the only solution is trying every possible combination of
numbers and stop if the amaunt you are looking for is found (or realy calculate all, if there are different solutions and save which one has used least numbers).
EDIT: realy calculating all possible combiations of 21 numbers will end up in realy, realy, realy much calculations - so there must be any "intelligent" solution for adding numbers in a special order (lik that one in niks post - with some improvements, maybe that will bring us to a reliable solution)
Without knowing if this is a homework assignment or not, I can give you some pseudo code as a hint for a possible solution, note the solution is not very efficient, more of a demonstration.
Hint:
Compare each field value to all field value and at each iteration check if their sum is equal to TOTAL_AMOUNT.
Pseudo code:
for i through field 1-20
for j through field 1-20
if value of i + value of j == total_amount
return i and j
Update:
What you seem to be having is the Subset sum problem, given within the Wiki link is pseudo code for the algorithm which might help point you in the right direction.

Categories