PHP check for empty spots in ranges - php

Is it possible to spot empty ranges in an array with ranges, for example:
$ranges = array (
'1000-2000',
'2050-5000',
'5050-9990'
)
As an output it needs to be like:
$notInRanges = array (
'2001-2049',
'5001-5049'
);
The range is from 1000 till 9999. Too bad i can't get it done and can figure it out.
I did got the PHP code to fill an array with all filled items, and did get an array what isn't filled in range:
foreach ($ranges as $range) :
$rangeParts = explode('-', $range);
for($i=$range[0];$range[1] > $i;$++) :
$rangeItems[] = $i;
endfor;
endforeach;
for($i=1000;$i<=9999;$i++) :
if (!in_array($i, $rangeItems)) :
$notInRanges[] = $i;
endif;
endfor;
But can't figure out how to make it an array like ['2001-2049'],['5001-5049'] or a way to do this.

Ah hell I was bored anyway:
define('MIN_RANGE', 1000);
define('MAX_RANGE', 10000);
$ranges = [
'1000-2000',
'2050-5000',
'5050-9990'
];
$filled = [];
foreach($ranges as $range) {
list($from, $to) = explode('-', $range);
$filled = array_merge($filled, range($from, $to));
}
$filled = array_flip($filled);
$notInRange = [];
for($i = MIN_RANGE; $i < MAX_RANGE; $i++)
{
if(!isset($filled[$i]))
{
$beginRange = $i;
for($i = $i+1; $i <= MAX_RANGE; $i++)
{
if(isset($filled[$i]) || $i >= MAX_RANGE)
{
$notInRange[] = $beginRange.'-'.($i-1);
break;
}
}
}
}
var_dump($notInRange);
Output as expected:
array (size=3)
0 => string '2001-2049' (length=9)
1 => string '5001-5049' (length=9)
2 => string '9991-9999' (length=9)

$ranges = array (
'1000-2000',
'2050-5000',
'5050-9990'
);
foreach($ranges as $k=>$range)
if ($k>0)
if ((int)substr($range, 0, strpos($range, '-')) <> (int)substr($ranges[$k-1], strpos($ranges[$k-1], '-')+1)+1)
$gaps[] = ((int)substr($ranges[$k-1], strpos($ranges[$k-1], '-')+1)+1) . '-' . ((int)substr($range, 0, strpos($range, '-'))-1);
print_r($gaps);

I won't give you a full code, but following should help you to get started
Sort the array
Use foreach to walk over the array and check if start-position is last end-position + 1, if not you found a gap

$ranges = array (
'1000-2000',
'2050-5000',
'5050-9990'
);
$extremes=range(1000,9990);
$existing=array();
foreach ($ranges as $range){
list($min, $max)=explode('-', $range);
$existing=array_merge($existing, range($min, $max));
}
$missing=array_diff($extremes, $existing);
//print_r($missing);
$missing_chunks=array();
$chunks=0;
foreach ($missing as $m){
if (!isset($missing_chunks[$chunks])) $missing_chunks[$chunks]=array('max'=>$m);
if ($missing_chunks[$chunks]['max']==$m-1) $missing_chunks[$chunks]['max']=$m;
else {
$chunks++;
$missing_chunks[$chunks]=array('min'=>$m, 'max'=>$m);
}
}
print_r($missing_chunks);
The result $missing_chunks isn't perfect and it's slow

Related

How to add string into array in PHP?

