Search code examples
phparraysrangetext-parsing

Expand comma-delimited string of alphanumeric ranges such as "A1-3, 5B-7" to "A1, A2, A3, 5B, 6B, 7B"


If user adds a name and they want to create multiple fields, they can put numeric values in a ranged format. If they type in A1-7, it will create 7 names A1 A2 A3 A4 A5 A6 A7. Deepening on the range, it creates the title according to provided range the user can use a comma to separate multiple ranges. Use a number range with a dash in-between to create multiple units in a row. Letters can be used before or after the first number in each range.

Example: A1-3, 5B-7 will create 6 units (A1, A2, A3, 5B, 6B, 7B)

I tried using the explode function to first separate it by comma, but I am stuck on how to expand the ranges.

$units = array_map('trim', explode(',', $_POST['title']));

Solution

  • You only need to adapt Populate array of integers from a comma-separated string of numbers and hyphenated number ranges to accommodate your two range signatures. preg_replace_callback_array() is a tidy way to implement the two replacement rules. If you need to convert the hydrated/expanded string into an array, then just explode on the comma-space delimiters.

    Code: (Demo)

    $string = 'A1-3, 5B-7';
    
    echo preg_replace_callback_array(
             [
                 '/(\d+)([A-Z]+)-(\d+)/' => fn($m) => implode($m[2] . ', ', range($m[1], $m[3])) . $m[2],
                 '/([A-Z]+)(\d+)-(\d+)/' => fn($m) => $m[1] . implode(', ' . $m[1], range($m[2], $m[3])),
             ],
             $string
         );
    // A1, A2, A3, 5B, 6B, 7B
    

    A more verbose way without a preg_ function call is to attempt parsing the values and expanding the ranges with a nested loop. (Demo)

    $string = 'A1-3, A14, 5B-7';
    $result = [];
    foreach (explode(', ', $string) as $value) {
        if (sscanf($value, '%[A-Z]%d-%d', $letter, $low, $high) === 3) {
            for ($x = $low; $x <= $high; ++$x) {
                $result[] = $letter . $x;
            }
        } elseif (sscanf($value, '%d%[A-Z]-%d', $low, $letter, $high) === 3) {
            for ($x = $low; $x <= $high; ++$x) {
                $result[] = $x . $letter;
            }
        } else {
            $result[] = $value;
        }
    }
    var_export($result);
    

    Output:

    array (
      0 => 'A1',
      1 => 'A2',
      2 => 'A3',
      3 => 'A14',
      4 => '5B',
      5 => '6B',
      6 => '7B',
    )
    

    ...there will be many ways to perform this task