Count values between ranges - php

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,
)

Related

How do I solve this problem statement using PHP?

Suppose a number is given which is of positive integer type, e.g: 312. Please help me to write a program in PHP which convert that given number to a new number having same number of digits and all the digits of the new number must be equal to any of the digits of the given number (e.g: 333, 111, 222 by either decrementing or incrementing each digit by 1 at a time only). But print only that sequence of digits which takes lesser number of steps to generate the sequence and also print the number of steps taken to generate that sequence.
Explanation:
Input: A positive integer N (e.g: 312)
converting the number(312) to the sequence of 3
3 2 2
3 3 2
3 3 3
here number of steps = 3
Now, converting the number(312) to the sequence of 1
2 1 2
1 1 2
1 1 1
here number of steps = 3
and finally converting the number(312) to the sequence of 2
2 1 2
2 2 2
here number of steps = 2
So, Output: 222
Number of steps: 2
Here's what I tried but lost
<?php
$num = 312;
$arr_num = array_map('intval', str_split($num));
//steps taken for each sequence will be stored in this array
$steps = array();
//printing number
for($i = 0; $i < count($arr_num); $i++)
echo $arr_num[$i];
//calculation
for($i = 0; $i < count($arr_num); $i++) {
$count = 0;
for($j = 0; $j < count($arr_num); $j++) {
if($arr_num[$i] == $arr_num[$j])
++$j;
elseif($arr_num[$i] > $arr_num[$j]) {
while($arr_num[$j] != $arr[$i]) {
$arr_num[$j] += 1;
$count++;
}
}
else {
while($arr_num[$j] != $arr_num[$i]) {
$arr_num[$j] -= 1;
$count++;
}
}
}
//pushing the count to steps array for each sequence
array_push($steps, $count);
}
//I am stuck here...can't find the further solution
?>
This works (according to my very quick testing)):
$intIn = 312;
# function changeDigits( $intIn ) { // uncomment for function
$digits = str_split( $intIn ); // convert to array of digits
$numerOfDigits = count($digits);
$numberOfSteps = array();
# check each digit in number
for ($i=0; $i < $numerOfDigits; $i++) {
$numberOfSteps[$i] = 0;
$currentDigit = $digits[$i];
# count the number of inc/decrements to change the other digits to this digit
foreach($digits as $otherDigit) {
if ($currentDigit > $otherDigit) $numberOfSteps[$i] += $currentDigit - $otherDigit;
if ($currentDigit < $otherDigit) $numberOfSteps[$i] += $otherDigit - $currentDigit;
}
}
$digitKey = array_search( min($numberOfSteps), $numberOfSteps );
echo 'Number of Steps: ' . $numberOfSteps[$digitKey] . PHP_EOL; // (or '<br>')
echo 'New number = ' . str_repeat( $digits[$digitKey], $numerOfDigits );
#}
# changeDigits(312);
Demo
I devepled this code. please have a look once
<?php
function find_output($input)
{
$digits = str_split($input);
foreach ($digits as $index => $d) {
$new_array = $digits;
unset($new_array[$index]);
$sum = 0;
foreach ($new_array as $value) {
$sum += abs($d - $value);
}
$final_array[$d] = $sum;
}
$steps = min($final_array);
echo "steps : " . $steps . '<br>';
$final_value = array_search(min($final_array), $final_array);
echo "Output: " . implode(array_fill(0, count($digits), $final_value));
}
find_output(819);
?>
<?php
class SeqSolver
{
public function solve($str_num)
{
if(!ctype_digit($str_num))
throw new Exception('Invalid input. Input string must contain digits between 0 and 9 only.');
$digits = str_split($str_num);
$length = count($digits);
foreach(array_unique($digits) as $digit)
$results[$digit] = $this->stepsToSequence($str_num, $digit);
//var_export($results);
$min_keys = array_keys($results, min($results));
// Prepare result
$result['input'] = $str_num;
foreach($min_keys as $key)
$result['solutions'][] = [
'sequence' => str_repeat($key, $length),
'steps' => $results[$key]
];
return $result;
}
public function stepsToSequence($str_num, $target_digit) {
$digits = str_split($str_num);
$steps = 0;
foreach($digits as $digit)
$steps += abs($digit - $target_digit);
return $steps;
}
}
Example use:
$solver = new SeqSolver;
foreach(['312', '334', '39'] as $input) {
$result = $solver->solve($input);
var_export($result);
echo "\n";
}
Output:
array (
'input' => '312',
'solutions' =>
array (
0 =>
array (
'sequence' => '222',
'steps' => 2,
),
),
)
array (
'input' => '334',
'solutions' =>
array (
0 =>
array (
'sequence' => '333',
'steps' => 1,
),
),
)
array (
'input' => '39',
'solutions' =>
array (
0 =>
array (
'sequence' => '33',
'steps' => 6,
),
1 =>
array (
'sequence' => '99',
'steps' => 6,
),
),
)
I think this will do the job.
<?php
$input = 312;
$input_array = [];
for($x=0;$x<3;$x++) {
array_push($input_array,strval($input)[$x]);
}
function equalize($input_array, $mark) {
$i = 0;
for($x=0;$x<count($input_array);$x++) {
$input_array[$x] = intval($input_array[$x]);
#print($input_array[$x]);
while($input_array[$x] != $mark) {
if($input_array[$x] < $mark) {
$input_array[$x] = $input_array[$x] + 1;
$i++;
}
else {
$input_array[$x] = $input_array[$x] - 1;
$i++;
}
}
}
$output_val = intval($input_array[0] .$input_array[1] .$input_array[2]);
return $output = [$output_val,$i];
}
#to first
$mark = $input_array[0];
$output = equalize($input_array, $mark);
#to second
$mark = $input_array[1];
$data = equalize($input_array, $mark);
if($data[1] < $output[1]) {
$output = $data;
}
#to last
$mark = $input_array[2];
$data = equalize($input_array, $mark);
if($data[1] < $output[1]) {
$output = $data;
}
echo 'Digit: ' .$output[0] .'<br/>';
echo 'Number of steps: ' .$output[1];
?>

