Split Number by a number format - php

I truly thinking too long to find the logic/ algorithm to do this thing. Then I just use if else but I know this is bad because there will be too much statement.
I have number format group below to split the input number :
01 (Get 14 digits after this number format)
3101 (Get 6 digits after this number format)
3102 (Get 6 digits)
3202 (Get 6 digits)
13 (Get 6 digits)
15 (Get 6 digits)
11 (Get 6 digits)
21 (Get the rest)
Some of rules :
01 always in first sequence
21 always in last sequence
other number format except 01 and 21 can be in any sequence position.
The same prefix number format cannot be repeat
example, Input Number : 010069008517306731020020001319100421191004091395
The Result Should be :
01 : 00690085173067
3102 : 002000
13 : 191004
21 : 191004091395
Currently I only use IF ELSE statement to get the digits after.
This is my pieces of code using PHP. This code can only handle that example input above. There will be possibility of other sequence number format as per rules, but it will difficult if only use if else statement like this.
$first = substr($input, 0, 2);
if ($first == 01) {
$itemCode = substr($input, 2, 14); // get the 6 digits after 01
$second = substr($input, 16, 4);
if ($second == 3102) {
$quantity = substr($input, 20, 6); // get the 6 digits after 3102
$third = substr($input, 26, 2);
if ($third == 13) {
$packedDate = substr($input, 28, 6); // get the 6 digits after 13
$fourth = substr($input, 34, 2);
if ($fourth == 21) {
$serialNumber = substr($scanner, 36); // get the rest number after 21
}
}
}
}
Is there any good way to solve this thing?

If the prefixes won't repeat, you can use preg_match_all to match the prefixes with each of their trailing digits, using array_combine to create arrays of digits indexed by their prefixes:
$input = '010069008517306731020020001319100421191004091395';
if (preg_match_all('/(01)(\d{14})|(310[12]|3202|1[135])(\d{6})|(21)(\d+)/', $input, $matches)) {
$numbers = array_filter(array_combine($matches[1], $matches[2]) +
array_combine($matches[3], $matches[4]) +
array_combine($matches[5], $matches[6]));
print_r($numbers);
}
else {
echo "Invalid input!";
}
Output:
Array
(
[01] => 00690085173067
[3102] => 002000
[13] => 191004
[21] => 191004091395
)
Demo on 3v4l.org

Related

How Do I Implement Incomplete Answer to Old Question

This comment looks like it would work if the author included the value for $numbers. They say it is some type of array, but don't provide enough information to replicate it. I picture some hard coded array ranging from 0 to 9, but I can't help think that such an array would miss numbers greater than 9. What does the numbers array in this example look like?
$text = "1 out of 23";
if(preg_match_all('/\d+/', $text, $numbers))
$lastnum = end($numbers[0]);
I would just post a comment asking whoever wrote that to paste the value for $numbers, but it says I need reputation points to do that.
See How do I grab last number in a string in PHP?
To answer your initial question print_r() can be used to output all contents of an array. e.g. print_r($numbers)
https://3v4l.org/2jA1b
To explain the code:
\d is a single number
+ is a quantifier meaning one or more of the previous character or group
so this would find all numbers in a string. The $numbers[0] would be all numbers, 1 per index, and the end() pulls to the last number/index. Each index would be a number, the 0 is all matches, each indice at the root level is a capture group.
This code wouldn't work as intended for decimals or comma delimited integers. In those cases the numbers would be split up at the delimiter. 1.0 would become 1 and 0 (2 different numbers).
You could rewrite this as:
$text = "1 out of 23";
if(preg_match('/.*\K\D\d+/', $text, $numbers))
echo $numbers[0];
so the end function is not needed. This pulls everything until the last number then forgets everything before the last number.
What you are trying to do is likely easier using preg_split instead of preg_match_all. We can split the input text by the matched regex (digits) and then rebuild the string while incrementing the numbers as we go.
<?php
function incrementNumbers($text) {
// NOTES:
// parenthesis are important in the regex in order to return the captured values
// the -? will capture negative numbers too if necessary
// PREG_SPLIT_DELIM_CAPTURE allows the captured values to be returned too
$split = preg_split('/(-?\d+)/', $text, -1, PREG_SPLIT_DELIM_CAPTURE);
$return = '';
foreach($split as $i => $s) {
// because we didn't use PREG_SPLIT_NO_EMPTY, $split[0] will either be an empty string if
// $text began with a number, or the text before the first number. Either way, $split alternates
// between non-number [0], number [1], non-number [2], number [3], etc which is why we can detect
// even or odd indexes to determine if this is a number that needs to be incremented or not
if ($i % 2 === 0) {
$return .= $s;
} else {
$return .= (intval($s) + 1);
}
}
return $return;
}
Examples:
echo incrementNumbers("1 out of 23 with 1 and 1 and 24 and 23");
echo incrementNumbers("1 1 2 2 3 3 2 2 1 1");
echo incrementNumbers("0 1 2 3 4 5 6 7");
echo incrementNumbers("-3 -2 -1 0 1 2 3 4 5 6 7");
echo incrementNumbers("there are no numbers in this text");
echo incrementNumbers("does not start 999 with a number 123 nor end 17 with a number");
Outputs:
2 out of 24 with 2 and 2 and 25 and 24
2 2 3 3 4 4 3 3 2 2
1 2 3 4 5 6 7 8
-2 -1 0 1 2 3 4 5 6 7 8
there are no numbers in this text
does not start 1000 with a number 124 nor end 18 with a number
Working example at https://3v4l.org/iKskO

