Matching the sum of values on string - php

I have a string of numerical values separated with spaces:
70 58 81 909 70 215 70 1022 580 930 898 70 276 31 11 **920 898** 1503 195 770 573 508 1015 31 8 815 1478 31 1022 31 1506 31 **318 500 358 865** 358 991 518 58 450 420 487 31 1478 108 70 1022 31 215 318 500 61 31 655 1061 918 54 898 31 8 1011 9 8 459 346 770 751 31 346 770 880 1171 688 1680 31 1002 769 500 61 8 702 898 8 1206 31 709 565 8 138 58 215 81 1171 31 288 500 380 70 284 1500 565 31 601 55 1501 490 565 530 56 990 380 1061 770 345 1171 31 55 1100 605 1471 1234 **31 470 725 358 114 56 9 55** 1100 1610 1471 1000 971 565 55 1100 1610 1061 770 345 949 31 370 52 688 1680 770 880 1171 163 249 151 489 653 56 990 380 503 490 770 1376 1056 31 8 64 490 565 55 108 56 1178 501 653 898 860 565 31 315 61 509 108 501 653 31 349 249 151 489 246 56 990 380 1070 573 1663 725 821 31 70 373 1171 490 565 55 108...
I want to get all occurrences of the string, where combined sum of numbers is x. So, if I search: 2041 it should return an array ("318 500 358 865") or if I search: 1818 it should return an array ("920 898", "31 470 725 358 114 56 9 55").
I also need solution to be optimal, because string of numbers can sum up to hundreds of thousands.
Principle explained in theory would be good, but preferred languages are PHP or NodeJs if solution is given by programming language. Even MySQL solution would be nice, if possible. But solution by any language would be nice anyway.
Extra notes
numbers are all positive integers
number values are between 1-10000

