Parse formatted strings containing 3 delimiters to create multiple flat arrays

Viewed 78

I have strings in following format:

$strings[1] = cat:others;id:4,9,13
$strings[2] = id:4,9,13;cat:electric-products
$strings[3] = id:4,9,13;cat:foods;
$strings[4] = cat:drinks,foods;

where cat means category and id is identity number of a product.

I want to split these strings and convert into arrays $cats = array('others'); and $ids = array('4','9','13');

I know that it can be done by foreach and explode function through multiple steps. I think I am somewhere near, but the following code does not work.

Also, I wonder if it can be done by preg_match or preg_split in fewer steps. Or any other simpler method.

foreach ($strings as $key=>$string) {
   $temps = explode(';', $string);
   foreach($temps as $temp) {
      $tempnest = explode(':', $temp);
      $array[$tempnest[0]] .= explode(',', $tempnest[1]);
   }
}

My desired result should be:

$cats = ['others', 'electric-products', 'foods', 'drinks';

and

$ids = ['4','9','13'];
2 Answers

One option could be doing a string compare for the first item after explode for cat and id to set the values to the right array.

$strings = ["cat:others;id:4,9,13", "id:4,9,13;cat:electric-products", "id:4,9,13;cat:foods", "cat:drinks,foods"];

foreach ($strings as $key=>$string) {
    $temps = explode(';', $string);
    $cats = [];
    $ids = [];
    foreach ($temps as $temp) {
        $tempnest = explode(':', $temp);

        if ($tempnest[0] === "cat") {
            $cats = explode(',', $tempnest[1]);
        }
        if ($tempnest[0] === "id") {
            $ids = explode(',', $tempnest[1]);
        }
    }
    print_r($cats);
    print_r($ids);
}

Php demo

Output for the first item would for example look like

Array
(
    [0] => others
)
Array
(
    [0] => 4
    [1] => 9
    [2] => 13
)

If you want to aggregate all the values in 2 arrays, you can array_merge the results, and at the end get the unique values using array_unique.

$strings = ["cat:others;id:4,9,13", "id:4,9,13;cat:electric-products", "id:4,9,13;cat:foods", "cat:drinks,foods"];
$cats = [];
$ids = [];
foreach ($strings as $key=>$string) {
    $temps = explode(';', $string);

    foreach ($temps as $temp) {
        $tempnest = explode(':', $temp);

        if ($tempnest[0] === "cat") {
            $cats = array_merge(explode(',', $tempnest[1]), $cats);
        }
        if ($tempnest[0] === "id") {
            $ids = array_merge(explode(',', $tempnest[1]), $ids);
        }
    }

}
print_r(array_unique($cats));
print_r(array_unique($ids));

Output

Array
(
    [0] => drinks
    [1] => foods
    [3] => electric-products
    [4] => others
)
Array
(
    [0] => 4
    [1] => 9
    [2] => 13
)

Php demo

I don't generally recommend using variable variables, but you are looking for a sleek snippet which uses regex to avoid multiple explode() calls.

Here is a script that will use no explode() calls and no nested foreach() loops.

You can see how the \G ("continue" metacharacter) allows continuous matches relative the "bucket" label (id or cat) by calling var_export($matches);.

If this were my own code, I'd probably not create separate variables, but a single array containing id and cat --- this would alleviate the need for variable variables.

By using the encountered value as the key for the element to be added to the bucket, you are assured to have no duplicate values in any bucket -- just call array_values() if you want to re-index the bucket elements.

Code: (Demo) (Regex101)

$count = preg_match_all(
    '/(?:^|;)(id|cat):|\G(?!^),?([^,;]+)/',
    implode(';', $strings),
    $matches,
    PREG_UNMATCHED_AS_NULL
);

$cat = [];
$id = [];
for ($i = 0; $i < $count; ++$i) {
    if ($matches[1][$i] !== null) {
        $arrayName = $matches[1][$i];
    } else {
        ${$arrayName}[$matches[2][$i]] = $matches[2][$i];
    }
}
var_export(array_values($id));
echo "\n---\n";
var_export(array_values($cat));

All that said, I probably wouldn't rely on regex because it isn't very readable to the novice regex developer. The required logic is much simpler and easier to maintain with nested loops and explosions. Here is my adjustment of your code.

Code: (Demo)

$result = ['id' => [], 'cat' => []];
foreach ($strings as $string) {
   foreach (explode(';', $string) as $segment) {
      [$key, $values] = explode(':', $segment, 2);
      array_push($result[$key], ...explode(',', $values));
   }
}

var_export(array_unique($result['id']));
echo "\n---\n";
var_export(array_unique($result['cat']));

P.s. your posted coding attempt was using a combined operator .= (assignment & concatenation) instead of the more appropriate combined operator += (assignment & array union).

Related