Number Range generator using max and min length with leading zeroes [duplicate]

This question already has answers here:
Formatting a number with leading zeros in PHP [duplicate]
(11 answers)
Closed 3 years ago.
I have min and max range of number need to generate number between two range with leading zeroes.
Example:
$min:000001;
$max:999999;
$uname=mt_rand($min,$max);
output:
$uname=2;
if generate number of two digit then append four zeroes, how can i set dynamic append zeroes of starting of string.
expected output: $uname=000002;
if
$uname = 20;
then
expected output:
$uname=000020;
Just use the function sprintf(). You can pass a format-string and the number you would like to output to this function.
Here is a small example:
$x = 7;
$y = 13;
sprintf("%03d", $x); // 007
sprintf("%03d", $y); // 013
sprintf("%02d", $x); // 07
sprintf("%02d %02d", $x, $y); // 07 13
The "d" in the format-string is standing for an integer number, the "03" is caring about filling 3 places.
The two last lines are showing examples of how to use this function to fill 2 places and to output more than one numbers at once.
Another function that could be interesting for you is str_pad() - this function is working with strings instead of numbers.
we can use it like it is shown in the following example:
$str = 'abc';
echo str_pad($str, 5, '.'); // '..abc'
echo str_pad($str, 5, '.', STR_PAD_RIGHT); // '..abc'
echo str_pad($str, 5, '.', STR_PAD_LEFT); // 'abc..'
echo str_pad($str, 5, '.', STR_PAD_BOTH); // '.abc.'
echo str_pad($str, 8, '. '); // '. . .abc'
echo str_pad($str, 8); // ' abc'
The function takes a string as the first parameter and the second parameter is a value, how long the string should be.
The third, optional parameter is the character or the set of characters to be used for filling. If you omit this parameter, a blank is used. If the number of characters for filling is not fitting, the filling characters are just cut, so that they come to the right length.
The fourth and last parameter specifies whether the string should be filled right justified (STR_PAD_RIGHT), left justified (STR_PAD_LEFT) or centrally (STR_PAD_BOTH). If you omit this parameter, the function uses a right alignment.

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

Converting large numbers into letters (and back again)