Usage:
$ex = new finder($string);
$result = $ex->search(979)->find(true); // true = Hard mode
First we pass the string we want to search, then we use method to_array() so we can create the array from the string, then you need to choose which mode you want to use.
It has 2 modes, soft search and hard search.
you can choose which one to use in the find method, ->find(true) , True = hard mode, False = soft mode;
The soft mode it is really simple, creates a new multidimensional array with keys as the strlen of the numbers (method order()), so if we are searching for a number 70 we don't really wanna use the 3 digits or more, i mean ( 100, 99999 etc... ). Then it just minus the search value with the number and tries to search in the entire array with (in_array) if it has the result;
foreach ($this->_clean as $clean) {
$minus = abs($clean - $search);
// Simple and fast query
if(in_array($minus, $this->_clean)){
$tmp[] = array($minus,$clean);
}
}
The hard mode is just as the name states, we will loop through every number.
public function hard_search($array, $partial){
$s = 0;
foreach ($partial as $x){
$s += $x;
}
if ($s == $this->_search){
$this->_tmp[] = $partial;
}
if ($s < $this->_search){
for($i=0;$i<count($array);$i++) {
$remaining = array();
$n = $array[$i];
for ($j=$i+1; $j<count($array); $j++) {
array_push($remaining, $array[$j]);
};
$partial_rec = $partial;
array_push($partial_rec, $n);
$this->hard_search($remaining,$partial_rec);
}
}
return $this->_tmp;
}
Of course we could really make a few more tweaks but from what i tested it gives good results.
Feel free to ask if you have any questions.
Example with outputs: http://codepad.viper-7.com/INvSNo
class finder {
public $stop;
protected $_string,
$_search,
$_tmp,
$_array = array(),
$_array_order = array(),
$_clean = array();
public function __construct($string){
$this->_string = $string;
}
/**
* String to array
*
* #return \find
*/
public function to_array(){
$this->_array = array_keys(
array_flip(
explode(' ', $this->_string)
)
);
return $this;
}
/**
* what we are searching for
*
* #param string/int $search
* #return \finder
*/
public function search($search){
$this->reset();
$this->_search = $search;
$this->to_array();
return $this;
}
/**
*
* #param type $a // array
* #param type $total // total
* #return array
*/
public function find($hard = false){
$this->_hard = $hard;
if(is_array($this->_search)){
foreach($this->_search as $search){
$result[] = $this->search_array($search);
}
}else{
$result = $this->search_array($this->_search);
}
return $result;
}
/**********************************
* SOFT SEARCH
***********************************/
/**
* Multidimensional Array with strlen as the key
*
* #return \find
*/
public function order(){
// Order
foreach($this->_array as $n){
$this->_array_order[strlen($n)][] = $n;
}
return $this;
}
public function clean($search){
$tmp = array();
$check_length = function($number) use($search){
if($number <= $search){
return $number;
}
return false;
};
foreach(range(key($this->_array_order), strlen($search)) as $v){
$tmp = array_map($check_length,array_merge($tmp, $this->_array_order[$v]));
}
$this->_clean = array_filter($tmp);
sort($this->_clean);
return $this;
}
public function search_array($search) {
$res = array();
if($this->_hard == false){
$this->order();
$this->clean($search);
$res = $this->soft_search($search);
}else{
$res = $this->hard_search($this->_array, array());
}
return $res;
}
/**
*
* #return array
*/
public function soft_search($search){
$tmp = array();
foreach ($this->_clean as $clean) {
$minus = abs($clean - $search);
// Simple and fast query
if(in_array($minus, $this->_clean)){
$tmp[] = array($minus,$clean);
}
}
return $tmp;
}
/**********************************
* END SOFT SEARCH
***********************************/
public function hard_search($array, $partial){
$s = 0;
foreach ($partial as $x){
$s += $x;
}
if ($s == $this->_search){
$this->_tmp[] = $partial;
}
// if higher STOP
if ($s < $this->_search){
for($i=0;$i<count($array);$i++) {
$remaining = array();
$n = $array[$i];
for ($j=$i+1; $j<count($array); $j++) {
array_push($remaining, $array[$j]);
};
$partial_rec = $partial;
array_push($partial_rec, $n);
$this->hard_search($remaining,$partial_rec);
}
}
return $this->_tmp;
}
/****************************
*
* Extra
*
*****************************/
public function reset(){
$this->_search = '';
$this->_clean = array();
$this->_array = array();
$this->_array_order = array();
$this->_tmp = array();
$this->_tmp = array();
return $this;
}
public function new_string($string){
$this->reset();
$this->_string = $string;
$this->to_array();
return $this;
}
}
$string = '70 58 81 909 70 215 130 70 1022 580 930 898 70 276 31 11 920 898 1503 195 770 573 508 '
. '1171 490 565 55 108';
$ex = new finder($string);
echo '<pre>';
echo '<h1>Hard 979</h1>';
$result = $ex->search(979)->find(true);
print_r($result);
echo '<h1>Soft 979</h1>';
$result = $ex->search(979)->find();
print_r($result);
echo '<h1>Hard 238</h1>';
$result = $ex->search(238)->find(true);
print_r($result);
echo '<h1>Soft 238</h1>';
$result = $ex->search(238)->find();
print_r($result);
Output example for 285:
Hard 285
Array
(
[0] => Array
(
[0] => 70
[1] => 215
)
[1] => Array
(
[0] => 58
[1] => 130
[2] => 31
[3] => 11
[4] => 55
)
)
Soft 285
Array
(
[0] => Array
(
[0] => 215
[1] => 70
)
)

Here you go, in Python. This has linear complexity:
def list2string( alist ):
return " ".join( map(str, alist ))
def string2list( s ):
return list(map( int, s.split() ))
def find_number( a, total ):
u = 0
y = 0 # the current sum of the interval (u .. v)
res = []
for v in range( 0, len(a) ):
# at this point the interval sum y is smaller than the requested total,
# so we move the right end of the interval forward
y += a[v]
while y >= total:
if y == total:
res.append( list2string( a[ u : v+1 ] ) )
# if the current sum is too large, move the left end of the interval forward
y -= a[u]
u += 1
return res
text = "70 58 81 909 70 215 70 1022 580 930 898"
alist = string2list(text)
print( find_number( alist, 285) )
I'm assuming there are no negative values in your list, and the list consists purely of integers separated by spaces. You shouldn't have any problems translating it into any other language. I hope the algorithm is self-explanatory, if not ask.