I have these for loop to determine consecutive number. What I achieve so far is to print the output in string.
$arr = [1,2,3,6,11,5,4,8,9,3];
for($start=0; $start<=count($arr); $start++){
for($end=$start+1; $end<=count($arr); $end++){
$total = $arr[$end] - $arr[$start];
if($total == 1){
echo 'Number is '.$arr[$start].','.$arr[$end].'<br/>';
} else {
echo '';
}
$arr[$start++];
}
}
My goal is to add the output into array.
I tried to use multidimensional array but no output display.
$arr = [1,2,3,6,11,5,4,8,9,3];
$arr3 = [];
for($start=0; $start<=count($arr); $start++){
for($end=$start+1; $end<=count($arr); $end++){
$total = $arr[$end] - $arr[$start];
if($total == 1){
$arr2 = array();
$arr2[] = $arr[$start].','.$arr[$end].'';
$arr3[] = $arr2;
} else {
}
$arr[$start++];
}
}
echo '<pre>';
print_r($arr3);
echo '</pre>';
exit;
Appreciate if someone can help me. Thanks.
you can simply use array functions, if sorting is important to you as #nice_dev said, you must sort your array before.
$arr = [1,2,3,6,11,5,4,8,9,3];
$cons = [] ;
while (array_key_last($arr) != key($arr)) {
if ((current($arr)+1) == next($arr)) {
prev($arr);
$cons[] = current($arr) . ',' . next($arr);
}
}
print_r($cons);
the output will be :
Array
(
[0] => 1,2
[1] => 2,3
[2] => 8,9
)
You can better sort() the input array first. This way, collecting all consecutive elements would get much simpler. If value at any index isn't +1 of the previous one, we add the $temp in our $results array and start a new $temp from this index.
Snippet:
<?php
$arr = [1,2,3,6,11,5,4,8,9,3];
$result = [];
sort($arr);
$temp = [];
for($i = 0; $i < count($arr); ++$i){
if($i > 0 && $arr[ $i ] !== $arr[$i - 1] + 1){
$result[] = implode(",", $temp);
$temp = [];
}
$temp[] = $arr[$i];
if($i === count($arr) - 1) $result[] = implode(",", $temp);
}
print_r($result);
Online Demo

Count values between ranges