Is there a term for the idea of storing large numbers as letters? For example let's say I have the (relatively small) number 138201162401719 and I want to shrink the number of characters (I know this does not help with saving disk space) to the fewest possible number of characters. There are 26 letters in the English alphabet (but i count them as 25 since we need a zero letter). If I start splitting up my large number into pieces that are each 25 or less I get:
13, 8, 20, 11, 6, 24, 0, 17, 19
If I then count the numbers of the alphabet a=0, b=1, c=2, d=3... I can convert this to:
NIULGYART
So I went from 15 digits long (138201162401719) to 9 characters long (NIULGYART). This could of course be easily converted back to the original number as well.
So...my first question is "Does this have a name" and my second "Does anyone have PHP code that will do the conversion (in both directions)?"
I am looking for proper terminology so that I can do my own research in Google...though working code examples are cool too.
This only possible if you're considering to store your number before processing as a string. Because you can't store huge number as integers. You will lost the precision (13820116240171986468445 will be stored as 1.3820116240172E+22) so the alot of digits are lost.
If you're considering storing the number as a string this will be your answer:
Functions used: intval, chr and preg_match_all.
<?php
$regex = '/(2[0-5])|(1[0-9])|([0-9])/';
$numberString = '138201162401719';
preg_match_all($regex, $numberString, $numberArray, PREG_SET_ORDER);
echo($numberString . " -> ");
foreach($numberArray as $value){
$character = chr (intval($value[0]) + 65);
echo($character);
}
?>
Demo
This is the result:
138201162401719 -> NIULGYART
Here's how I would do it:
Store the big number as a string and split it into an array of numbers containing one digit each
Loop through the array extract 2-digit chunks using substr()
Check if the number is less than 26 (in which case, it is an alphabet) and add them to an array
Use array_map() with chr() to create a new array of characters from the above array
Implode the resulting array to get the cipher
In code:
$str = '138201162401719';
$arr = str_split($str);
$i = 0; // starting from the left
while ($i < count($arr)) {
$n = substr($str, $i, 2);
$firstchar = substr($n, 0, 1);
if ($n < 26 && $firstchar != 0) {
$result[] = substr($str, $i, 2);
$i += 2; // advance two characters
} else {
$result[] = substr($str, $i, 1);
$i++; // advance one character
}
}
$output = array_map(function($n) {
return chr($n+65);
}, $result);
echo implode($output); // => NIULGYART
Demo.
As an alternative, you could convert the input integer to express it in base 26, instead of base 10. Something like (pseudocode):
func convertBase26(num)
if (num < 0)
return "-" & convertBase26(-num) // '&' is concatenate.
else if (num = 0)
return "A"
endif
output = "";
while (num > 0)
output <- ('A' + num MOD 26) & output // Modulus operator.
num <- num DIV 26 // Integer division.
endwhile
return output
endfunc
This uses A = 0, B = 1, up to Z = 25 and standard place notation: 26 = BA. Obviously a base conversion is easily reversible.
strtr() is a magnificent tool for this task! It replaces the longest match as is traverses the string.
Code: (Demo)
function toAlpha ($num) {
return strtr($num, range("A", "Z"));
}
$string = toAlpha("138201162401719");
echo "$string\n";
$string = toAlpha("123456789012345");
echo "$string\n";
$string = toAlpha("101112131415161");
echo "$string\n";
$string = toAlpha("2625242322212019");
echo "$string";
Output:
NIULGYART
MDEFGHIJAMDEF
KLMNOPQB
CGZYXWVUT
Just flip the lookup array to reverse the conversion: https://3v4l.org/YsFZu
Merged: https://3v4l.org/u3NQ5
Of course, I must mention that there is a vulnerability with converting a sequence of letters to numbers and back to letters. Consider BB becomes 11 then is mistaken for eleven which would traslate to L when converted again.
There are ways to mitigate this by adjusting the lookup array, but that may not be necessary/favorable depending on program requirements.
And here is another consideration from CodeReview.
I have been trying to do the same thing in PHP without success.
Assuming I'm using the 26 letters of the English alphabet, starting with A = 0 down to Z as 25:
I find the highest power of 26 lower than the number I am encoding. I divide it by the best power of 26 I found. Of the result I take away the integer, convert it to a letter and multiply the decimals by 26. I keep doing that until I get a whole number. It's ok to get a zero as it's an A, but if it has decimals it must be multiplied.
For 1 billion which is DGEHTYM and it's done in 6 loops obviously. Although my answer demonstrates how to encode, I'm afraid it does not help doing so on PHP which is what I'm trying to do myself. I hope the algorithm helps people out there though.