Related

How to print all possible variations without repetitions of array for given range?

This is what I got:
<?php
// Program to print all
// combination of size r
// in an array of size n
function printCombinations($arr, $n, $r) {
$data = [];
combinationUtil($arr, $data, 0, $n - 1, 0, $r);
}
function combinationUtil($arr, $data, $start, $end, $index, $r) {
if ($index == $r) {
for ($j = 0; $j < $r; $j++) {
echo $data[$j];
}
echo "<br>";
return;
}
for ($i = $start; $i <= $end && $end - $i + 1 >= $r - $index; $i++) {
$data[$index] = $arr[$i];
combinationUtil($arr, $data, $i + 1, $end, $index + 1, $r);
}
}
$arr = [
1,
2,
3,
4,
5
];
$r = 3;
$n = count($arr);
printCombinations($arr, $n, $r);
and it gives this output:
123
124
125
134
135
145
234
235
245
345
And what I need is this:
123
124
125
132
134
135
142
143
145
152
153
154
213
214
215
231
234
235
241
243
245
251
253
254
312
314
315
321
324
325
341
342
345
351
352
354
412
413
414
415
421
423
425
431
432
435
451
452
453
512
513
514
521
523
524
531
532
534
541
542
543
You can use a recursive approach, that iterates over the array, and calls itself in each iteration by removing the current element, and prepending the returned variations with it.
Something like this:
<?php
function variation_without_repetition ($array,$items){
if($items == 0 || count($array) == 0) return [[]];
$variations = [];
foreach($array as $index => $item){
if(array_search($item, $array) < $index) continue;
$array_remaining = $array;
array_splice($array_remaining,$index,1);
foreach(variation_without_repetition($array_remaining,$items - 1) as $variation){
array_unshift($variation,$item);
$variations[] = $variation;
}
}
return $variations;
}
$variations = variation_without_repetition([1,2,3,4,5], 3);
foreach($variations as $variation){
echo implode($variation);
echo "<br>\n";
}
?>

Is there a way to remove two numbers not affecting others?

The code is giving the following output and each time, it will give a different output.
Is there any way to remove to 2 numbers for example 15 and 60, so that other numbers are not effected they remain the same?
I have tried but could not get the desired result.
Any help or any idea how to do this?
<?php
$arr = array();
for ($i = 1; $i < 82; $i++) {
$arr[] = $i;
}
shuffle($arr);
$lines = array_chunk($arr, 9);
echo "<table>";
foreach ($lines as $key => $line) {
echo "<tr>";
for ($i = 0; $i < sizeof($line); $i++) {
echo "<td align='right'>" . $line[$i] . "</td>";
}
echo "</tr>";
}
echo "</table>";
?>
Out put
52 67 5 44 76 49 1 27 28
73 33 19 66 4 14 63 45 62
26 75 50 80 70 38 12 54 78
9 69 36 32 2 7 56 11 51
40 20 22 15 60 65 31 41 77
57 29 34 79 68 23 18 71 39
42 10 72 17 81 30 35 48 47
37 59 53 6 3 55 13 46 58
61 25 24 64 74 43 21 8 16
This should work for you:
Just place the code before your shuffle() call. Basically first we create an array of patterns, by looping through all numbers, which we want to extract from the list, with array_map().
The pattern basically has two anchors to match the numbers fully in your array and not only partially (E.g. 2 will not match 24, but it will match 2).
With the created pattern array we just use preg_replace() to replace the numbers in your array with an empty string.
$numbers = [15, 60];
$pattern = array_map(function($v){
return "/^$v$/";
}, $numbers);
$arr = preg_replace($pattern, "", $arr);