I have two arrays, one of values, and the other for ranges:
$ranges = array(10,15,30);
$values = array(1,4,12,15,27,32);
I want to count the number of values in between each ranges, as such:
$output = array(
"<10" => 2, // number of values < 10
"10-15" => 1, // number of values >= 10 && < 15
"15-30" => 2, // number of values >= 15 && < 30
">=30" => 1, // number of values > 30
);
Obviously, ranges and values are dynamic and can't be hard-coded if-conditions.
What I made so far is working:
$output = array();
foreach ( $values as $val ) {
foreach ( $ranges as $k => $range ) {
if ( $k == 0 ) { // first range
$max = $range;
$label = '<' . $max;
if ( $val < $max ) {
$output[$label] += 1;
}
} else if ( $k == count($ranges) - 1 ) { // last range
$min = $ranges[$k-1];
$max = $range;
$label = $min . '-' . $max;
if ( $val >= $min && $val < $max ) {
$output[$label] += 1;
}
$min = $range;
$label = '>=' . $min;
if ( $val >= $min ) {
$output[$label] += 1;
}
} else {
$min = $ranges[$k-1];
$max = $range;
$label = $min . '-' . $max;
if ( $val >= $min && $val < $max ) {
$output[$label] += 1;
}
}
}
}
print_r($output);
This seems costly and I'm really not sure about it. Is there a simpler way to achieve what I'm looking for?
You can simplify the logic a bit by added limiting values to the beginning and end of the $ranges array and then just processing the whole array by pairs.
<?php
$ranges = [10,15,30];
$values = [1,4,12,15,27,32];
\array_push($ranges, null); // append null to array
\array_unshift($ranges, null); // prepend null to array
$output = [];
$count = \count($ranges);
for ($i = 0; $i < $count - 1; $i++) {
$output[] = ['start' => $ranges[$i], 'end' => $ranges[$i+1], 'count' => 0];
}
foreach ($values as $value) {
foreach ($output as $key => $range) {
if (
($range['start'] === null || $range['start'] <= $value) &&
($range['end'] === null || $range['end'] > $value)
) {
$output[$key]['count']++;
break;
}
}
}
var_dump($output);
This starts off by creating a zeroed array with the keys from the $ranges array (using array_fill_keys()), plus one for the values 'over' the last entry.
The loops over each value and checks it against the range, if it finds it, it just adds 1 to the corresponding count and stops looking. If after finishing the loop, the value is greater than the last range, it adds 1 to the 'over' entry.
$ranges = array(10,15,30);
$values = array(1,4,12,15,27,32);
$rangeCount = array_fill_keys($ranges, 0);
$rangeCount[ "over" ] = 0;
foreach ( $values as $value ) {
foreach ( $ranges as $range ) {
if ( $value < $range ) {
$rangeCount [ $range ]++;
break;
}
}
if ( $value >= $range ) {
$rangeCount[ "over" ]++;
}
}
print_r($rangeCount);
which gives...
Array
(
[10] => 2
[15] => 1
[30] => 2
[over] => 1
)
Just to add an optimised version which only does one loop. But assumes the values are in ascending order. Each time it passes the 'current' range it moves onto the next output counter and the last part doesn't even loop for the over the top value, it subtracts the current count from the total count and does a break...
$currentRange = 0;
$numberValues = count($values);
$numberRanges = count($ranges);
$rangeCount = array_fill(0, $numberRanges, 0);
$rangeCount[ "over" ] = 0;
foreach ( $values as $count => $value ) {
if ( $value >= $ranges[$currentRange] ) {
$currentRange++;
if ( $currentRange >= $numberRanges ) {
$rangeCount[ "over" ] = $numberValues - $count;
break;
}
}
$rangeCount[$currentRange]++;
}
print_r($rangeCount);
The following solution first sorts the ranges in ascending/non-decreasing order.
Then, we create a range_map which is a collection of all possible ranges from $ranges.
Then, we loop over all values in $values and do a binary search over $ranges to get the exact range index a particular value belongs to. In the below code, exact index is stored in $low.
Then, we just collect the count by taking the range key from $range_map and incrementing it's counter by 1.
This is faster from nested looping since time complexity of nested looping is O(m*n) where m is size of $ranges and n is size of $values, whereas time complexity of current solution is O(m logm) + O(n logm) where m is size of $ranges and n is size of $values.
Snippet:
<?php
$ranges = array(10,15,30);
$values = array(1,4,12,15,27,32);
sort($ranges);
$range_map = [];
$ptr = 0;
foreach($ranges as $index => $value){
if($index === 0) $range_map[$ptr++] = "<" . $value;
if($index > 0) $range_map[$ptr++] = $ranges[$index - 1] . "-" . $value;
if($index === count($ranges) - 1) $range_map[$ptr++] = ">=" . $value;
}
$result = [];
foreach($values as $value){
$low = 0; $high = count($ranges) - 1;
while($low <= $high){
$mid = $low + intval(($high - $low) / 2);
if($value === $ranges[ $mid ]){
$low = $mid + 1;
break;
}else if($value < $ranges[ $mid ]){
$high = $mid - 1;
}else{
$low = $mid + 1;
}
}
if(!isset($result[$range_map[$low]])) $result[$range_map[$low]] = 0; // get the range key from range_map
$result[$range_map[$low]]++; // increment the value for that range
}
print_r($result);
Demo: https://3v4l.org/JcYBv
Assuming you have pre-sorted ranges and values.
<?php
$ranges = array(10,15,30);
$values = array(1,4,12,15,27,32);
$lower = null;
$i = 0;
$upper = $ranges[$i];
foreach($values as $item) {
if(!is_null($upper) && $item >= $upper) {
$lower = $upper;
$upper = $ranges[++$i] ?? null;
}
$result["$lower<$upper"][] = $item;
}
var_export(array_map('count', $result));
Output:
array (
'<10' => 2,
'10<15' => 1,
'15<30' => 2,
'30<' => 1,
)

