How to add commas and dashes for numbers: ie 1-10 or 1,4,13

The ids of a database table have numbers. Instead of writing them on the page in length I want to shorten it. If the result is 1-10 that’s how I want it written. If the result is non-sequential such as 2, 4, 9, 14 I need to include commas. Or mixed 1, 3, 6-10.
How would the for loop work?


$id=array();
while($row = mysql_fetch_array($result)){
    $id = $row["id"];
}
for($i=0; $i<count($id); $i++){
    echo $id[$i];
}

If I would try your code I get this error:

Fatal error: Cannot redeclare reduce_ranges() (previously declared on line 111

	$blid=array();
	$bibleid0=array();
	$bookTitle0=array();
	$book0=array();
	$chapter0=array();
	$verse0=array();
	$bibleid1=array();
	$bookTitle1=array();
	$book1=array();
	$chapter1=array();
	$verse1=array();
	$comments=array();
	$txtarea=array();
	$username=array();
	$date=array();
	
while($row = mysql_fetch_array($result)){
	for($i=0; $i<count($COLORS); $i++){
		if($row["txtarea".$i]!==""){
			$txtarea[]= $row["txtarea".$i];
			//echo $row["txtarea".$i]."<br />\
";
		}
	}	
	//print_r($txtarea);
	$totalTxtarea[]= $txtarea; //array of $txtarea
	
	$blid[] = $row["blid"];
	$bibleid0[] = $row["id0"];
	$bookTitle0[] = $row["bookTitle0"];
	$book0[] = $row["book0"];
	$chapter0[] = $row["chapter0"];
	$verse0[] = $row["verse0"];
	$bibleid1[] = $row["id1"];
	$bookTitle1[] = $row["bookTitle1"];
	$book1[] = $row["book1"];
	$chapter1[] = $row["chapter1"];
	$verse1[] = $row["verse1"];
	$comments[] = $row["comments"];
	$username[] = $row["username"];
	$date[] = $row["date"];
}

for($i=0; $i<count($blid); $i++){
?>
<span style="float: left; text-align: left; font-family: Arial, Helvetica, sans-serif; font-size: 11px; display: block; margin: 5px 5px 0px 5px; width: 90&#37;; border: 1px solid #505050; padding: 2px 2px 0px 2px;">
<?php	
//print_r($totalTxtarea[$i]);


	echo "<a href=\\"JavaScript:;\\" style=\\"font-weight: bold;\\" onmouseover=\\"this.title='";
	//echo count($txtarea);
	for($j=0; $j<count($totalTxtarea[$i]); $j++){
		echo $totalTxtarea[$i][$j];
		//echo $txtarea[$j];
		if($j<count($totalTxtarea[$i])-1){
			echo " + ";
		}
	}
	echo "';\\" onmouseout=\\"this.title='';\\" title=\\"\\">".$bookTitle0[$i]." ".$chapter0[$i].":";
	
		$t=explode("+", $verse0[$i]);
		//print_r($t);
		/*******************************************************************************************************/
function reduce_ranges($reduced, $value){
    static $in_range, $previous;
    // Reset
    if ($in_range === NULL) {
        $in_range = FALSE;
        $previous = NULL;
    }
    // In a range
    if (isset($previous) && $value == $previous + 1) {
        if (!$in_range) {
            $in_range = TRUE;
        }
        // Update the range
        end($reduced);
        $key = key($reduced);
        $start = strtok($reduced[$key], "-");
        $reduced[$key] = $start."-".$value;
    // Not in a range
    } else {
        $reduced[] = $value;
        $in_range = FALSE;
    }
    $previous = $value;
    return $reduced;
}

// Sample array of numbers
$data = array_merge($t);

// sort the array in ascending order
sort($data, SORT_NUMERIC);
$ranges = array_reduce($data, "reduce_ranges");
echo implode(",", $ranges) . PHP_EOL; 

So I took the function out of the while loop but here’s the result:
http://www.gbgrafix.com/thewheelofgod/twotexts/getfiles/biblelinks.php?no=0&book0=1&chapter0=1&verse0=all

I used this but the firefox browser crashed:

while($row = mysql_fetch_array($result)){
	for($i=0; $i<count($COLORS); $i++){
		if($row["txtarea".$i]!==""){
			$txtarea[]= $row["txtarea".$i];
			//echo $row["txtarea".$i]."<br />\
";
		}
	}	
	//print_r($txtarea);
	$totalTxtarea[]= $txtarea; //array of $txtarea
	
	$blid[] = $row["blid"];
	$bibleid0[] = $row["id0"];
	$bookTitle0[] = $row["bookTitle0"];
	$book0[] = $row["book0"];
	$chapter0[] = $row["chapter0"];
	$verse0[] = (int)$row["verse0"];
	$bibleid1[] = $row["id1"];
	$bookTitle1[] = $row["bookTitle1"];
	$book1[] = $row["book1"];
	$chapter1[] = $row["chapter1"];
	$verse1[] = (int)$row["verse1"];
	$comments[] = $row["comments"];
	$username[] = $row["username"];
	$date[] = $row["date"];
}

for($i=0; $i<count($blid); $i++){
?>
<span style="float: left; text-align: left; font-family: Arial, Helvetica, sans-serif; font-size: 11px; display: block; margin: 5px 5px 0px 5px; width: 90%; border: 1px solid #505050; padding: 2px 2px 0px 2px;">
<?php	
//print_r($totalTxtarea[$i]);


	echo "<a href=\\"JavaScript:;\\" style=\\"font-weight: bold;\\" onmouseover=\\"this.title='";
	//echo count($txtarea);
	for($j=0; $j<count($totalTxtarea[$i]); $j++){
		echo $totalTxtarea[$i][$j];
		//echo $txtarea[$j];
		if($j<count($totalTxtarea[$i])-1){
			echo " + ";
		}
	}
	echo "';\\" onmouseout=\\"this.title='';\\" title=\\"\\">".$bookTitle0[$i]." ".$chapter0[$i].":";
	
		$t=explode("+", $verse0[$i]);
		//print_r($t);
		/*******************************************************************************************************/
//$id=array();
 
 
//while($row = mysql_fetch_array($result)){
//    $id[] = (int)$row["id"];
//}
 
sort($t); //sort the array in numerical order
$prev_verse=0; //the previous id
$prev_was_sequential=false; //was the previous id in sequential order?
$numlist=""; //the list of numbers to print out
 
for($i=0;$i<count($t);$i++){
    if($i>0){ //if this is not the first id in the array
        if($prev_verse+1==$t[$i]){
            $prev_was_sequential=true; //the previous number was in sequence
            if($i+1==count($t)){
                $numlist.="-".$t[$i]; //if this is the last id and there was a sequence up to the last id
            }
        }
        elseif($prev_was_sequential===true){ //if the current number was not in sequence, but the previous one was
            $numlist.="-".$prev_verse.", ".$t[$i];
            $prev_was_sequential=false;
        }
        else{ //the number was not in sequence
            $numlist.=", ".$t[$i];
            $prev_was_sequential=false;
        }
 
    }
    else{ //if this is the first id in the array
        $prev_id=$t[$i];
        $numlist.=$t[$i];
    }
   
    $prev_verse=$t[$i];
 
}
 
echo $numlist;

There’s probably a way to refactor this since it seems a bit like spegetti code, but it seems to work:

$id=array();


while($row = mysql_fetch_array($result)){
    $id[] = (int)$row["id"];
}

sort($id); //sort the array in numerical order
$prev_id=0; //the previous id
$prev_was_sequential=false; //was the previous id in sequential order?
$numlist=""; //the list of numbers to print out

for($i=0;$i<count($id);$i++){
	if($i>0){ //if this is not the first id in the array
		if($prev_id+1==$id[$i]){
			$prev_was_sequential=true; //the previous number was in sequence
			if($i+1==count($id)){
				$numlist.="-".$id[$i]; //if this is the last id and there was a sequence up to the last id
			}
		}
		elseif($prev_was_sequential===true){ //if the current number was not in sequence, but the previous one was
			$numlist.="-".$prev_id.", ".$id[$i];
			$prev_was_sequential=false;
		}
		else{ //the number was not in sequence
			$numlist.=", ".$id[$i];
			$prev_was_sequential=false;
		}

	}
	else{ //if this is the first id in the array
		$prev_id=$id[$i];
		$numlist.=$id[$i];
	}
	
	$prev_id=$id[$i];

}

echo $numlist;

Another way


$t = array(1, 2, 3, 4, 10, 15, 19, 20, 30, 31, 32, 33);

$s = '';
$c = count($t);
for($i=0; $i<$c; $i++) {
	$start = $t[$i];
	$end = '';
	if($i+2 < $c && $start+1 == $t[$i+1] && $start+2 == $t[$i+2]) {
		$i+=2;
		$end = $t[$i];
		while(++$i < $c) {	
			if($end+1 == $t[$i]) $end = $t[$i];
			else break;
		}
	}
	$s .= $end ? $start.'-'.$end.', ' : $start.', ';
}

var_dump($s);
// string(24) "1-4, 15, 19, 20, 30-33, " 

Looks short and sweet. But How do you remove the quotes “” and string(). Also If there’s only 1 number in the array then there shouldn’t be any commas following that.

To only have the appropriate number of commas, first push the values on to an array, and then join them together with commas by using the implode command.

You also won’t need to test $end, because you can join together $start and $end with the dash. The dash will only appear if both values exist, for which you can use array_filter to remove values that are equivalent to false.

The steps are:

[list=1][]put $start and $end into their own array
[
]filter the array with array_filter
[]join the contents of the array with ‘-’
[
]add the joined result to a separate array for the result
[*]afterwards, join the results with ', '[/list]


$s = array();
for (...) {
    ...
    $values = array($start, $end);
    $s[] = implode('-', array_filter($values));
}
echo implode(', ', $s); // 1-4, 15, 19, 20, 30-33

The quotes aren’t there in the output. hash was just showing sample output using var_dump(). In your own code, feel free to output as you see fit.

Here’s another approach, inspired by hash’s, which strips off the extra space and comma:

<?php

$data = array_merge(
  array(1, 2, 17, 3, 4, 5, 25),
  range(100,105)
);

echo printnums($data), "\
";

function printnums(array $data)
{
  $out = '';
  for ($i = 0, $j = sizeof($data); $i < $j; ++$i) {
    $s = $e = $data[$i];
    while ($i < $j-1 && $e+1 == $data[$i+1]) {
      $e = $data[++$i];
    }
    $out .= $e-$s > 1 ? "$s-$e, " : "$s, "
      . ($e-$s === 1 ? "$e, " : '');
  }
  return rtrim($out, ', ');
}

This implementation requires a range greater than two before using the “[m,n]” notation.

Another approach would be to use array_reduce to tease the data into another form.

Here’s an example which groups the ranges together and returns an array in the style array(1,“3-5”,9) which may or may not be useful for the OP. Alternatively, the output string (comma-separated, or whatever you like) could be done directly in the reducing function but I think keeping an array is more flexible.

Reducing Function


function reduce_ranges($reduced, $value) {
    static $in_range, $previous;
    // Reset
    if ($in_range === NULL) {
        $in_range = FALSE;
        $previous = NULL;
    }
    // In a range
    if (isset($previous) && $value == $previous + 1) {
        if (!$in_range) {
            $in_range = TRUE;
        }
        // Update the range
        end($reduced);
        $key = key($reduced);
        $start = strtok($reduced[$key], "-");
        $reduced[$key] = $start."-".$value;
    // Not in a range
    } else {
        $reduced[] = $value;
        $in_range = FALSE;
    }
    $previous = $value;
    return $reduced;
}

Sample Use


// Sample array of numbers
$data = array_merge( 
  array(1, 2, 17, 3, 4, 5, 25), 
  range(100,105) 
);

// sort the array in ascending order
sort($data, SORT_NUMERIC);
$ranges = array_reduce($data, "reduce_ranges");
echo implode(",", $ranges) . PHP_EOL;

Outputs

1-5,17,25,100-105

If this is for a pagination component, something like this would work.


$min = 1;
$max = 100;
$skip = (max(1,ceil(($max-$min) / 10)); # Show 10 links
for ($i = $min; $i <= $max; $i = $i + $skip) {
  echo "\
" . $i;
}

Note, pseudo-code.

you can use this code
$id=array();
while($row = mysql_fetch_array($result)){
$id = $row[“id”];
}
for($i=0; $i<count($id); $i++){
echo $id[$i];
}