How to show these numbers in tabular format?

This code is giving the following output, I want to show these numbers in table is there any way to do this? and why it is not showing the numbers in proper format like why 53 is not exactly below 40 and others also not showing in proper order?
<?php
$arr = array();
for ($i=1;$i<82;$i++) {
$arr[] = $i;
}
shuffle($arr);
$lines = array_chunk($arr, 9);
foreach ($lines as $key => $line) {
$lines[$key] = implode("&nbsp&nbsp&nbsp", $line);
}
echo implode("<br>", $lines);
?>
Output
73 40 79 1 43 7 76 44 18
6 53 45 55 71 20 80 66 74
69 51 52 65 22 63 59 50 54
29 33 23 49 77 24 61 60 58
8 81 30 15 26 32 16 47 31
17 39 4 35 27 11 5 25 68
2 34 72 42 75 46 48 3 38
14 28 37 62 10 78 12 56 13
41 21 19 36 9 64 67 57 70
If you don't want to use tables or CSS. You can use a <pre> tag then use some tabs and newlines
<pre>
<?php
$arr = array();
for ($i=1;$i<82;$i++) {
$arr[] = $i;
}
shuffle($arr);
$lines = array_chunk($arr, 9);
foreach ($lines as $key => $line) {
$lines[$key] = implode("\t", $line);
}
echo implode("\n", $lines);
?>
Fiddle
Note: I didn't really care about your logic for creating those lines while answering this question since its only about formatting. You can trim down some code too.
Output
22 16 66 79 8 41 47 2 80
29 38 76 18 40 46 73 34 45
31 3 62 68 14 33 20 72 67
78 44 42 30 51 77 36 25 48
64 70 21 15 19 9 56 50 65
37 27 4 1 35 74 75 52 32
81 23 10 28 26 59 7 54 11
6 63 5 39 53 12 24 60 49
71 55 17 13 61 69 43 57 58
Now with slightly better code
<pre>
<?php
$numbers=range(1,81);
shuffle($numbers);
$c=0;
foreach($numbers as $n)
{
if($c%9==0)echo "\n";
echo $n."\t";
$c++;
}
?>
modify your code like this:
<?php
$arr = array();
for ($i=1;$i<82;$i++) {
$arr[] = $i;
}
shuffle($arr);
$lines = array_chunk($arr, 9);
echo '<table>';
foreach ($lines as $key => $line) {
echo '<tr><td align="right">';
echo $lines[$key] = implode('</td><td align="right">', $line);
echo '</td></tr>';
}
echo '</table>';
?>
Output
57 41 48 17 73 76 7 78 12
69 61 39 80 24 58 45 11 70
47 65 33 21 38 4 19 13 46
59 52 63 14 25 3 30 28 77
50 40 68 6 2 29 20 66 26
72 74 34 75 15 36 71 10 60
55 53 1 16 23 42 51 35 62
44 32 43 64 18 8 54 49 5
81 27 31 67 37 22 79 56 9
try this
<?php
$arr = array();
for ($i = 1; $i < 82; $i++) {
$arr[] = $i;
}
shuffle($arr);
$lines = array_chunk($arr, 9);
echo "<table>";
foreach ($lines as $key => $line) {
echo "<tr>";
for ($i = 0; $i < sizeof($line); $i++) {
echo "<td align='right'>" . $line[$i] . "</td>";
}
echo "</tr>";
}
echo "</table>";
?>
Your Out put

Finding and removing outliers in PHP