Expand array of numbers and hyphenated number ranges to array of integers [duplicate]

This question already has answers here:
Populate array of integers from a comma-separated string of numbers and hyphenated number ranges
(8 answers)
Closed 6 months ago.
I'm trying to normalize/expand/hydrate/translate a string of numbers as well as hyphen-separated numbers (as range expressions) so that it becomes an array of integer values.
Sample input:
$array = ["1","2","5-10","15-20"];
should become :
$array = [1,2,5,6,7,8,9,10,15,16,17,18,19,20];
The algorithm I'm working on is:
//get the array values with a range in it :
$rangeArray = preg_grep('[-]',$array);
This will contain ["5-10", "16-20"]; Then :
foreach($rangeArray as $index=>$value){
$rangeVal = explode('-',$value);
$convertedArray = range($rangeVal[0],$rangeVal[1]);
}
The converted array will now contain ["5","6","7","8","9","10"];
The problem I now face is that, how do I pop out the value "5-10" in the original array, and insert the values in the $convertedArray, so that I will have the value:
$array = ["1","2",**"5","6","7","8","9","10"**,"16-20"];
How do I insert one or more values in place of the range string? Or is there a cleaner way to convert an array of both numbers and range values to array of properly sequenced numbers?
Here you go.
I tried to minimize the code as much as i can.
Consider the initial array below,
$array = ["1","2","5-10","15-20"];
If you want to create a new array out of it instead $array, change below the first occurance of $array to any name you want,
$array = call_user_func_array('array_merge', array_map(function($value) {
if(1 == count($explode = explode('-', $value, 2))) {
return [(int)$value];
}
return range((int)$explode[0], (int)$explode[1]);
}, $array));
Now, the $array becomes,
$array = [1,2,5,6,7,8,9,10,15,16,17,18,19,20];
Notes:
Casts every transformed member to integer
If 15-20-25 is provided, takes 15-20 into consideration and ignores the rest
If 15a-20b is provided, treated as 15-20, this is result of casing to integer after exploded with -, 15a becomes 15
Casts the array keys to numeric ascending order starting from 0
New array is only sorted if given array is in ascending order of single members and range members combined
Try this:
<?php
$array = ["1","2","5-10","15-20"];
$newdata = array();
foreach($array as $data){
if(strpos($data,'-')){
$range = explode('-', $data);
for($i=$range[0];$i<=$range[1];$i++){
array_push($newdata, $i);
}
}
else{
array_push($newdata, (int)$data);
}
}
echo "<pre>";
print_r($array);
echo "</pre>";
echo "<pre>";
print_r($newdata);
echo "</pre>";
Result:
Array
(
[0] => 1
[1] => 2
[2] => 5-10
[3] => 15-20
)
Array
(
[0] => 1
[1] => 2
[2] => 5
[3] => 6
[4] => 7
[5] => 8
[6] => 9
[7] => 10
[8] => 15
[9] => 16
[10] => 17
[11] => 18
[12] => 19
[13] => 20
)
Problem solved!
Using range and array_merge to handle the non-numeric values:
$array = ["1","2","5-10","15-20"];
$newArray = [];
array_walk(
$array,
function($value) use (&$newArray) {
if (is_numeric($value)) {
$newArray[] = intval($value);
} else {
$newArray = array_merge(
$newArray,
call_user_func_array('range', explode('-', $value))
);
}
}
);
var_dump($newArray);
It's easier to find out the minimum and maximum value and create the array with them. Here's an example:
$in = ["1","2","5-10","15-20"];
$out = normalizeArray($in);
var_dump($out);
function normalizeArray($in)
{
if(is_array($in) && sizeof($in) != 0)
{
$min = null;
$max = null;
foreach($in as $k => $elem)
{
$vals = explode('-', $elem);
foreach($vals as $i => $val)
{
$val = intval($val);
if($min == null || $val < $min)
{
$min = $val;
}
if($max == null || $val > $max)
{
$max = $val;
}
}
}
$out = array();
for($i = $min; $i <= $max; $i++)
{
$out[] = $i;
}
return $out;
}
else
{
return array();
}
}
here you go mate.
<?php
$array = ["1","2","5-10","15-20"];
$newArr = array();
foreach($array as $item){
if(strpos($item, "-")){
$temp = explode("-", $item);
$first = (int) $temp[0];
$last = (int) $temp[1];
for($i = $first; $i<=$last; $i++){
array_push($newArr, $i);
}
}
else
array_push($newArr, $item);
}
print_r($newArr);
?>
Simpler and shorter answer.
See in Ideone
$new_array = array();
foreach($array as $number){
if(strpos($number,'-')){
$range = explode('-', $number);
$new_array = array_merge($new_array, range($range[0],$range[1]));
}
else{
$new_array[] = (int) $number;
}
}
var_dump($new_array);
try this:
$array = ["1","2","5-10","15-20"];
$result = [];
foreach ($array as $a) {
if (strpos($a,"-")!== false){
$tmp = explode("-",$a);
for ($i = $tmp[0]; $i<= $tmp[1]; $i++) $result[] = $i;
} else {
$result[] = $a;
}
}
var_dump($result);
you did not finish a little
$array = ["1","2","5-10","15-20"];
// need to reverse order else index will be incorrect after inserting
$rangeArray = array_reverse( preg_grep('[-]',$array), true);
$convertedArray = $array;
foreach($rangeArray as $index=>$value) {
$rangeVal = explode('-',$value);
array_splice($convertedArray, $index, 1, range($rangeVal[0],$rangeVal[1]));
}
print_r($convertedArray);