How to calculate number of possible combinations? [duplicate]

I have an array of 7 numbers (1,2,3,4,5,6,7) and I want to choose 5 of the numbers like
(1,2,3,4,5), (1,2,3,4,6), (1,2,3,4,7).
Note that (1,2,3,4,5) is equal to (4,5,3,1,2), so only one of those should be included in the output.
I would like to know if there is a function in PHP or any algorithm that can do this ?
I have no idea where to start from.
Can you help me ?
I want all the combinations of 7 given numbers ( they are taken from an array ) put into 5 slots, disregarding order.
You can use the solution found here http://stereofrog.com/blok/on/070910.
Incase the link goes down here's the code....
class Combinations implements Iterator
{
protected $c = null;
protected $s = null;
protected $n = 0;
protected $k = 0;
protected $pos = 0;
function __construct($s, $k) {
if(is_array($s)) {
$this->s = array_values($s);
$this->n = count($this->s);
} else {
$this->s = (string) $s;
$this->n = strlen($this->s);
}
$this->k = $k;
$this->rewind();
}
function key() {
return $this->pos;
}
function current() {
$r = array();
for($i = 0; $i < $this->k; $i++)
$r[] = $this->s[$this->c[$i]];
return is_array($this->s) ? $r : implode('', $r);
}
function next() {
if($this->_next())
$this->pos++;
else
$this->pos = -1;
}
function rewind() {
$this->c = range(0, $this->k);
$this->pos = 0;
}
function valid() {
return $this->pos >= 0;
}
protected function _next() {
$i = $this->k - 1;
while ($i >= 0 && $this->c[$i] == $this->n - $this->k + $i)
$i--;
if($i < 0)
return false;
$this->c[$i]++;
while($i++ < $this->k - 1)
$this->c[$i] = $this->c[$i - 1] + 1;
return true;
}
}
foreach(new Combinations("1234567", 5) as $substring)
echo $substring, ' ';
12345 12346 12347 12356 12357 12367 12456 12457 12467 12567 13456 13457 13467 13567 14567 23456 23457 23467 23567 24567 34567
<?php
echo "<pre>";
$test = array("test_1","test_2","test_3");
// Get Combination
$return = uniqueCombination($test);
//Sort
sort($return);
//Pretty Print
print_r(array_map(function($v){ return implode(",", $v); }, $return));
function uniqueCombination($in, $minLength = 1, $max = 2000) {
$count = count($in);
$members = pow(2, $count);
$return = array();
for($i = 0; $i < $members; $i ++) {
$b = sprintf("%0" . $count . "b", $i);
$out = array();
for($j = 0; $j < $count; $j ++) {
$b{$j} == '1' and $out[] = $in[$j];
}
count($out) >= $minLength && count($out) <= $max and $return[] = $out;
}
return $return;
}
?>
output
Array
(
[0] => test_1
[1] => test_2
[2] => test_3
[3] => test_1,test_2
[4] => test_1,test_3
[5] => test_2,test_3
[6] => test_1,test_2,test_3
)
The Math_Combinatorics in PEAR repository does exactly what you want:
A package that returns all the combinations and permutations, without
repetition, of a given set and subset size. Associative arrays are
preserved.
require_once 'Math/Combinatorics.php';
$combinatorics = new Math_Combinatorics;
$input = array(1, 2, 3, 4, 5, 6, 7);
$output = $combinatorics->combinations($input, 5); // 5 is the subset size
// 1,2,3,4,5
// 1,2,3,4,6
// 1,2,3,4,7
// 1,2,3,5,6
// 1,2,3,5,7
// 1,2,3,6,7
// 1,2,4,5,6
// 1,2,4,5,7
// 1,2,4,6,7
// 1,2,5,6,7
// 1,3,4,5,6
// 1,3,4,5,7
// 1,3,4,6,7
// 1,3,5,6,7
// 1,4,5,6,7
// 2,3,4,5,6
// 2,3,4,5,7
// 2,3,4,6,7
// 2,3,5,6,7
// 2,4,5,6,7
// 3,4,5,6,7
Another solution that bases on stack. It's quit fast but eats much memory.
Hope that helps someone.
In detail:
function _combine($numbers, $length)
{
$combinations = array();
$stack = array();
// every combinations can be ordered
sort($numbers);
// startup
array_push($stack, array(
'store' => array(),
'options' => $numbers,
));
while (true) {
// pop a item
$item = array_pop($stack);
// end of stack
if (!$item) {
break;
}
// valid store
if ($length <= count($item['store'])) {
$combinations[] = $item['store'];
continue;
}
// bypass when options are not enough
if (count($item['store']) + count($item['options']) < $length) {
continue;
}
foreach ($item['options'] as $index => $n) {
$newStore = $item['store'];
$newStore[] = $n;
// every combine can be ordered
// so accept only options which is greater than store numbers
$newOptions = array_slice($item['options'], $index + 1);
// push new items
array_push($stack, array(
'store' => $newStore,
'options' => $newOptions,
));
}
}
return $combinations;
}
Improved this answer to work with associative array as well:
function uniqueCombination($values, $minLength = 1, $maxLength = 2000) {
$count = count($values);
$size = pow(2, $count);
$keys = array_keys($values);
$return = [];
for($i = 0; $i < $size; $i ++) {
$b = sprintf("%0" . $count . "b", $i);
$out = [];
for($j = 0; $j < $count; $j ++) {
if ($b[$j] == '1') {
$out[$keys[$j]] = $values[$keys[$j]];
}
}
if (count($out) >= $minLength && count($out) <= $maxLength) {
$return[] = $out;
}
}
return $return;
}
Eg:
print_r(uniqueCombination([
'a' => 'xyz',
'b' => 'pqr',
]);
Result:
Array
(
[0] => Array
(
[b] => pqr
)
[1] => Array
(
[a] => xyz
)
[2] => Array
(
[a] => xyz
[b] => pqr
)
)
It will still work for non-associative arrays:
print_r(uniqueCombination(['a', 'b']);
Result:
Array
(
[0] => Array
(
[1] => b
)
[1] => Array
(
[0] => a
)
[2] => Array
(
[0] => a
[1] => b
)
)
New solution which optimizes speed and memory for combining algorithm
Mindset: generate combinations K numbers of Array of numbers. New solution will use K 'for' statements. One 'for' One number.
Such as: $K = 5 mean that 5 of 'for' statements is used
$total = count($array);
$i0 = -1;
for ($i1 = $i0 + 1; $i1 < $total; $i1++) {
for ($i2 = $i1 + 1; $i2 < $total; $i2++) {
for ($i3 = $i2 + 1; $i3 < $total; $i3++) {
for ($i4 = $i3 + 1; $i4 < $total; $i4++) {
for ($i5 = $i4 + 1; $i5 < $total; $i5++) {
$record = array();
for ($i = 1; $i <= $k; $i++) {
$t = "i$i";
$record[] = $array[$$t];
}
$callback($record);
}
}
}
}
}
And detail of code that generated the real code that will be execute by eval() function
function combine($array, $k, $callback)
{
$total = count($array);
$init = '
$i0 = -1;
';
$sample = '
for($i{current} = $i{previous} + 1; $i{current} < $total; $i{current}++ ) {
{body}
}
';
$do = '
$record = array();
for ($i = 1; $i <= $k; $i++) {
$t = "i$i";
$record[] = $array[$$t];
}
$callback($record);
';
$for = '';
for ($i = $k; $i >= 1; $i--) {
switch ($i) {
case $k:
$for = str_replace(['{current}', '{previous}', '{body}'], [$i, $i - 1, $do], $sample);
break;
case 1:
$for = $init . str_replace(['{current}', '{previous}', '{body}'], [$i, $i - 1, $for], $sample);
break;
default:
$for = str_replace(['{current}', '{previous}', '{body}'], [$i, $i - 1, $for], $sample);
break;
}
}
// execute
eval($for);
}
How to combine K numbers of Array
$k = 5;
$array = array(1, 2, 3, 4, 5, 6, 7);
$callback = function ($record) {
echo implode($record) . "\n";
};
combine($array, $k, $callback);
I found the other answers here confusing or overly complicated, so I wrote my own. I think this is a simple solution with a recursive method. The basic idea is that you go through your array and for each item decide whether or not it is in the combination (actually, you don't decide, you recursively try both ways). You make this choice for the first item and then combine it with the recursively generated combinations of the rest of the array. This solution fills a result array with every combination of your array as a sub-array. It uses the items in order and it preserves associations, including with numeric keys.
function combinations(array $items, int $numToChoose, array &$results, $comb = []): void {
if (count($items) < $numToChoose) {
throw new \Exception("Asked to choose $numToChoose items from an array of length ". count($items));
}
// if nothing left to choose, we have a complete combination
if ($numToChoose === 0) {
$results[] = $comb;
return;
}
// if we have to choose everything at this point, then we know what to do
if (count($items) == $numToChoose) {
$results[] = $comb + $items;
return;
}
// The recursive cases: either use the first element or not and find combinations of the rest
$val = reset($items);
$key = key($items);
unset($items[$key]);
// not using it
combinations($items, $numToChoose, $results, $comb);
// using it
$comb[$key] = $val;
combinations($items, $numToChoose - 1, $results, $comb);
}
// Do a test run
$combs = [];
combinations([1=>1, 2=>2, 3=>3], 2, $combs);
var_dump($perms);
This results in the output:
array(3) {
[0]=>
array(2) {
[2]=>
int(2)
[3]=>
int(3)
}
[1]=>
array(2) {
[1]=>
int(1)
[3]=>
int(3)
}
[2]=>
array(2) {
[1]=>
int(1)
[2]=>
int(2)
}
}
I needed a combining function that included subsets, so I took #Nguyen Van Vinh's answer and modified it for my needs.
If you pass [1,2,3,4] to the function, it returns every unique combination and subset, sorted:
[
[1,2,3,4], [1,2,3], [1,2,4], [1,3,4], [2,3,4], [1,2], [1,3], [1,4], [2,3], [2,4], [3,4], [1], [2], [3], [4]
]
Here's the function:
function get_combinations_with_length( $numbers, $length ){
$result = array();
$stack = array();
// every combinations can be ordered
sort($numbers);
// startup
array_push($stack, array(
'store' => array(),
'options' => $numbers,
));
while (true) {
// pop a item
$item = array_pop($stack);
// end of stack
if (!$item) break;
// valid store
if ($length <= count($item['store'])) {
$result[] = $item['store'];
continue;
}
// bypass when options are not enough
if (count($item['store']) + count($item['options']) < $length) {
continue;
}
foreach ($item['options'] as $i=>$n) {
$newStore = $item['store'];
$newStore[] = $n;
// every combine can be ordered, so accept only options that are greater than store numbers
$newOptions = array_slice($item['options'], $i + 1);
// array_unshift to sort numerically, array_push to reverse
array_unshift($stack, array(
'store' => $newStore,
'options' => $newOptions,
));
}
}
return $result;
}
function get_all_combinations( $numbers ){
$length = count($numbers);
$result = [];
while ($length > 0) {
$result = array_merge($result, get_combinations_with_length( $numbers, $length ));
$length--;
}
return $result;
}
$numbers = [1,2,3,4];
$result = get_all_combinations($numbers);
echo 'START: '.json_encode( $numbers ).'<br><br>';
echo 'RESULT: '.json_encode( $result ).'<br><br>';
echo '('.count($result).' combination subsets found)';

PHP all permutation with N digits number [duplicate]

I have an array of 7 numbers (1,2,3,4,5,6,7) and I want to choose 5 of the numbers like
(1,2,3,4,5), (1,2,3,4,6), (1,2,3,4,7).
Note that (1,2,3,4,5) is equal to (4,5,3,1,2), so only one of those should be included in the output.
I would like to know if there is a function in PHP or any algorithm that can do this ?
I have no idea where to start from.
Can you help me ?
I want all the combinations of 7 given numbers ( they are taken from an array ) put into 5 slots, disregarding order.
You can use the solution found here http://stereofrog.com/blok/on/070910.
Incase the link goes down here's the code....
class Combinations implements Iterator
{
protected $c = null;
protected $s = null;
protected $n = 0;
protected $k = 0;
protected $pos = 0;
function __construct($s, $k) {
if(is_array($s)) {
$this->s = array_values($s);
$this->n = count($this->s);
} else {
$this->s = (string) $s;
$this->n = strlen($this->s);
}
$this->k = $k;
$this->rewind();
}
function key() {
return $this->pos;
}
function current() {
$r = array();
for($i = 0; $i < $this->k; $i++)
$r[] = $this->s[$this->c[$i]];
return is_array($this->s) ? $r : implode('', $r);
}
function next() {
if($this->_next())
$this->pos++;
else
$this->pos = -1;
}
function rewind() {
$this->c = range(0, $this->k);
$this->pos = 0;
}
function valid() {
return $this->pos >= 0;
}
protected function _next() {
$i = $this->k - 1;
while ($i >= 0 && $this->c[$i] == $this->n - $this->k + $i)
$i--;
if($i < 0)
return false;
$this->c[$i]++;
while($i++ < $this->k - 1)
$this->c[$i] = $this->c[$i - 1] + 1;
return true;
}
}
foreach(new Combinations("1234567", 5) as $substring)
echo $substring, ' ';
12345 12346 12347 12356 12357 12367 12456 12457 12467 12567 13456 13457 13467 13567 14567 23456 23457 23467 23567 24567 34567
<?php
echo "<pre>";
$test = array("test_1","test_2","test_3");
// Get Combination
$return = uniqueCombination($test);
//Sort
sort($return);
//Pretty Print
print_r(array_map(function($v){ return implode(",", $v); }, $return));
function uniqueCombination($in, $minLength = 1, $max = 2000) {
$count = count($in);
$members = pow(2, $count);
$return = array();
for($i = 0; $i < $members; $i ++) {
$b = sprintf("%0" . $count . "b", $i);
$out = array();
for($j = 0; $j < $count; $j ++) {
$b{$j} == '1' and $out[] = $in[$j];
}
count($out) >= $minLength && count($out) <= $max and $return[] = $out;
}
return $return;
}
?>
output
Array
(
[0] => test_1
[1] => test_2
[2] => test_3
[3] => test_1,test_2
[4] => test_1,test_3
[5] => test_2,test_3
[6] => test_1,test_2,test_3
)
The Math_Combinatorics in PEAR repository does exactly what you want:
A package that returns all the combinations and permutations, without
repetition, of a given set and subset size. Associative arrays are
preserved.
require_once 'Math/Combinatorics.php';
$combinatorics = new Math_Combinatorics;
$input = array(1, 2, 3, 4, 5, 6, 7);
$output = $combinatorics->combinations($input, 5); // 5 is the subset size
// 1,2,3,4,5
// 1,2,3,4,6
// 1,2,3,4,7
// 1,2,3,5,6
// 1,2,3,5,7
// 1,2,3,6,7
// 1,2,4,5,6
// 1,2,4,5,7
// 1,2,4,6,7
// 1,2,5,6,7
// 1,3,4,5,6
// 1,3,4,5,7
// 1,3,4,6,7
// 1,3,5,6,7
// 1,4,5,6,7
// 2,3,4,5,6
// 2,3,4,5,7
// 2,3,4,6,7
// 2,3,5,6,7
// 2,4,5,6,7
// 3,4,5,6,7
Another solution that bases on stack. It's quit fast but eats much memory.
Hope that helps someone.
In detail:
function _combine($numbers, $length)
{
$combinations = array();
$stack = array();
// every combinations can be ordered
sort($numbers);
// startup
array_push($stack, array(
'store' => array(),
'options' => $numbers,
));
while (true) {
// pop a item
$item = array_pop($stack);
// end of stack
if (!$item) {
break;
}
// valid store
if ($length <= count($item['store'])) {
$combinations[] = $item['store'];
continue;
}
// bypass when options are not enough
if (count($item['store']) + count($item['options']) < $length) {
continue;
}
foreach ($item['options'] as $index => $n) {
$newStore = $item['store'];
$newStore[] = $n;
// every combine can be ordered
// so accept only options which is greater than store numbers
$newOptions = array_slice($item['options'], $index + 1);
// push new items
array_push($stack, array(
'store' => $newStore,
'options' => $newOptions,
));
}
}
return $combinations;
}
Improved this answer to work with associative array as well:
function uniqueCombination($values, $minLength = 1, $maxLength = 2000) {
$count = count($values);
$size = pow(2, $count);
$keys = array_keys($values);
$return = [];
for($i = 0; $i < $size; $i ++) {
$b = sprintf("%0" . $count . "b", $i);
$out = [];
for($j = 0; $j < $count; $j ++) {
if ($b[$j] == '1') {
$out[$keys[$j]] = $values[$keys[$j]];
}
}
if (count($out) >= $minLength && count($out) <= $maxLength) {
$return[] = $out;
}
}
return $return;
}
Eg:
print_r(uniqueCombination([
'a' => 'xyz',
'b' => 'pqr',
]);
Result:
Array
(
[0] => Array
(
[b] => pqr
)
[1] => Array
(
[a] => xyz
)
[2] => Array
(
[a] => xyz
[b] => pqr
)
)
It will still work for non-associative arrays:
print_r(uniqueCombination(['a', 'b']);
Result:
Array
(
[0] => Array
(
[1] => b
)
[1] => Array
(
[0] => a
)
[2] => Array
(
[0] => a
[1] => b
)
)
New solution which optimizes speed and memory for combining algorithm
Mindset: generate combinations K numbers of Array of numbers. New solution will use K 'for' statements. One 'for' One number.
Such as: $K = 5 mean that 5 of 'for' statements is used
$total = count($array);
$i0 = -1;
for ($i1 = $i0 + 1; $i1 < $total; $i1++) {
for ($i2 = $i1 + 1; $i2 < $total; $i2++) {
for ($i3 = $i2 + 1; $i3 < $total; $i3++) {
for ($i4 = $i3 + 1; $i4 < $total; $i4++) {
for ($i5 = $i4 + 1; $i5 < $total; $i5++) {
$record = array();
for ($i = 1; $i <= $k; $i++) {
$t = "i$i";
$record[] = $array[$$t];
}
$callback($record);
}
}
}
}
}
And detail of code that generated the real code that will be execute by eval() function
function combine($array, $k, $callback)
{
$total = count($array);
$init = '
$i0 = -1;
';
$sample = '
for($i{current} = $i{previous} + 1; $i{current} < $total; $i{current}++ ) {
{body}
}
';
$do = '
$record = array();
for ($i = 1; $i <= $k; $i++) {
$t = "i$i";
$record[] = $array[$$t];
}
$callback($record);
';
$for = '';
for ($i = $k; $i >= 1; $i--) {
switch ($i) {
case $k:
$for = str_replace(['{current}', '{previous}', '{body}'], [$i, $i - 1, $do], $sample);
break;
case 1:
$for = $init . str_replace(['{current}', '{previous}', '{body}'], [$i, $i - 1, $for], $sample);
break;
default:
$for = str_replace(['{current}', '{previous}', '{body}'], [$i, $i - 1, $for], $sample);
break;
}
}
// execute
eval($for);
}
How to combine K numbers of Array
$k = 5;
$array = array(1, 2, 3, 4, 5, 6, 7);
$callback = function ($record) {
echo implode($record) . "\n";
};
combine($array, $k, $callback);
I found the other answers here confusing or overly complicated, so I wrote my own. I think this is a simple solution with a recursive method. The basic idea is that you go through your array and for each item decide whether or not it is in the combination (actually, you don't decide, you recursively try both ways). You make this choice for the first item and then combine it with the recursively generated combinations of the rest of the array. This solution fills a result array with every combination of your array as a sub-array. It uses the items in order and it preserves associations, including with numeric keys.
function combinations(array $items, int $numToChoose, array &$results, $comb = []): void {
if (count($items) < $numToChoose) {
throw new \Exception("Asked to choose $numToChoose items from an array of length ". count($items));
}
// if nothing left to choose, we have a complete combination
if ($numToChoose === 0) {
$results[] = $comb;
return;
}
// if we have to choose everything at this point, then we know what to do
if (count($items) == $numToChoose) {
$results[] = $comb + $items;
return;
}
// The recursive cases: either use the first element or not and find combinations of the rest
$val = reset($items);
$key = key($items);
unset($items[$key]);
// not using it
combinations($items, $numToChoose, $results, $comb);
// using it
$comb[$key] = $val;
combinations($items, $numToChoose - 1, $results, $comb);
}
// Do a test run
$combs = [];
combinations([1=>1, 2=>2, 3=>3], 2, $combs);
var_dump($perms);
This results in the output:
array(3) {
[0]=>
array(2) {
[2]=>
int(2)
[3]=>
int(3)
}
[1]=>
array(2) {
[1]=>
int(1)
[3]=>
int(3)
}
[2]=>
array(2) {
[1]=>
int(1)
[2]=>
int(2)
}
}
I needed a combining function that included subsets, so I took #Nguyen Van Vinh's answer and modified it for my needs.
If you pass [1,2,3,4] to the function, it returns every unique combination and subset, sorted:
[
[1,2,3,4], [1,2,3], [1,2,4], [1,3,4], [2,3,4], [1,2], [1,3], [1,4], [2,3], [2,4], [3,4], [1], [2], [3], [4]
]
Here's the function:
function get_combinations_with_length( $numbers, $length ){
$result = array();
$stack = array();
// every combinations can be ordered
sort($numbers);
// startup
array_push($stack, array(
'store' => array(),
'options' => $numbers,
));
while (true) {
// pop a item
$item = array_pop($stack);
// end of stack
if (!$item) break;
// valid store
if ($length <= count($item['store'])) {
$result[] = $item['store'];
continue;
}
// bypass when options are not enough
if (count($item['store']) + count($item['options']) < $length) {
continue;
}
foreach ($item['options'] as $i=>$n) {
$newStore = $item['store'];
$newStore[] = $n;
// every combine can be ordered, so accept only options that are greater than store numbers
$newOptions = array_slice($item['options'], $i + 1);
// array_unshift to sort numerically, array_push to reverse
array_unshift($stack, array(
'store' => $newStore,
'options' => $newOptions,
));
}
}
return $result;
}
function get_all_combinations( $numbers ){
$length = count($numbers);
$result = [];
while ($length > 0) {
$result = array_merge($result, get_combinations_with_length( $numbers, $length ));
$length--;
}
return $result;
}
$numbers = [1,2,3,4];
$result = get_all_combinations($numbers);
echo 'START: '.json_encode( $numbers ).'<br><br>';
echo 'RESULT: '.json_encode( $result ).'<br><br>';
echo '('.count($result).' combination subsets found)';

PHP check for empty spots in ranges

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

PHP array combinations

I have an array of 7 numbers (1,2,3,4,5,6,7) and I want to choose 5 of the numbers like
(1,2,3,4,5), (1,2,3,4,6), (1,2,3,4,7).
Note that (1,2,3,4,5) is equal to (4,5,3,1,2), so only one of those should be included in the output.
I would like to know if there is a function in PHP or any algorithm that can do this ?
I have no idea where to start from.
Can you help me ?
I want all the combinations of 7 given numbers ( they are taken from an array ) put into 5 slots, disregarding order.
You can use the solution found here http://stereofrog.com/blok/on/070910.
Incase the link goes down here's the code....
class Combinations implements Iterator
{
protected $c = null;
protected $s = null;
protected $n = 0;
protected $k = 0;
protected $pos = 0;
function __construct($s, $k) {
if(is_array($s)) {
$this->s = array_values($s);
$this->n = count($this->s);
} else {
$this->s = (string) $s;
$this->n = strlen($this->s);
}
$this->k = $k;
$this->rewind();
}
function key() {
return $this->pos;
}
function current() {
$r = array();
for($i = 0; $i < $this->k; $i++)
$r[] = $this->s[$this->c[$i]];
return is_array($this->s) ? $r : implode('', $r);
}
function next() {
if($this->_next())
$this->pos++;
else
$this->pos = -1;
}
function rewind() {
$this->c = range(0, $this->k);
$this->pos = 0;
}
function valid() {
return $this->pos >= 0;
}
protected function _next() {
$i = $this->k - 1;
while ($i >= 0 && $this->c[$i] == $this->n - $this->k + $i)
$i--;
if($i < 0)
return false;
$this->c[$i]++;
while($i++ < $this->k - 1)
$this->c[$i] = $this->c[$i - 1] + 1;
return true;
}
}
foreach(new Combinations("1234567", 5) as $substring)
echo $substring, ' ';
12345 12346 12347 12356 12357 12367 12456 12457 12467 12567 13456 13457 13467 13567 14567 23456 23457 23467 23567 24567 34567
<?php
echo "<pre>";
$test = array("test_1","test_2","test_3");
// Get Combination
$return = uniqueCombination($test);
//Sort
sort($return);
//Pretty Print
print_r(array_map(function($v){ return implode(",", $v); }, $return));
function uniqueCombination($in, $minLength = 1, $max = 2000) {
$count = count($in);
$members = pow(2, $count);
$return = array();
for($i = 0; $i < $members; $i ++) {
$b = sprintf("%0" . $count . "b", $i);
$out = array();
for($j = 0; $j < $count; $j ++) {
$b{$j} == '1' and $out[] = $in[$j];
}
count($out) >= $minLength && count($out) <= $max and $return[] = $out;
}
return $return;
}
?>
output
Array
(
[0] => test_1
[1] => test_2
[2] => test_3
[3] => test_1,test_2
[4] => test_1,test_3
[5] => test_2,test_3
[6] => test_1,test_2,test_3
)
The Math_Combinatorics in PEAR repository does exactly what you want:
A package that returns all the combinations and permutations, without
repetition, of a given set and subset size. Associative arrays are
preserved.
require_once 'Math/Combinatorics.php';
$combinatorics = new Math_Combinatorics;
$input = array(1, 2, 3, 4, 5, 6, 7);
$output = $combinatorics->combinations($input, 5); // 5 is the subset size
// 1,2,3,4,5
// 1,2,3,4,6
// 1,2,3,4,7
// 1,2,3,5,6
// 1,2,3,5,7
// 1,2,3,6,7
// 1,2,4,5,6
// 1,2,4,5,7
// 1,2,4,6,7
// 1,2,5,6,7
// 1,3,4,5,6
// 1,3,4,5,7
// 1,3,4,6,7
// 1,3,5,6,7
// 1,4,5,6,7
// 2,3,4,5,6
// 2,3,4,5,7
// 2,3,4,6,7
// 2,3,5,6,7
// 2,4,5,6,7
// 3,4,5,6,7
Another solution that bases on stack. It's quit fast but eats much memory.
Hope that helps someone.
In detail:
function _combine($numbers, $length)
{
$combinations = array();
$stack = array();
// every combinations can be ordered
sort($numbers);
// startup
array_push($stack, array(
'store' => array(),
'options' => $numbers,
));
while (true) {
// pop a item
$item = array_pop($stack);
// end of stack
if (!$item) {
break;
}
// valid store
if ($length <= count($item['store'])) {
$combinations[] = $item['store'];
continue;
}
// bypass when options are not enough
if (count($item['store']) + count($item['options']) < $length) {
continue;
}
foreach ($item['options'] as $index => $n) {
$newStore = $item['store'];
$newStore[] = $n;
// every combine can be ordered
// so accept only options which is greater than store numbers
$newOptions = array_slice($item['options'], $index + 1);
// push new items
array_push($stack, array(
'store' => $newStore,
'options' => $newOptions,
));
}
}
return $combinations;
}
Improved this answer to work with associative array as well:
function uniqueCombination($values, $minLength = 1, $maxLength = 2000) {
$count = count($values);
$size = pow(2, $count);
$keys = array_keys($values);
$return = [];
for($i = 0; $i < $size; $i ++) {
$b = sprintf("%0" . $count . "b", $i);
$out = [];
for($j = 0; $j < $count; $j ++) {
if ($b[$j] == '1') {
$out[$keys[$j]] = $values[$keys[$j]];
}
}
if (count($out) >= $minLength && count($out) <= $maxLength) {
$return[] = $out;
}
}
return $return;
}
Eg:
print_r(uniqueCombination([
'a' => 'xyz',
'b' => 'pqr',
]);
Result:
Array
(
[0] => Array
(
[b] => pqr
)
[1] => Array
(
[a] => xyz
)
[2] => Array
(
[a] => xyz
[b] => pqr
)
)
It will still work for non-associative arrays:
print_r(uniqueCombination(['a', 'b']);
Result:
Array
(
[0] => Array
(
[1] => b
)
[1] => Array
(
[0] => a
)
[2] => Array
(
[0] => a
[1] => b
)
)
New solution which optimizes speed and memory for combining algorithm
Mindset: generate combinations K numbers of Array of numbers. New solution will use K 'for' statements. One 'for' One number.
Such as: $K = 5 mean that 5 of 'for' statements is used
$total = count($array);
$i0 = -1;
for ($i1 = $i0 + 1; $i1 < $total; $i1++) {
for ($i2 = $i1 + 1; $i2 < $total; $i2++) {
for ($i3 = $i2 + 1; $i3 < $total; $i3++) {
for ($i4 = $i3 + 1; $i4 < $total; $i4++) {
for ($i5 = $i4 + 1; $i5 < $total; $i5++) {
$record = array();
for ($i = 1; $i <= $k; $i++) {
$t = "i$i";
$record[] = $array[$$t];
}
$callback($record);
}
}
}
}
}
And detail of code that generated the real code that will be execute by eval() function
function combine($array, $k, $callback)
{
$total = count($array);
$init = '
$i0 = -1;
';
$sample = '
for($i{current} = $i{previous} + 1; $i{current} < $total; $i{current}++ ) {
{body}
}
';
$do = '
$record = array();
for ($i = 1; $i <= $k; $i++) {
$t = "i$i";
$record[] = $array[$$t];
}
$callback($record);
';
$for = '';
for ($i = $k; $i >= 1; $i--) {
switch ($i) {
case $k:
$for = str_replace(['{current}', '{previous}', '{body}'], [$i, $i - 1, $do], $sample);
break;
case 1:
$for = $init . str_replace(['{current}', '{previous}', '{body}'], [$i, $i - 1, $for], $sample);
break;
default:
$for = str_replace(['{current}', '{previous}', '{body}'], [$i, $i - 1, $for], $sample);
break;
}
}
// execute
eval($for);
}
How to combine K numbers of Array
$k = 5;
$array = array(1, 2, 3, 4, 5, 6, 7);
$callback = function ($record) {
echo implode($record) . "\n";
};
combine($array, $k, $callback);
I found the other answers here confusing or overly complicated, so I wrote my own. I think this is a simple solution with a recursive method. The basic idea is that you go through your array and for each item decide whether or not it is in the combination (actually, you don't decide, you recursively try both ways). You make this choice for the first item and then combine it with the recursively generated combinations of the rest of the array. This solution fills a result array with every combination of your array as a sub-array. It uses the items in order and it preserves associations, including with numeric keys.
function combinations(array $items, int $numToChoose, array &$results, $comb = []): void {
if (count($items) < $numToChoose) {
throw new \Exception("Asked to choose $numToChoose items from an array of length ". count($items));
}
// if nothing left to choose, we have a complete combination
if ($numToChoose === 0) {
$results[] = $comb;
return;
}
// if we have to choose everything at this point, then we know what to do
if (count($items) == $numToChoose) {
$results[] = $comb + $items;
return;
}
// The recursive cases: either use the first element or not and find combinations of the rest
$val = reset($items);
$key = key($items);
unset($items[$key]);
// not using it
combinations($items, $numToChoose, $results, $comb);
// using it
$comb[$key] = $val;
combinations($items, $numToChoose - 1, $results, $comb);
}
// Do a test run
$combs = [];
combinations([1=>1, 2=>2, 3=>3], 2, $combs);
var_dump($perms);
This results in the output:
array(3) {
[0]=>
array(2) {
[2]=>
int(2)
[3]=>
int(3)
}
[1]=>
array(2) {
[1]=>
int(1)
[3]=>
int(3)
}
[2]=>
array(2) {
[1]=>
int(1)
[2]=>
int(2)
}
}
I needed a combining function that included subsets, so I took #Nguyen Van Vinh's answer and modified it for my needs.
If you pass [1,2,3,4] to the function, it returns every unique combination and subset, sorted:
[
[1,2,3,4], [1,2,3], [1,2,4], [1,3,4], [2,3,4], [1,2], [1,3], [1,4], [2,3], [2,4], [3,4], [1], [2], [3], [4]
]
Here's the function:
function get_combinations_with_length( $numbers, $length ){
$result = array();
$stack = array();
// every combinations can be ordered
sort($numbers);
// startup
array_push($stack, array(
'store' => array(),
'options' => $numbers,
));
while (true) {
// pop a item
$item = array_pop($stack);
// end of stack
if (!$item) break;
// valid store
if ($length <= count($item['store'])) {
$result[] = $item['store'];
continue;
}
// bypass when options are not enough
if (count($item['store']) + count($item['options']) < $length) {
continue;
}
foreach ($item['options'] as $i=>$n) {
$newStore = $item['store'];
$newStore[] = $n;
// every combine can be ordered, so accept only options that are greater than store numbers
$newOptions = array_slice($item['options'], $i + 1);
// array_unshift to sort numerically, array_push to reverse
array_unshift($stack, array(
'store' => $newStore,
'options' => $newOptions,
));
}
}
return $result;
}
function get_all_combinations( $numbers ){
$length = count($numbers);
$result = [];
while ($length > 0) {
$result = array_merge($result, get_combinations_with_length( $numbers, $length ));
$length--;
}
return $result;
}
$numbers = [1,2,3,4];
$result = get_all_combinations($numbers);
echo 'START: '.json_encode( $numbers ).'<br><br>';
echo 'RESULT: '.json_encode( $result ).'<br><br>';
echo '('.count($result).' combination subsets found)';

Categories