I have a range of information. For example:
Volume 1 Chapter 3 Page 5 TO Volume 1 Chapter 5 Page 10
what is the fastest way to remove redundant information and convert this to:
Volume 1 Chapter 3 Page 5 TO Chapter 5 Page 10
OR if the input is
Volume 1 Chapter 3 Page 5 TO Volume 1 Chapter 3 Page 10
then output
Volume 1 Chapter 3 Page 5 TO Page 10
The hardest part here is to split the input into tokens, as it's not structured well enough. I used a recursive function to sequentially clean the string of the first element duplicates. It works correctly for this input, but I am not sure, that it's 100% correct, as input structure is unclear:
<?php
$str = 'Volume 1 Chapter 3 Page 5 TO Volume 1 Chapter 3 Page 10';
$str = clear_first_element_duplicates($str);
var_dump($str);
function clear_first_element_duplicates($str)
{
if (preg_match('/(.*?\d)\s(.*)/', $str, $tokens))
{
$regexp = preg_quote($tokens[1]);
$str = preg_replace("/$regexp\s?/", '', $tokens[2]);
return $tokens[1]." ".clear_first_element_duplicates($str);
}
return $str;
}
Prints:
"Volume 1 Chapter 3 Page 5 TO Page 10"
My script seems complex but worth it:
I Added variable Levels, So it's not restricted to just Volume, chapter and pages, you can add for example paragraph line and character if desired, and you can even change the Wording. See examples at the end.
** BE careful with the $separator parameter, it must be Exact(case sensitive) and might appear only once on the script, this is easy to fix but I focused on the important part of the function **
function redundancy($string, $separator){
list($a, $b) = explode($separator, $string);
//getting the numeric values of both sides
$pattern = '/[0-9]+/';
preg_match_all($pattern, $a, $a_values);
preg_match_all($pattern, $b, $b_values);
$a_values = $a_values[0];
$b_values = $b_values[0];
//getting the wording and cleaning out the numbers, I guess this can be improved through a better REGEX
preg_match_all('/\b\w+\b/', $a, $matches);
foreach($matches[0] as $match){
if(!is_numeric($match)) $words[] = $match;
}
//algorithm
$length = count($a_values) - 1; // excluding the last element, to be checked separately
$output = $a.$separator." ";
$same_full_path = true; // check if the levels has been altered to check the last element
$same_parent = true; // check the previous level
for($i = 0; $i < $length; $i++){
if($a_values[$i] !== $b_values[$i] || $same_parent === false){
$same_parent = false;
$same_full_path = false;
$output .= $words[$i]." ".$b_values[$i]." ";
}
}
//adding the word to the last element or not, The last element check must be outside the loop because it's special;
if($same_full_path === false || end($a_values) === end($b_values)) $output .= end($words)." ";
$output .= end($b_values);
echo "$string <Br/> $output; <br/><br/> ";
}
redundancy('Volume 1 Chapter 3 Page 5 TO Volume 1 Chapter 5 Page 10', 'TO');
redundancy('Serie 1 Season 2 Chapter 2 Minute 5 Second 6 Until Serie 1 Season 3 Chapter 4 Minute 3 Second 1', 'Until');
redundancy('District 4 Building 2 Floor 4 Door 5 To District 4 Building 2 Floor 4 Door 8', 'To');
Outputs :
Volume 1 Chapter 3 Page 5 TO Volume 1 Chapter 5 Page 10
Volume 1 Chapter 3 Page 5 TO Chapter 5 Page 10;
-
Serie 1 Season 2 Chapter 2 Minute 5 Second 6 Until Serie 1 Season 3 Chapter 4 Minute 3 Second 1
Serie 1 Season 2 Chapter 2 Minute 5 Second 6 Until Season 3 Chapter 4 Minute 3 Second 1;
-
District 4 Building 2 Floor 4 Door 5 To District 4 Building 2 Floor 4 Door 8
District 4 Building 2 Floor 4 Door 5 To 8;
Related
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
I am trying to code the codility les 13 ladder.php exercise.
You have to calc using the fibonacci numers the total ways you can climb a ladder when you can do 1 or 2 rungs at the time.
I made a loop creating the first 50 fibonacci numbers like this:
$total = 50;
$fibonacci[0]=0;
$fibonacci[1]=1;
for($i=2;$i<=$total;$i++){
$fibonacci[$i] = $fibonacci[($i-1)] + $fibonacci[($i-2)];
}
This works well if you can do 1 or 2 rungs at the time.
But how do i adjust this loop to give me the first 50 fibonacci numbers if you can take 1, 2 or 3 rungs at the time.
if you can take 1 or 2 rungs you use: Fx = F[(x-1)] + F[(x-2)];
if you can do 1, 2 and 3 you use: Fx = F[(x-1)] + F[(x-2)] + F[(x-3)];
if you can do 1, 2, 3 and 4 you use: Fx = F[(x-1)] + F[(x-2)] + F[(x-3)] + F[(x-4)];
Then you can make as many numbers or calculate as many numbers as you wish.
But you always need the first few numbers to be able to make the calculations.
I hope this table makes clear what i mean:
2 3 4 5 6
1 1 1 1 1 1
2 2 2 2 2 2
3 3 4 4 4 4
4 5 7 8 8 8
5 8 13 15 16 16
6 13 24 29 31 32
The row below 2 is the basic fibonacci
Below 3 I would need number 1 to 3 to be able to calculate #4 and so on
Below 4 I need the first 4 to be able to calc #5 and so on
Can anyone help me create a loop to create the first say 10 numbers for row 4 and 5 and 6?
Thanks
Here you are a function returns a part of fibonacci numbers. You can set a start element (0 - based) and how many elements you need.
function takeFibonacci($start, $length)
{
$fibonacci = array(0, 1);
$result = array();
$position = 0;
do {
if (!isset($fibonacci[$position])) {
$fibonacci[$position] = $fibonacci[$position - 1] + $fibonacci[$position - 2];
}
if ($position >= $start) {
$result[] = $fibonacci[$position];
}
$position++;
} while ($position < $start + $length);
return $result;
}
$fib = takeFibonacci(5, 5);
var_dump($fib);
Code above returns this result
array(5) { [0]=> int(5) [1]=> int(8) [2]=> int(13) [3]=> int(21) [4]=> int(34) }
I wrote a sudoku generator that creates numbers cell by cell and checks immediately after a cell has been created if it is valid (horizontally, vertically and in a 3x3 block).
Now my problem is that the algorithm always gets stuck at some point, as it won't find a valid number for the current cell. Sometimes closer to the end, sometimes already after writing 30 cells.
This is my function to check the cell, which should change the number depending on its validity:
private function checkCell($index)
{
while ($this->isValid($index) === false) {
$this->cell[$index]->setValue(rand(1, 9));
$this->counter++;
echo 'counter: ' . $this->counter;
echo PHP_EOL;
if ($this->counter > 1000) {
$this->display();
die();
}
}
}
isValid() checks if the cell is valid horizontally, vertically and in a block (this is currently not working, it just returns true).
The counter is for debugging purposes so I can see when it gets stuck.
Here is the function generating my cells:
private function fillCell($index)
{
$rand = rand(1, 9);
$this->cell[$index]->setValue($rand);
$this->checkCell($index);
}
What should be changed so the algorithm doesn't get stuck all the time?
The issue might be that the algorithm is a little too random. You end up creating a grid that is invalid and can not be completed further.
I would propose starting from a known valid grid and shuffle the cells randomly. If a cell can't be moved, we can simply skip it.
A fair warning to the reader, the following will contain pseudo code rather than working code.
A perfectly valid starting grid:
1 2 3 | 4 5 6 | 7 8 9
7 8 9 | 1 2 3 | 4 5 6
4 5 6 | 7 8 9 | 1 2 3
------|-------|------
9 1 2 | 3 4 5 | 6 7 8
6 7 8 | 9 1 2 | 3 4 5
3 4 5 | 6 7 8 | 9 1 2
------|-------|------
8 9 1 | 2 3 4 | 5 6 7
5 6 7 | 8 9 1 | 2 3 4
2 3 4 | 5 6 7 | 8 9 1
We can store this in a single dimension array, as you already appear to do.
We follow a simple logic:
We create a single dimension array containing the cells
$cells = array(
1,2,3,4,5,6,7,8,9,7,8,9,1,2,3,4,5,6,4,5,6,7,8,9,1,2,3,
9,1,2,3,4,5,6,7,8,6,7,8,9,1,2,3,4,5,3,4,5,6,7,8,9,1,2,
8,9,1,2,3,4,5,6,7,5,6,7,8,9,1,2,3,4,2,3,4,5,6,7,8,9,1,
);
We create another array containing numbers from 0 to 80 in a random order, which are the indexes of $cells
$indexes = range(0, 80);
shuffle($indexes);
We iterate over $indexes and use the value to select a random $cell in $cells
foreach($indexes as $index) {
$cell = $cells[$index];
}
For each $cell, we iterate over $cells. In each iteration, we create a temporary grid where we switch the value of the current cell with the value of the target cell. If the temporary grid is valid, we save the target index in an array of candidates
// pseudo code because that's a lot of work
$candidates = getCandidates($cell, $cells);
We randomly choose one of the candidates and switch the cells. If no candidate is available, we simply ignore this step
candidatesCount = count(candidates);
if(candidatesCount > 0) {
$candidate = $candidates[range(0, candidatesCount -1)];
// switch values
$cells[$index] = $cells[$candidate];
$cells[candidate] = $cell;
}
Repeat until $cells is processed
There are likely more efficient ways to proceed, but this logic can not get stuck.
Note that there is a low probability that the shuffle will undo itself and produce the original grid. But it's still a valid grid.
You never want to make a backtracking algorithm that uses random numbers. It can end up running infinitely.
What you want to do is:
Find the first empty cell
Try all possible values from 1 to 9 in this cell. When all values are tried, go back.
For every value you try in the cell (at step 2), recursively call the backtracking algorithm. (go back to step 1)
If the function is called and there are no empty cells, evaluate the board. If everything is ok, you found the solution! If it's not ok, go back.
The evaluation means, check that you have all numbers from 1 to 9 exactly once on every line, every column, and every 3x3 square.
Example of how it might look like:
function back($pos) {
if ($pos >= 9*9) {
if (evaluate()) {
// we found a solution
// do soemthing with it
} else {
return;
}
}
$x = pos / 9;
$y = pos % 9;
if ($m[x][y] != 0) {
// we already have a value assigned for this position
back($pos+1);
return;
}
for ($v = 1; $v <= 9; $v++) {
$m[x][y] = $v;
back($pos+1);
}
$m[x][y] = 0; // clean up tested value before going back
}
back(1)
The above algorithm can be optimized by evaluating lines/columns at every step, instead of just once at the end. If the algorithm tries to place number x, but x is already found on the line/column, then we can just move on to try x+1 since we know x will create an invalid solution.
What I need is to create five random integer (say rand(1,5)). Then, I generate a score based on these numbers. For instance, if I get a result of 1,2,3,4,5 then that would equal a zero score, but if I got 1,1,3,4,5 that would be 1 as we have a pair. Similar to a poker kind of scoring, so five of the same number would be a "full house" thus resulting in the highest score.
How would I go about the scoring system, even if it is just the mathematical equation?
More detail:
1-5 will hold separate images and then will be fought against "The House" which will have identical code to the user to determine the winner. Here's some example draws and the score they would receive:
1,2,3,4,5 = score 0
1,1,2,3,4 = score 1 (1 pair)
1,1,2,2,4 = score 2 (2 pair)
1,1,1,3,4 = score 3 (3 of a kind)
1,1,1,1,5 = score 4 (4 of a kind)
1,1,1,3,3 = score 5 (full house)
1,1,1,1,1 = score 6 (5 of a kind)
The combination of numbers is irreverent if they score 6 and the house scores 6, it's a tie.
if (isset($_POST['play'])) {
$rand1 = rand(1, 5);
$rand2 = rand(1, 5);
$rand3 = rand(1, 5);
$rand4 = rand(1, 5);
$rand5 = rand(1, 5);
if ($_POST['bet'] <= $user_data['coins']) {
if ($_POST['bet'] < 999999999) {
if ($_POST['bet'] > 0.99) {
if ($user_data['coins'] >= 1) {
$array = array($rand1,$rand2,$rand3,$rand4,$rand5);
print_r(array_count_values($array));
echo $rand1.', '.$rand2.', '.$rand3.', '.$rand4.', '.$rand5;
Array( // Here I don't understand
1 => 3,//
2 => 1,//
3 => 1 //
);
}
}
}
}
}
This outputs ; Array ( [5] => 2 [4] => 2 [1] => 1 ) 5, 5, 4, 4, 1
Use array_count_value function for this.
$array = array(1,1,1,2,5);
print_r(array_count_values($array));
Array(
1 => 3,
2 => 1,
3 => 1
);
Here's the approach I would consider, building on #Lele's answer. Warning: this is a bit confusing, so sit down with a cup of tea for this one.
Build a set of five buckets, [1] to [5], and scan a player's numbers, so that the count for each number is stored in the corresponding bucket
Then count the numbers you are left with into a new bucket system, with each position representing the number of counts you have for something.
So, if your score is this:
1 1 2 2 4
Then your first buckets are:
2 2 0 1 0
That's because you have two ones, two twos, and one four. And your second buckets are:
1 2 0 0 0
That's because you have two two-counts, and one one-count. Here, you disregard the first position (since a one-count for something does not score anything) and score for the others. So, test for two twos, and score that two.
If you score is this:
5 5 5 5 1
Then your first buckets are:
1 0 0 0 4
That's one one and four fives. So your second buckets are:
1 0 0 1 0
Your lookup table for this could be:
x 1 0 0 0 -> one pair
x 2 0 0 0 -> two pairs
x 0 1 0 0 -> three of a kind
x 1 1 0 0 -> full house
x 0 0 1 0 -> four of a kind
x 0 0 0 1 -> five of a kind
The 'x' means that you don't match on this. So, your lookup table matches four numbers to a score.
I was rather interested in this problem, so I have written some code to do the above. You'll still need to do the lookup table, but that is relatively trivial, and will be good practice for you. Here is a demo, with comments (run code here):
<?php
function counting(array $array) {
// Input figures
print_r($array);
// Run the figures twice through the bucket-counter
$firstBuckets = bucketCounter($array);
$secondBuckets = bucketCounter($firstBuckets);
// Ignore counts of 1
array_shift($secondBuckets);
// Output, just need to do the lookup now
echo ' converts to ';
print_r($secondBuckets);
echo "<br />";
}
/**
* Bucket counter
*/
function bucketCounter(array $array) {
$result = array(0, 0, 0, 0, 0, );
foreach($array as $value) {
if ($value > 0) {
$result[$value - 1]++;
}
}
return $result;
}
// Try some demos here!
counting(array(1, 2, 3, 4, 5));
counting(array(1, 1, 2, 4, 2));
counting(array(1, 1, 1, 1, 1));
?>
The demos I've included seem to work, but do hunt for bugs!
If the range is quite small, you can use counting sort approach. For each number, provide a "bucket" to count how many times a number appear. Scan once to fill in the buckets. Then another scan, but this time against the bucket to get the highest value. That's your score.
I have a need for a function that will do the following thing:
If I have a string like this "2 1 3 6 5 4 8 7" I have to insert dashes between pairs of numbers following some rules.
The rules are simple.
Put a dash between two numbers if the first one of the pair is smaller then the one that follows it. Do all possible combinations of this and if a pair already has a dash then the space next to it can't have a dash.
Basically my results for above string would be
2 1-3 6 5 4 8 7
2 1-3 6 5 4-8 7
2 1 3-6 5 4 8 7
2 1 3-6 5 4-8 7
2 1 3 6 5 4-8 7
I did create a function that does this but I am thinking it is pretty sluggish and I don't want to taint your ideas with it. If possible I would like to know how you guys are thinking about this and even some pseudo code or code would be great.
EDIT 1:
here is the code I have so far
$string = "2 1 3 6 5 4 8 7";
function dasher($string){
global $dasherarray;
$lockcodes = explode(' ', $string);
for($i = 0; $i < count($lockcodes) - 1; $i++){
if(strlen($string) > 2){
$left = $lockcodes[$i];
$right = $lockcodes[$i+1];
$x = $left . ' ' . $right;
$y = $left . '-' . $right;
if (strlen($left) == 1 && strlen($right) == 1 && (int)$left < (int)$right) {
$dashercombination = str_replace($x, $y, $string);
$dasherarray[] = $dashercombination;
dasher($dashercombination);
}
}
}
return array_unique($dasherarray);
}
foreach(dasher($string) as $combination) {
echo $combination. '<br>';
}
Perhaps this will be helpful in terms of offering different methods to parse the string.
$str="2 1 3 6 5 4 8 7";
$sar=explode(' ',$str);
for($i=1;$i<count($sar);$i++)
if($sar[$i-1]<$sar[$i])
print substr_replace($str,'-',2*($i-1)+1,1) . "\n";
Note that the code expects only single digits numbers in the string.
Note that the code expects that the string is formatted as per your example. It would be good to add some sanity checks (collapse multiple spaces, strip/trim blanks at the beginning/end).
We can improve upon this by finding all the spaces in the string and using them to index substrings for comparison, still assuming that only a single spaces separates adjacent numbers.
<?php
$str="21 11 31 61 51 41 81 71";
$letter=' ';
#This finds the locations of all the spaces in the strings
$spaces = array_keys(array_intersect(str_split($str),array($letter)));
#This function takes a start-space and an end-space and finds the number between them.
#It also takes into account the special cases that we are considering the first or
#last space in the string
function ssubstr($str,$spaces,$start,$end){
if($start<0)
return substr($str,0,$spaces[$end]);
if($end==count($spaces))
return substr($str,$spaces[$start],strlen($str)-$spaces[$start]);
return substr($str,$spaces[$start],$spaces[$end]-$spaces[$start]);
}
#This loops through all the spaces in the string, extracting the numbers on either side for comparison
for($i=0;$i<count($spaces);$i++){
$firstnum=ssubstr($str,$spaces,$i-1,$i);
$secondnum=ssubstr($str,$spaces,$i,$i+1) . "\n";
if(intval($firstnum)<intval($secondnum))
print substr_replace($str,'-',$spaces[$i],1) . "\n";
}
?>
Note the explicit conversion to integers in order to avoid lexicographic comparison.