How to sort a string by length of its words in php without using functions?

I have been asked a question in an interview to sort a string by length of its words in php without using built in functions.No idea how to do this. Can somebody help me with this?
String: Sort a string by length of its words
Thanks in advance
$array = array('harish', 'mohan', 'jaideep', 'hari');
for ($i = 1; $i < count($array); $i++) {
for ($j = $i; $j > 0; $j--) {
if (strlen($array[$j]) < strlen($array[$j - 1])) {
$tmp = $array[$j];
$array[$j] = $array[$j - 1];
$array[$j - 1] = $tmp;
}
}
}
var_dump($array);
you could try this:
$string = "this is my test string";
$array = explode(" ", $string);
$result = array();
foreach ($array as $key => $string){
$counter = 0;
for($i = 0;;$i++){
if (!isset($string[$i])){
break;
}
$counter++;
}
$result[$counter][] = $string;
}
This splits your string and puts it into an array, where the keys are the counted characters. The problem now is, that you need to sort the array by the keys, which can be acquired by using ksort.
I do not know if you may use it, if not, refer to this answer (use of sort) or this answer (no sort), this should do the trick (though I didn't test it).
This is the solution that I propose. I added some comments.
<?php
/* First part split the string in their words and store them in an array called $words.
* The key is the length of the word and the value is an array with all the words having the same length as the key.
* e.g
* Array
(
[4] => Array( [0] => Sort )
[1] => Array( [0] => a )
[6] => Array( [0] => string, [1] => length)
[2] => Array( [0] => by, [1] => of)
[3] => Array( [0] => its )
)
*/
$str = "Sort a string by length of its words";
$word = '';
$i = 0;
$word_length = 0;
$words = [];
$max = -1;
while( isset($str[$i]) ) {
if( $str[$i] !== ' ' ){
$word .= $str[$i];
$word_length++;
}else{
//This is going to save the size of the longhest word in the array:
$max = ($word_length > $max) ? $word_length : $max;
//store the
$words[$word_length][] = $word;
$word = '';
$word_length = 0;
}
$i++;
}
//print_r($words); // uncomment this if you wanna see content of the array.
//The if-condition is for ascending order or descending order.
$order = "DESC"; // "ASC" | "DESC"
$res = '';
if( $order === "DESC") {
for( $i = $max; $i>=0 ; $i--) {
if( ! isset($words[$i]) ) { continue; }
foreach($words[$i] as $word){
$res .= $word . ' ';
}
}
}else {
//ascending order:
for( $i = 0; $i<=$max; $i++) {
if( ! isset($words[$i]) ) { continue; }
foreach($words[$i] as $word){
$res .= $word . ' ';
}
}
}
echo $res . "\n";
?>
Is this what you want?
Note: isset, echo, print, etc are PHP language constructs whereas print_r(), strlen(), etc. are built in functions. If you have doubts, you can see what's the difference in this post. What is the difference between a language construct and a "built-in" function in PHP?

Inefficient rotating of an array

I'm trying to make a function that is able to rotate through an array a given amount of times and then return the first index. But what I have is really slow and clunky. Take a look:
<?php
/**
* Get the current userid
* #return integer
*/
public function getCurrentUser( DateTime $startDate, DateInterval $interval, DateTime $endDate, $currentUser, $users, $rotating )
{
if ($rotating == 0)
{
return $currentUser;
}
$usrArray = array();
$dateRange = new DatePeriod( $startDate, $interval, $endDate);
// Push userIds to an array
foreach ($users as $user)
{
$usrArray[] = $user->id;
}
// Get the number of iterations from startDate to endDate
$steps = iterator_count($dateRange);
// Find the initial position of the orignal user
$key = array_search($currentUser, $usrArray);
// Set up the array so index 0 == currentUser
$usr = $usrArray;
array_splice($usr, $key);
$slice = array_slice($usrArray, $key);
$startList = array_merge($slice, $usr);
// Start rotating the array
for ($i=0; $i < $steps; $i++)
{
array_push($startList, array_shift($startList));
}
return $startList[0];
}
Here's an Xdebug profile before the PHP script timed out.
xdebug profile
Is there a better way to figure out who is index 0 after x amount of rotations?
Your array rotation is not that slow but it can be improved .. i believe this is your rotation code
Your code
// Start rotating the array
for ($i=0; $i < $steps; $i++)
{
array_push($startList, array_shift($startList));
}
return $startList[0];
You can remove the loop replace it with mod .. the way you can still get the same results here is the solution:
Solution
return $startList[ $steps % count($startList)];
You would get the same result .
Simple benchmark & Testing
$steps = 10000; <----------------- 10,000 steps
set_time_limit(0);
echo "<pre>";
$file = "log.txt";
// Using your current code
function m1($steps) {
$startList = range("A", "H");
for($i = 0; $i < $steps; $i ++) {
array_push($startList, array_shift($startList));
}
return $startList[0];
}
// Using InfiniteIterator
function m2($steps) {
$startList = range("A", "H");
$n = 0;
foreach ( new InfiniteIterator(new ArrayIterator($startList)) as $l ) {
if ($n == $steps) {
return $l;
break;
}
$n ++;
}
}
// Simple MOD solution
function m3($steps) {
$startList = range("A", "H");
return $startList[ $steps % count($startList)];
}
$result = array('m1' => 0,'m2' => 0,'m3' => 0);
for($i = 0; $i < 1; ++ $i) {
foreach ( array_keys($result) as $key ) {
$alpha = microtime(true);
$key($file);
$result[$key] += microtime(true) - $alpha;
}
}
echo '<pre>';
echo "Single Run\n";
print_r($result);
var_dump(m1($steps),m2($steps),m2($steps));
echo '</pre>';
Output
Single Run
Array
(
[m1] => 0.00012588500976562
[m2] => 0.00021791458129883
[m3] => 7.7962875366211E-5 <----------------- Mod solution fastest
)
string 'A' (length=1) |
string 'A' (length=1) |+------------- They all return same result
string 'A' (length=1) |

Categories