0

I am processing text with PHP, and need to find numbers in parenthesis and merge sequential numbers, such as

$raw = "outside 1,2,3,11,12,13 (5,6,11,12,13,14,16,19,20,21)";

$result = "outside 1,2,3,11,12,13 (5,6,11-14,16,19-21)";

I don't expect a full code, but have no clue what can be the strategy to do so. I guess that procedure is as

  1. Finding numbers in parenthesis (by regex).
  2. Then, checking numbers if are in sequential order.
  3. Then, replacing the matched one.

But these shouldn't be three different steps in practice.

Googlebot
  • 15,159
  • 44
  • 133
  • 229

2 Answers2

1

You can use preg_replace_callback to match the numbers in parenthesises, then use explode and array_map to turn it into integers, then sort it, and then check for range.

Function:

function fixRange($match) {
    // Extract the numbers
    $numbers = array_map('intval', explode(',', $match[1]));

    // Make sure they're sequential
    sort($numbers);

    $count = count($numbers);

    $replacements = [];

    for ($i = 0; $i < $count; $i++) {
        $startNumber = $endNumber = $numbers[$i];

        for ($j = $i + 1; $j < $count; $j++) {
            if ($numbers[$j] === ($endNumber + 1)) {
                $endNumber = $numbers[$j];

                $i = $j;
            } else {
                break;
            }
        }

        // Add range if $endNumber is not the same as $startNumber
        $replacements[] = $startNumber . ($startNumber !== $endNumber ? '-' . $endNumber : '');
    }

    return '(' . implode(',', $replacements) . ')';
}

Example:

<?php

$raw = "outside 1,2,12,13 (5,6,11,12,13,14,16,19,20,21)";

$result = preg_replace_callback('~\(([^\)]+)\)~', 'fixRange', $raw);

var_dump($result);
// string(38) "outside 1,2,12,13 (5-6,11-14,16,19-21)"

$raw = "outside 1,2,12,13 (5,6,11,12,13,14,16,19,20,21,22,23,25)";

$result = preg_replace_callback('~\(([^\)]+)\)~', 'fixRange', $raw);

var_dump($result);
// string(41) "outside 1,2,12,13 (5-6,11-14,16,19-23,25)"

DEMO

h2ooooooo
  • 39,111
  • 8
  • 68
  • 102
1

A solution that's hard to read but short (as if that was ever the right way to code (!)).

$raw = "outside 1,2,3,11,12,13 (5,6,11,12,13,14,16,19,20,21)";

// Find sections inside parentheses
$result = preg_replace_callback("[\(([^\)]+)\)]", function($in_parens){
    // Split the numbers - I'm assuming the format is guaranteed to be just digits and
    // commas.
    $numbers = explode(",", $in_parens[1]);
    return "(" . array_reduce($numbers, function($carry, $number) {
        if (preg_match("[(?:^|[-,])(\d+)$]", $carry, $prev) && $prev[1] == $number - 1) {
            // If the previous number is one less than the current number: remove
            // the previous number (if it's the end of the range) and replace it
            // with "-" followed by this number.
            return preg_replace("[-\d+$]", "", $carry) . "-$number";
        } else {
            // Otherwise just tack this number on to the previous result with a comma.
            return ($carry ? "$carry," : "") . $number;
        }
    }) . ")";
}, $raw);

var_dump($result);
//string 'outside 1,2,3,11,12,13 (5-6,11-14,16,19-21)' (length=43)

This function marks two number ranges with "-" although your example actually separates a two number range with a comma.

Matt Raines
  • 4,149
  • 8
  • 31
  • 34