Parsing nested curly brace with php regex - php

I want to parse raw string with nested curly braces to multidimensional arrays. Below I added sample code which works. But main problem my regex captures only first matched group and ignores another occurences.
Any help highly appreciated.
Code:
$regex = '/(?ims)(group [a-z0-9\s\,\.\:#_\-#]+)\{([^\}]*)\}/';
preg_match_all( $regex, file_get_contents('data.txt', FILE_USE_INCLUDE_PATH), $arr);
$result = array();
foreach ($arr[0] as $i => $x)
{
$selector = trim($arr[1][$i]);
$rules = explode(';', trim($arr[2][$i]));
$result[$selector] = array();
foreach ($rules as $strRule)
{
if (!empty($strRule))
{
$rule = explode(" = ", $strRule);
$result[$selector][][trim($rule[0])] = trim($rule[1]);
}
}
}
Raw string (data.txt):
group A { T1 { X = 44; }
T2 { Y = 33; } }
group B { T1 { X = 555; } }
Code Output:
Array (
[group A] => Array (
[0] => Array (
[T1 { X] => 44
)
)
[group B] => Array (
[0] => Array (
[T1 { X] => 555
)
)
)
But excepted Output:
Array(
[group A] => Array(
[T1] => Array(
[X] => 44
)
[T2] => Array (
[Y] => 33
)
)
[group B] => Array(
[T1] => Array(
[X] => 555
)
)
)

A better solutions might exists, but it works.
Ensure your input cannot have deeper values, or you have to improve/rewrite the entiere rule !
$input = <<<REG
group A { T1 { X = 44; }
T2 { Y = 33; } }
group B { T1 { X = 555; } }
REG;
$out = [];
$cbuff = null;
$rule = "/^(?<group>[^\{\}]*)?\{|((?<set>[\w\s]+)\{(?<var>[\w\s]+)=(?<val>[\d\s]+)[;\s]+\})/im";
preg_replace_callback($rule, function($m)
use(&$out, &$cbuff) // import locally our working requirements
{
$m = array_filter($m, "is_string", ARRAY_FILTER_USE_KEY); // cleaning
$m = array_map("trim", array_filter($m)); // cleaning and trim
if(key($m)=="group") // get & create the current root group
$out[($cbuff = reset($m))] = [];
if(isset($m["var"])) // assuming 'var' exists, you'll get 'set/var' too
{
if(is_null($cbuff)) return; // prevent no group found
$out[$cbuff][$m["set"]] = [$m["var"] => $m["val"]];
}
}, $input);
var_dump($out);
Will output:
array(2) {
["group A"]=>
array(2) {
["T1"]=>
array(1) {
["X"]=>
string(2) "44"
}
["T2"]=>
array(1) {
["Y"]=>
string(2) "33"
}
}
["group B"]=>
array(1) {
["T1"]=>
array(1) {
["X"]=>
string(3) "555"
}
}
}
Hint
I think you are ok with PHP,
but i put a little bit of explaination about regexes:
first i've used "named group" for each parts 'set/var/val' pattern: ?<name>
That confer an own group key under the matching output
(?<set>...)|(?<var>...) gives --> ["set" => ...], ["var" => "..."]
Finally, to separate the sub properties to root group,
i'm just matching any part does not contains brackets
Bench
You can retrive my bench on regex101
3 matches, 60 steps (~0ms)
5 matches, 107 steps (~0ms)
yes it can be optimized!

Related

How to get hyrarchy from key elements and create new elements based on that - PHP

I have a very big array, I will try to explain the issue in small examples:
Input:
Array (
[alert:accountDisabled:heading] => XYZ
[alert:accountDisabled:message] => XYZ
[alert:accountExpired:heading] => XYZ
[alert:accountExpired:message] => XYZ
[alert:errorResponse:heading] => XYZ
[button:back] => XYZ
)
What I need to get is:
array() {
["alert"]=> array(7) {
["accountDisabled"]=> array(2) {
["heading"]=> string(3) "XYZ"
["message"]=> string(3) "XYZ" }
["accountExpired"]=> array(2) {
["heading"]=> string(3) "XYZ"
["message"]=> string(3) "XYZ" }
["clientError"]=> array(2) {
["heading"]=> string(3) "XYZ"
["message"]=> string(3) "XYZ" }
["errorResponse"]=> array(1) {
["heading"]=> string(3) "XYZ" }
}
["button"]=> array(1) {
["back"]=> string(3) "XYZ"
}
As I said this is a very small example, but the point is to get hierarchy from keys from array number one, hierarchy is divided by this character in key :
I checked for those questions that look similar to this one but they are not helpful lat all
How to access and manipulate multi-dimensional array by key names / path?
Using a string path to set nested array data
SO please read carefully the description of my issue.
I tried to use it for each loop, and I succeed to divide elements from the key, for one element, but I'm not sure where I need to store those hierarchy values for the next elements, any ideas?
$input = [
'alert:accountDisabled:heading' => 'XYZ_1',
'alert:accountDisabled:message' => 'XYZ_2',
'alert:accountExpired:heading' => 'XYZ_3',
'alert:accountExpired:message' => 'XYZ_4',
'alert:errorResponse:heading' => 'XYZ_5',
'button:back' => 'XYZ_6'
];
$results = [];
foreach ($input as $key => $value) {
$arr = explode(':', $key);
$result = $value;
for ($i = count($arr) - 1; $i >= 0; $i--) {
$result = [ $arr[$i] => $result ];
}
$results[] = $result;
}
$result = array_merge_recursive(...$results);
print_r($result);
Output:
Array
(
[alert] => Array
(
[accountDisabled] => Array
(
[heading] => XYZ_1
[message] => XYZ_2
)
[accountExpired] => Array
(
[heading] => XYZ_3
[message] => XYZ_4
)
[errorResponse] => Array
(
[heading] => XYZ_5
)
)
[button] => Array
(
[back] => XYZ_6
)
)
Based on Lukas.j answer, you can use this function:
function parsePath($array, $separator = ':'){
$result = [];
foreach($array as $key => $value){
if(strpos($key, $separator) !== FALSE){
$keys = explode($separator, $key);
$inner_result = $value;
foreach (array_reverse($keys) as $valueAsKey) $inner_result = [$valueAsKey => $inner_result];
$result[] = $inner_result;
}
}
return array_merge_recursive(...$result);
}

PHP - Splitting string lines up into two variables

Not sure how I would do this but if someone could point me in the right track that'll be great, basically I've got a lone line of text in a variable which looks like this:
Lambo 1; Trabant 2; Car 3;
Then I want to split "Lambo" to it's own variable then "1" to it's own variable, and repeat for the others. How would I go and do this?
I know about explode() but not sure how I would do it to split the variable up twice etc.
As requested in the comments my desired output would be like this:
$Item = "Lambo"
$Quantity = 1
Then echo them out and go back to top of loop for example and do the same for the Trabant and Car
<?php
$in = "Lambo 1; Trabant 2; Car 3;";
foreach (explode(";", $in) as $element) {
$element = trim($element);
if (strpos($element, " ") !== false ) {
list($car, $number) = explode(" ", $element);
echo "car: $car, number: $number";
}
}
You can use explode to split the input on each ;, loop over the results and then split over each .
You can use preg_split and iterate over the array by moving twice.
$output = preg_split("/ (;|vs) /", $input);
You could use preg_match_all for getting those parts:
$line = "Lambo 1; Trabant 2; Car 3;";
preg_match_all("/[^ ;]+/", $line, $matches);
$matches = $matches[0];
With that sample data, the $matches array will look like this:
Array ( "Lambo", "1", "Trabant", "2", "Car", "3" )
$new_data="Lambo 1;Trabant 2;Car 3;" ;
$new_array=explode(";", $new_data);
foreach ($new_array as $key ) {
# code...
$final_data=explode(" ", $key);
if(isset($final_data[0])){ echo "<pre>".$final_data[0]."</pre>";}
if(isset($final_data[1])){echo "<pre>".$final_data[1]."</pre>";}
}
This places each word and number in a new key of the array if you need to acess them seperatly.
preg_match_all("/(\w+) (\d+);/", $input_lines, $output_array);
Click preg_match_all
http://www.phpliveregex.com/p/fM8
Use a global regular expression match:
<?php
$subject = 'Lambo 1; Trabant 2; Car 3;';
$pattern = '/((\w+)\s+(\d+);\s?)+/Uu';
preg_match_all($pattern, $subject, $tokens);
var_dump($tokens);
The output you get is:
array(4) {
[0] =>
array(3) {
[0] =>
string(8) "Lambo 1;"
[1] =>
string(10) "Trabant 2;"
[2] =>
string(6) "Car 3;"
}
[1] =>
array(3) {
[0] =>
string(8) "Lambo 1;"
[1] =>
string(10) "Trabant 2;"
[2] =>
string(6) "Car 3;"
}
[2] =>
array(3) {
[0] =>
string(5) "Lambo"
[1] =>
string(7) "Trabant"
[2] =>
string(3) "Car"
}
[3] =>
array(3) {
[0] =>
string(1) "1"
[1] =>
string(1) "2"
[2] =>
string(1) "3"
}
}
In there the elements 2 and 3 hold exactly the tokens you are looking for.

how to write a function for array manipulation?

I need to create a logical relationships without inbreeding ...(animal crossing)
A B C D E
Each letter is below a family, which can vary from 2 to N (user input)
Array:
a b c d e f
Step 1 (print: )
ab bc cd de ef fa
Step 2 (print: )
abcd bcde cdef defa efab fabc
end there, because for example if you try to cross the abcd everyone else has at least one letter
SOME RULES
- The elements cannot be repeated at any stage eg: AA ou BB..CC..DD
- Elements of the groups may not be present in the next ... Example on the 3rd stage:
STRING: AB BC CD DE EA
WRONG -> AB BC
CORRECT -> AB CD
First stage, just print each element of array
Second Stage, print each element with the next, and the last with the first…
Three stage, print the 4 elements each family…
Any idea?
UPDATE
In my database i have an array:
eg:
["Fish1","FishEx3","FishExample","FishSpecie","FishOtherSpecie"]
I need use this function for parse this.
It's not a pretty solution but it works. PHP Fiddle
function Crossbreed($species = array())
{
if (empty($species)) {
return array();
}
$half_way = array();
$output = array();
$species_length = count($species) - 1;
foreach ($species as $index => $specie) {
$next = $index + 1;
if ($index >= $species_length) {
$next = $index - $species_length;
}
$half_way[] = $specie . $species[$next];
}
foreach ($half_way as $index => $specie) {
$next = $index + 2;
if ($next >= $species_length + 1) {
$next = $next - $species_length - 1;
}
$output[] = $specie . $half_way[$next];
}
return $output;
}
$species = array("Fish1","FishEx3","FishExample","FishSpecie","FishOtherSpecie");
$crossbred = Crossbreed($species);
Output of the $half_way variable (Did it with numbers in the array so its easy to read)
Array (
[0] => 12
[1] => 23
[2] => 34
[3] => 45
[4] => 56
[5] => 67
[6] => 78
[7] => 89
[8] => 91
)
Output of the function (Did it with numbers in the array so its easy to read)
Array (
[0] => 1234
[1] => 2345
[2] => 3456
[3] => 4567
[4] => 5678
[5] => 6789
[6] => 7891
[7] => 8912
[8] => 9123
)
Use this functions:
$arr = ['a', 'b', 'c', 'd', 'e', 'f'];
print_stage1($arr);
print_stage2($arr);
print_stage3($arr);
function increment($total, $pos, $inc){
if($pos + $inc < $total)
return $pos + $inc;
else
return ($pos + $inc) - $total;
}
function print_stage1($arr){
foreach($arr as $key => $family){
print $arr[$key]. " ";
}
}
function print_stage2($arr){
$total = count($arr);
foreach($arr as $key => $family){
$next_key = increment($total, $key, 1);
print $arr[$key] . $arr[$next_key]. " ";
}
}
function print_stage3($arr){
$total = count($arr);
foreach($arr as $key => $family){
$next1 = increment($total, $key, 1);
$next2 = increment($total, $key, 2);
$next3 = increment($total, $key, 3);
print $arr[$key] . $arr[$next1]. $arr[$next2]. $arr[$next3]. " ";
}
}
What you're explaining is a pretty simple unique combinatorics problem that can be solved with a stack. By shifting/popping one element at a time off the array/stack and concatenating it with another element you eventually exhaust your array. Repeat this as many times as you'd like for further combinatorics.
Here's an example.
function combine(Array $stack)
{
$newStack = [];
$lastElement = array_shift($stack);
while($nextElement = array_shift($stack)) { // shift
$newStack[] = $lastElement . $nextElement; // push
$lastElement = $nextElement;
}
return $newStack;
}
var_dump($level1 = combine(['a','b','c','d','e','f']), $level2 = combine($level1));
Result is ...
array(5) {
[0]=>
string(2) "ab"
[1]=>
string(2) "bc"
[2]=>
string(2) "cd"
[3]=>
string(2) "de"
[4]=>
string(2) "ef"
}
array(4) {
[0]=>
string(4) "abbc"
[1]=>
string(4) "bccd"
[2]=>
string(4) "cdde"
[3]=>
string(4) "deef"
}

Transform array associative recursive

We have an array with the following values:
$a = array("a", "a.b", "a.b.c", "X", "X.Y", "X.Y.Z");
And the goal is, to make modify the first array into the following structure:
$a = array(
"a" => array(
"b" => array(
"c" => array(),
),
),
"X" => array(
"Y" => array(
"Z" => array(),
),
),
);
Why i am asking? On of my customer has a table for shop categories. And these categories are in one column (simplified!):
+-----------------------+
|id | name |
+---|-------------------+
| 4 | A |
| 5 | A.B |
| 6 | A.B.C |
| 7 | X |
| 8 | X.Y |
| 9 | X.Y.Z |
+-----------------------+
How can i do this with PHP?
EDIT:
My current "solution / trys"
<?php
$arr = array(
"a",
"a.b",
"a.b.c",
"x",
"x.y",
"x.y.z",
);
$container = array();
$updateMe = array();
foreach($arr as $key => $value) {
$cleanName = explode(".", $value);
foreach($cleanName as $keyArray => $valueArray) {
for($c = 0;$c<$keyArray+1;$c++) {
$updateMe[$cleanName[$c]] = array();
}
}
$container[$cleanName[0]] = $updateMe;
unset($updateMe);
}
echo "<pre>";
var_dump($container);
echo "===\r\n";
My output:
array(2) {
["a"]=>
array(3) {
["a"]=>
array(0) {
}
["b"]=>
array(0) {
}
["c"]=>
array(0) {
}
}
["x"]=>
array(3) {
["x"]=>
array(0) {
}
["y"]=>
array(0) {
}
["z"]=>
array(0) {
}
}
}
===
SOLUTION
<?php
$arr = array(
"a",
"a.b",
"a.b.c",
"x",
"x.y",
"x.y.z",
);
$array = array();
$test = array();
foreach($arr as $key => $text) {
$array = array();
foreach(array_reverse(explode('.', $text)) as $key) $array = array($key => $array);
$test[] = $array;
}
echo "<pre>";
var_dump($test);
echo "===\r\n";
I like using references in PHP. This might not be the best solution, but it seems to work.
<?php
$arr = array(
"a",
"a.b",
"a.b.c",
"x",
"x.y",
"x.y.z",
);
$output = array();
foreach($arr as $path){
// Create a reference to the array
// As we go deeper into the path we will move this reference down
$setArray =& $output;
foreach(explode('.', $path) as $key){
// If this key does not exist, create it
if(!isset($setArray[$key])){
$setArray[$key] = array();
}
// Move the reference down one level,
// so that the next iteration will create
// the key at the right level
$setArray =& $setArray[$key];
}
}
// Destroy the reference, so that we don't accidently write to it later
unset($setArray);
var_dump($output);
You can use the accepted answer from this question, or this answer from the same question to get a good starting point (I'll use the second answer because it's shorter in this example).
$out = array();
foreach ($a as $string) {
$array = array();
foreach(array_reverse(explode('.', $string)) as $key) {
$array = array($key => $array);
}
$out[] = $array;
}
This will give you a numeric key based array, so then you can shift off the first level of the array using an answer from this question:
$out = call_user_func_array('array_merge', $out);
And your result:
Array
(
[a] => Array
(
[b] => Array
(
[c] => Array
(
)
)
)
[X] => Array
(
[Y] => Array
(
[Z] => Array
(
)
)
)
)
This should work for you:
<?php
$a = array("a", "a.b", "a.b.c", "1", "1.2", "1.2.3");
$results = array();
function stringToArray($path) {
$pos = strpos($path, ".");
if ($pos === false)
return array($path => "");
$key = substr($path, 0, $pos);
$path = substr($path, $pos + 1);
return array(
$key => stringToArray($path)
);
}
foreach($a as $k => $v) {
if(substr_count(implode(", ", $a), $v) == 1)
$results[] = stringToArray($v);
}
$results = call_user_func_array('array_merge', $results);
print_R($results);
?>
Output:
Array ( [a] => Array ( [b] => Array ( [c] => ) ) [0] => Array ( [2] => Array ( [3] => ) ) )
just tricky one
$a = array("a", "a.b", "a.b.c", "X", "X.Y", "X.Y.Z");
$res = array();
foreach ($a as $str) {
$str = str_replace('.','"]["',$str);
$tricky = '$res["'.$str.'"]';
eval ($tricky.' = array();');
};

Making values unique in multidimensional array

I have some Problems reducing a multidimensional array into a normal one.
I have an input array like this:
Array
(
[0] => Array (
[0] => 17
[1] => 99
)
[1] => Array (
[0] => 17
[1] => 121
)
[2] => Array (
[0] => 99
[1] => 77
)
[3] => Array (
[0] => 45
[1] => 51
)
[4] => Array (
[0] => 45
[1] => 131
)
So I have a multidimensional array with some overlaps in the values (eg 17,99 and 17,121)
Now I want to have an output like this:
Array
(
[0] => Array (
[0] => 17
[1] => 99
[2] => 121
[3] => 77
)
[2] => Array (
[0] => 45
[1] => 51
[3] => 131
)
I want to save, which articles are the same in my database this way. The output array shpuld still be a multidimesional array, but every number on the second level should be unique in the array.
I'm trying to solve this for more than a week now, but I dont get it to work. I know it should be easy...but anyway - I dont get it :D
This is what i got so far:
$parity_sorted = array();
foreach($arr as $key => $a){
if(count($parity_sorted) > 0){
foreach($parity_sorted as $key2 => $arr_new){
if(in_array($a[0], $arr_new) || in_array($a[1], $arr_new)){
if(!in_array($a[0], $arr_new)){array_push($parity_sorted[$key2], $a[0]);}
if(!in_array($a[1], $arr_new)){array_push($parity_sorted[$key2], $a[1]);}
} else {
array_push($parity_sorted, array($a[0],$a[1]));
}
}
} else {
array_push($parity_sorted, array($a[0],$a[1]));
}
}
Did you maybe already solve problem like this or is there a much easier way? Maybe I just think too complicated (It's not my first try, but this code was the last try)
Any help would be appreciated. Thanks a lot
Here is my revised code given your comment and a DEMO of it working as expected. ( http://codepad.org/CiukXctS )
<?php
$tmp = array();
foreach($array as $value)
{
// just for claraty, let's set the variables
$val1 = $value[0];
$val2 = $value[1];
$found = false;
foreach($tmp as &$v)
{
// check all existing tmp for one that matches
if(in_array($val1, $v) OR in_array($val2, $v))
{
// this one found a match, add and stop
$v[] = $val1;
$v[] = $val2;
// set the flag
$found = true;
break;
}
}
unset($v);
// check if this set was found
if( ! $found)
{
// this variable is new, set both
$tmp[] = array(
$val1,
$val2,
);
}
}
// go trough it all again to ensure uniqueness
$array = array();
foreach($tmp as $value)
{
$array[] = array_unique($value); // this will eliminate the duplicates from $val2
}
ORIGIN ANSWER
The question is badly asked, but I'll attempt to answer what I believe the question is.
You want to gather all the pairs of arrays that have the same first value in the pair correct?
$tmp = array();
for($array as $value)
{
// just for claraty, let's set the variables
$val1 = $value[0];
$val2 = $value[1];
if(isset($tmp[$val1])) // we already found it
{
$tmp[$val1][] = $val2; // only set the second one
}
else
{
// this variable is new, set both
$tmp[$val1] = array(
$val1,
$val2,
);
}
}
// go trough it all again to change the index to being 0-1-2-3-4....
$array = array();
foreach($tmp as $value)
{
$array[] = array_unique($value); // this will eliminate the duplicates from $val2
}
Here is solution for common task.
$data = array(array(17,99), array(17,121), array(99,77), array(45,51), array(45,131));
$result = array();
foreach ($data as $innner_array) {
$intersect_array = array();
foreach ($result as $key => $result_inner_array) {
$intersect_array = array_intersect($innner_array, $result_inner_array);
}
if (empty($intersect_array)) {
$result[] = $innner_array;
} else {
$result[$key] = array_unique(array_merge($innner_array, $result_inner_array));
}
}
var_dump($result);
Try:
$arr = array(array(17,99),
array(17,121),
array(99,77),
array(45, 51),
array(45, 131)
);
foreach($arr as $v)
foreach($v as $m)
$new_arr[] = $m;
$array = array_chunk(array_unique($new_arr), 4);
var_dump($array);
Demo
It uses array_unique and array_chunk.
Output:
array(2) { [0]=>array(4) { [0]=> int(17) [1]=>int(99)
[2]=>int(121) [3]=> int(77) }
[1]=> array(3) { [0]=> int(45) [1]=>int(51)
[2]=>int(131) }
}
I think I get your problem. Let me have a crack at it.
$firstElems = array();
$secondElems = array();
foreach ( $arr as $v ) {
$firstElems[ $v[0] ] = array( $v[0] );
}
foreach ( $arr as $v ) {
$secondElems[ $v[1] ] = $v[0];
}
foreach ( $arr as $v ) {
if ( isset( $secondElems[ $v[0] ] ) ) {
array_push( $firstElems[ $secondElems[ $v[0] ] ], $v[1] );
}
else {
array_push( $firstElems[ $v[0] ], $v[1] );
}
}
foreach ( $firstElems as $k => $v ) {
if ( isset( $secondElems[ $k ] ) ) {
unset( $firstElems[ $k ] );
}
}
Output:
Array
(
[17] => Array
(
[0] => 17
[1] => 99
[2] => 121
[3] => 77
)
[45] => Array
(
[0] => 45
[1] => 51
[2] => 131
)
)
(Code examples: http://codepad.org/rJNNq5Vd)
I truly believe I understand you and if this is the case here is what you're looking for:
function arrangeArray($array) {
$newArray = array(array_shift($array));
for ($x = 0; $x < count($newArray); $x++) {
if (!is_array($newArray[$x])) {
unset($newArray[$x]);
return $newArray;
}
for ($i = 0; $i < count($newArray[$x]); $i++) {
foreach ($array as $key => $inArray) {
if (in_array($newArray[$x][$i], $inArray)) {
$newArray[$x] = array_unique(array_merge($newArray[$x], $inArray));
unset($array[$key]);
}
}
}
$newArray[] = array_shift($array);
}
}
Which will return:
array(2) {
[0]=>
array(4) {
[0]=>
int(17)
[1]=>
int(99)
[2]=>
int(121)
[4]=>
int(77)
}
[1]=>
array(3) {
[0]=>
int(45)
[1]=>
int(51)
[3]=>
int(131)
}
}
For:
var_dump(arrangeArray(array(
array(17,99),
array(17,121),
array(99,77),
array(45, 51),
array(45, 131),
)));
And:
array(1) {
[0]=>
array(6) {
[0]=>
int(17)
[1]=>
int(99)
[2]=>
int(121)
[3]=>
int(45)
[4]=>
int(77)
[6]=>
int(51)
}
}
For:
var_dump(arrangeArray(array(
array(17,99),
array(17,121),
array(99,77),
array(45, 51),
array(45, 17),
)));

Categories