Suppose I sample a selection of database records that return the following numbers:
20.50, 80.30, 70.95, 15.25, 99.97, 85.56, 69.77
Is there an algorithm that can be efficiently implemented in PHP to find the outliers (if there are any) from an array of floats based on how far they deviate from the mean?
Ok let's assume you have your data points in an array like so:
<?php $dataset = array(20.50, 80.30, 70.95, 15.25, 99.97, 85.56, 69.77); ?>
Then you can use the following function (see comments for what is happening) to remove all numbers that fall outside of the mean +/- the standard deviation times a magnitude you set (defaults to 1):
<?php
function remove_outliers($dataset, $magnitude = 1) {
$count = count($dataset);
$mean = array_sum($dataset) / $count; // Calculate the mean
$deviation = sqrt(array_sum(array_map("sd_square", $dataset, array_fill(0, $count, $mean))) / $count) * $magnitude; // Calculate standard deviation and times by magnitude
return array_filter($dataset, function($x) use ($mean, $deviation) { return ($x <= $mean + $deviation && $x >= $mean - $deviation); }); // Return filtered array of values that lie within $mean +- $deviation.
}
function sd_square($x, $mean) {
return pow($x - $mean, 2);
}
?>
For your example this function returns the following with a magnitude of 1:
Array
(
[1] => 80.3
[2] => 70.95
[5] => 85.56
[6] => 69.77
)
For a normally distributed set of data, removes values more than 3 standard deviations from the mean.
<?php
function remove_outliers($array) {
if(count($array) == 0) {
return $array;
}
$ret = array();
$mean = array_sum($array)/count($array);
$stddev = stats_standard_deviation($array);
$outlier = 3 * $stddev;
foreach($array as $a) {
if(!abs($a - $mean) > $outlier) {
$ret[] = $a;
}
}
return $ret;
}
Topic: Detecting local, additive outliers in unordered arrays by walking a small window through the array and calculating the standard deviation for a certain range of values.
Good morning folks,
here is my solution much to late, but since I was looking for detecting outliers via PHP and could'nt find anything basic, I decided somehow smoothing a given dataset in a timeline of 24 h by simply moving a range of 5 items in a row through an unordered array and calculate the local standard deviation to detect the additive outliers.
The first function will simply calculate the average and deviation of a given array, where $col means the column with the values (sorry for the freegrades, this means that in an uncomplete dataset of 5 values you only have 4 freegrades - I don't know the exact english word for Freiheitsgrade):
function analytics_stat ($arr,$col,$freegrades = 0) {
// calculate average called mu
$mu = 0;
foreach ($arr as $row) {
$mu += $row[$col];
}
$mu = $mu / count($arr);
// calculate empiric standard deviation called sigma
$sigma = 0;
foreach ($arr as $row) {
$sigma += pow(($mu - $row[$col]),2);
}
$sigma = sqrt($sigma / (count($arr) - $freegrades));
return [$mu,$sigma];
}
Now its time for the core function, which will move through the given array and create a new array with the result. Margin means the factor to multiply the deviation with, since only one Sigma detects to many outliers, whereas more than 1.7 seems to high:
function analytics_detect_local_outliers ($arr,$col,$range,$margin = 1.0) {
$count = count($arr);
if ($count < $range) return false;
// the initial state of each value is NOT OUTLIER
$arr_result = [];
for ($i = 0;$i < $count;$i++) {
$arr_result[$i] = false;
}
$max = $count - $range + 1;
for ($i = 0;$i < $max;$i++) {
// calculate mu and sigma for current interval
// remember that 5 values will determine the divisor 4 for sigma
// since we only look at a part of the hole data set
$stat = analytics_stat(array_slice($arr,$i,$range),$col,1);
// a value in this interval counts, if it's found outside our defined sigma interval
$range_max = $i + $range;
for ($j = $i;$j < $range_max;$j++) {
if (abs($arr[$j][$col] - $stat[0]) > $margin * $stat[1]) {
$arr_result[$j] = true;
// this would be the place to add a counter to isolate
// real outliers from sudden steps in our data set
}
}
}
return $arr_result;
}
And finally comes the test function with random values in an array with length 24.
As for margin I was curious and choose the Golden Cut PHI = 1.618 ... since I really like this number and some Excel test results have led me to a margin of 1.7, above which outliers very rarelly were detected. The range of 5 is variable, but for me this was enough. So for every 5 values in a row there will be a calculation:
function test_outliers () {
// create 2 dimensional data array with items [hour,value]
$arr = [];
for ($i = 0;$i < 24;$i++) {
$arr[$i] = [$i,rand(0,500)];
}
// set parameter for detection algorithm
$result = [];
$col = 1;
$range = 5;
$margin = 1.618;
$result = analytics_detect_local_outliers ($arr,$col,$range,$margin);
// display results
echo "<p style='font-size:8pt;'>";
for ($i = 0;$i < 24;$i++) {
if ($result[$i]) echo "♦".$arr[$i][1]."♦ "; else echo $arr[$i][1]." ";
}
echo "</p>";
}
After 20 calls of the test function I got these results:
417 140 372 131 449 26 192 222 320 349 94 147 201 ♦342♦ 123 16 15
♦490♦ 78 190 ♦434♦ 27 3 276
379 440 198 135 22 461 208 376 286 ♦73♦ 331 358 341 14 112 190 110 266
350 232 265 ♦63♦ 90 94
228 ♦392♦ 130 134 170 ♦485♦ 17 463 13 326 47 439 430 151 268 172 342
445 477 ♦21♦ 421 440 219 95
88 121 292 255 ♦16♦ 223 244 109 127 231 370 16 93 379 218 87 ♦335♦ 150
84 181 25 280 15 406
85 252 310 122 188 302 ♦13♦ 439 254 414 423 216 456 321 85 61 215 7
297 337 204 210 106 149
345 411 308 360 308 346 ♦451♦ ♦77♦ 16 498 331 160 142 102 ♦496♦ 220
107 143 ♦241♦ 113 82 355 114 452
490 222 412 94 2 ♦480♦ 181 149 41 110 220 ♦477♦ 278 349 73 186 135 181
♦39♦ 136 284 340 165 438
147 311 246 449 396 328 330 280 453 374 214 289 489 185 445 86 426 246
319 ♦30♦ 436 290 384 232
442 302 ♦436♦ 50 114 15 21 93 ♦376♦ 416 439 ♦222♦ 398 237 234 44 102
464 204 421 161 330 396 461
498 320 105 22 281 168 381 216 435 360 19 ♦402♦ 131 128 66 187 291 459
319 433 86 84 325 247
440 491 381 491 ♦22♦ 412 33 273 256 331 79 452 314 485 66 138 116 356
290 190 336 178 298 218
394 439 387 ♦80♦ 463 369 ♦104♦ 388 465 455 ♦246♦ 499 70 431 360 ♦22♦
203 280 241 319 ♦34♦ 238 439 497
485 289 249 ♦416♦ 228 166 217 186 184 ♦356♦ 142 166 26 91 70 ♦466♦ 177
357 298 443 307 387 373 209
338 166 90 122 442 429 499 293 ♦41♦ 159 395 79 307 91 325 91 162 211
85 189 278 251 224 481
77 196 37 326 230 281 ♦73♦ 334 159 490 127 365 37 57 246 26 285 468
228 181 74 ♦455♦ 119 435
328 3 216 149 217 348 65 433 164 473 465 145 341 112 462 396 168 251
351 43 320 123 181 198
216 213 249 219 ♦29♦ 255 100 216 181 233 33 47 344 383 ♦94♦ 323 440
187 79 403 139 382 37 395
366 450 263 160 290 ♦126♦ 304 307 335 396 458 195 171 493 270 434 222
401 38 383 158 355 311 150
402 339 382 97 125 88 300 332 250 ♦86♦ 362 214 448 67 114 ♦354♦ 140 16
♦354♦ 109 0 168 127 89
450 5 232 155 159 264 214 ♦416♦ 51 429 372 230 298 232 251 207 ♦322♦
160 148 206 293 446 111 338
I hope, this will help anyone in the present or future.
Greetings
P.S. To further improve this algorithm you may add a counter, which makes sure, that a certain value must for instance be found at least 2 times, that means in 2 different intervals or windows, before it is labeled as outlier. So a sudden jump of the following values does not make the first value the villain. Let me give you an example:
In 3,6,5,9,37,40,42,51,98,39,33,45 there is an obvious step from 9 to 37 and an isolated value 98. I would like to detect 98, but not 9 or 37.
The first interval 3,6,5,9,37 would detect 37, the second interval 6,5,9,37,40 not. So we would not detect 37, since there is only one problematic interval or one match. Now it should be clear, that 98 counts in 5 intervals and is therefore an outlier. So lets declare a value an outlier, if it "counts" at least 2 times.
Like so often we have to look closely the borders, since they have only one interval, and make for these values an exception.

Group PHP array numbers

How I can change this:
Array
(
[0] => 1
[1] => 2
[2] => 3
[3] => 4
[4] => 5
[5] => 6
[6] => 7
[7] => 11
[8] => 21
[9] => 22
[10] => 23
[11] => 24
)
To this:
1-7, 11, 21-24
I have a list of numbers like this in PHP array, and I just want to make this list a little bit smaller.
2000: 3 6 7 11 15 17 25 36 42 43 45
2001: 2 3 4 5 6 9 10 11 12 13 34 37 45 46 47 48 49 50 51 52
2002: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 33 34 35 36 37 39 40 41 42 43 44 45 46 47 48 49 50 51 52
2003: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
2004: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 16 17 18 19 21 22 23 24 25 26 27 28 29 30 31 32 33 34 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
2005: 1 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
2006: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
2007: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
Interesting task.
Here's a demo script that does exactly what you want.
Tweak to taste.
Code
<?php
$groups = array();
$active_group = 0;
$output = array();
$output_counter = 0;
$nums = array( 1, 2, 3, 4, 5, 6, 7, 11, 21, 22, 23, 24 );
foreach( $nums as $k => $num ) {
// if this isn't the first item, and the current number
// isn't one more than the previous one, increment the counter
if( $k !== 0 && $nums[$k] !== $nums[$k-1]+1 )
$active_group ++;
// add this number to a group
$groups[ $active_group ][] = $num;
}
// take the 1st and last of each group
foreach( $groups as $group ) {
$first = array_shift( array_values($group) );
$output[$output_counter][] = $first;
$last = array_pop( array_values($group) );
if( $first !== $last )
$output[$output_counter][] = $last;
$output_counter++;
}
echo '<pre>';
print_r($output);
?>
Output
Array
(
[0] => Array
(
[0] => 1
[1] => 7
)
[1] => Array
(
[0] => 11
)
[2] => Array
(
[0] => 21
[1] => 24
)
)
A single loop will do. You need need to keep track of the "previous" iteration's value and the "starting" value for storing ranged data.
Code:
$prev = -1; // initialize out of range
foreach ($numbers as $n) {
if (!isset($start)) { // first iteration
$start = $n; // declare $start
} elseif ($n != $prev + 1) { // not consecutive
$result[] = $start == $prev ? $prev : "$start-$prev"; // store single or ranged values
$start = $n; // update $start
}
$prev = $n; // declare / update $prev
}
$result[] = $start == $prev ? $prev : $start . '-' . $prev; // store final iteration data
echo implode(', ', $result); // comma delimit the values
Output from: $numbers = [1, 2, 3, 4, 5, 6, 7, 11, 21, 22, 23, 24]; (Demo)
1-7, 11, 21-24
Output from: $numbers = [1, 3, 5, 6, 11, 21, 22, 23, 24, 26]; (Demo)
1, 3, 5-6, 11, 21-24, 26
Here is a way to both compress an array of integers into the string format you want and to expand that string format back out to an array of integers.
function compress($expanded) {
$low = -1;
$prevNum = -1;
$expanded = array_unique($expanded);
sort($expanded, SORT_NUMERIC);
foreach($expanded as $num) {
if($low == -1) {
$low = $num;
} else if($num - $prevNum > 1) {
$compact[] = ($prevNum - $low >= 1) ? sprintf("%d-%d", $low, $prevNum) : $prevNum;
$low = $num;
}
$prevNum = $num;
}
if($low != -1 ) {
$compact[] = ($num - $low >= 1) ? sprintf("%d-%d", $low, $num) : $num;
}
return implode(",", $compact);
}
public static function expand($compact) {
$expanded = Array();
$compact = explode(",", $compact);
foreach($compact as $num) {
if( is_numeric($num) ) {
$expanded[] = $num;
} else {
list($low, $high) = explode("-", $num);
if( is_numeric($low) && is_numeric($high) && $low < $high) {
for($i = $low;$i <= $high;$i++) {
$expanded[] = $i;
}
}
}
}
return $expanded;
}
//Placeholder array
$b = array();
// Slice array (where to slice)
$s = array(11, 21);
foreach ($array as $year => $a) {
for($i = 0; $i < count($a); $i++) {
for($ii = 0; $ii < count($s); $ii++) {
if($i == 0) {
$b[$year]['<' . $s[$ii]][] = $a[$i];
break;
} else if ( isset($a[$i+1]) && $a[$i] < $s[$ii] && $a[$i+1] >=$s[$ii]){
$b[$year]['<' . $s[$ii]][] = $a[$i];
if (isset($s[$ii+1])) {
$b[$year]['<' . $s[$ii+1]][] = $a[$i+1];
} else {
$b[$year]['>' . $s[$ii]][] = $a[$i+1];
}
break;
} else if ( !isset($s[$ii+1]) && $i == count($a) - 1) {
$b[$year]['>' . $s[$ii]][] = $a[$i];
break;
}
}
}
}
$array
The list of numbers
OUTPUT ($b):
array
2000 =>
array
'<11' =>
array
0 => int 3
1 => int 7
'<21' =>
array
0 => int 11
1 => int 17
'>21' =>
array
0 => int 25
1 => int 45
2001 =>
array
'<11' =>
array
0 => int 2
1 => int 10
'<21' =>
array
0 => int 11
1 => int 13
'>21' =>
array
0 => int 34
1 => int 52
2002 =>
array
'<11' =>
array
0 => int 1
1 => int 10
'<21' =>
array
0 => int 11
1 => int 20
'>21' =>
array
0 => int 21
1 => int 52
2003 =>
array
'<11' =>
array
0 => int 1
1 => int 10
'<21' =>
array
0 => int 11
1 => int 20
'>21' =>
array
0 => int 21
1 => int 51
2004 =>
array
'<11' =>
array
0 => int 1
1 => int 10
'<21' =>
array
0 => int 11
1 => int 19
'>21' =>
array
0 => int 21
1 => int 52
2005 =>
array
'<11' =>
array
0 => int 1
1 => int 10
'<21' =>
array
0 => int 11
1 => int 20
'>21' =>
array
0 => int 21
1 => int 52
2006 =>
array
'<11' =>
array
0 => int 1
1 => int 10
'<21' =>
array
0 => int 11
1 => int 20
'>21' =>
array
0 => int 21
1 => int 52
2007 =>
array
'<11' =>
array
0 => int 1
1 => int 10
'<21' =>
array
0 => int 11
1 => int 20
'>21' =>
array
0 => int 21
1 => int 52
NOTE: Just change the values (11) and (21) to suit your needs. You can add more values.
Heres an example:
$query = "SELECT '1-11' Range, COUNT(rank) rank
FROM promoted WHERE rank between 1 and 11
union all
SELECT '12-21' Range, COUNT(rank) rank
from promoted
where rank between 12 and 21
union all
SELECT '22-31' Range, count(rank) rank
from promoted
where rank between 22 and 31
union all
SELECT '32-40' Range, count(rank) rank
from promoted
where rank between 22 and 31
union all
SELECT rank, count(rank) FROM promoted WHERE rank = '40'";

Categories