Math & php: FAST sort of an array [1..N] in a special way

$array = array(1, 2, 3, 4, 5, ..., N);
Also there is a number D = 10%. What is the fastest way to sort the array in such way that:
$sorted_array = {a[i]}
contains exactly the elements of $array in a mixed order, but also:
abs(a[i + 1] - a[i]) >= N * 10%
for any [i] and look randomized as much as possible.
For example,
// assume D = 25%
$array = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// so the difference between any neighbors is >= 4 = 10 * 25%.
$sorted_array = array(4, 8, 3, 7, 1, 5, 9, 2, 6, 10);
Of course if D is large, it is impossible to sort the array I want. I don't need the 100% perfect result, but I want the numbers to look "randomized" and most of them to be different at least for 10%.
I have a strange task but it has a practical area to use. I want to extract randomized lines from the image and they should be different as much as possible. Of course, the neighbor lines on the digital images (photos etc) look very similar.
Did I explain it properly?
I know it's not a good idea just to provide code, but I was intrigued by this question. Here's how I would do it:
$d = 0.3;
$random = array();
// Populate the original array
for ($n=1; $n <= 10; $n++) {
$arr[] = $n;
}
$count = count($arr);
// Loop through array
foreach (array_keys($arr) as $key) {
if (!isset($prev_key)) {
$prev_key = array_rand($arr);
}
$possibles = array(); // This stores the possible values
echo "Trying: $prev_key";
echo ":\n";
// Loop through the array again and populate $possibles with all possible
// values based on the previous values
foreach (array_keys($arr) as $n) {
if ($arr[$n] < $prev_key - $count * $d || $arr[$n] > $prev_key + $count * $d) {
$possibles[] = $n;
echo $arr[$n]." is valid\n";
}
else {
echo $arr[$n];
echo " outside range\n";
}
}
// If there is nothing outside that range, just return the remaining values
if (count($possibles) == 0) {
$possibles = array_keys($arr);
echo "Nothing within range so just returning whole array\n";
}
echo "\n";
// Choose random value from the possible values array
$rand_key = $possibles[array_rand($possibles)];
$random[] = $arr[$rand_key];
$prev_key = $arr[$rand_key];
// Unset this value from the original array since we can only use the
// values once
unset($arr[$rand_key]);
}
print_r($random);
This will produce output like this:
Trying: 8:
1 is valid
2 is valid
3 is valid
4 is valid
5 outside range
6 outside range
7 outside range
8 outside range
9 outside range
10 outside range
Trying: 2:
1 outside range
3 outside range
4 outside range
5 outside range
6 is valid
7 is valid
8 is valid
9 is valid
10 is valid
Trying: 9:
1 is valid
3 is valid
4 is valid
5 is valid
6 outside range
7 outside range
8 outside range
10 outside range
Trying: 5:
1 is valid
3 outside range
4 outside range
6 outside range
7 outside range
8 outside range
10 is valid
Trying: 10:
1 is valid
3 is valid
4 is valid
6 is valid
7 outside range
8 outside range
Trying: 4:
1 outside range
3 outside range
6 outside range
7 outside range
8 is valid
Trying: 8:
1 is valid
3 is valid
6 outside range
7 outside range
Trying: 3:
1 outside range
6 outside range
7 is valid
Trying: 7:
1 is valid
6 outside range
Trying: 1:
6 is valid
Array
(
[0] => 2
[1] => 9
[2] => 5
[3] => 10
[4] => 4
[5] => 8
[6] => 3
[7] => 7
[8] => 1
[9] => 6
)
The only drawback is that since it randomly gets rows, there is a chance that the values near the end may not be outside the defined range. By my tests, this happens to about 4% using the above $d = 0.25 and 1000 values. One way to get around this is just to insert these values back in at random places instead of appending them like I have done.
Also note, this method is not that efficient. It has to loop through the array count($arr) ^ 2 times. So for 1000 values, you're looking at 1,000,000 iterations. Fortunately the array gets progressively smaller.

Categories