Group values of array according to regex in PHP - php

I have an array that looks like this:
[0] => name {
[1] => abc
[2] => def
[3] => }
[4] =>
[5] => othername {
[6] => 123
[7] => 456
[8] => 789
[9] => }
[10] =>
As you can see each group (under each name) can have different amount of lines and different items in them but each group starts with the same regex syntax and ends with a closing } and a blank line after that.
I need to get an array for each group (for each name) recursively. I made a preg_match regex that will find each name line but I don't know how to make an array with that having also all the lines that are before the next name group.
So I want to obtain:
array(
array('name {', 'abc', 'def', '}'),
array('othername {', '123', '456', '789', '}')
)
How can I approach this? Thanks in advance.

Clicquot beat me but I'm not sure his works/is tested. Here's my solution
<?php
$array = array('0' => 'name {', '1' => 'abc', '2' => 'def', '3' => "}\n", '4' => "\n",
'5' => 'othername {',
'6' => '123',
'7' => '456',
'8' => '789',
'9' => '}',
'10' => "\n");
$string = array();
$count = 0;
foreach($array as $value){
$value = trim($value);
$pos = strpos($value, '{');
if ($pos !== false) {
$count++;
}
if(!empty($value)) {
$string[$count][] = $value;
}
}
print_r($string);

Maybe this code is what you asked for. But I'd never use it myself. If you try to build a code parser you shuold look for parsing-trees with a "grammar" or syntax analysis. It is part of parsing and compilers. The slide bellow shows how a compiler or interpreter can work. If you are only aiming to represent your data in order to reach some specific goal, maybe you can look at formats like json. You should always mention in one sentence what your overall goal is prior to asking a specific question.
(sorry for the German)
The issues of the code below are, that syntax variations can break the code, and you are highly voulnerable because the code changes your php variable contents concerning to the contents of your array.
If you are processing a file with code I do recommend you also to process it streight away, instead of first saving it to an array (which costs more memory).
<?php
$syntaxArray = array(
0 => 'name {',
1 => 'abc',
2 => 'def',
3 => '}',
4 => 'othername {',
5 => '123',
6 => '456',
7 => '789',
8 => '}'
);
$regex_open_array = '-(\w+)\s*{-';
$array_open = false;
foreach($syntaxArray as $line){
$matches = array();
if( preg_match($regex_open_array, $line, $matches) ){
if($array_open){
/*recursive array creations do not work - if that's what you need put it in the comments*/
throw new RuntimeException('An array can not be open while the previous array is not closed');
}
$array_open = true;
/*create an array variable
* ....
* isolate the variable name
*/
$currentArray = $matches[1];
/*you can either create a new variable in your php code with the array name or use a big array and use the name as key
* $big_array[$name] = array();
* using this solution every array name must be only used once.
* of course you can use a name twice if you first check prior to creating a new array if the key is already in use.
*/
/*creates a variable with the given name. Must not overwrite an other arrays name like $syntaxArray*/
$$currentArray = array();
}else if($line == '}'){
/*the closing string could be wrapped with trim() or matched with a regex but I followed your example*/
$array_open = false;
}else{
if(!$array_open){
throw new RuntimeException('There is not array currently open');
}
$arr = &$$currentArray;
$arr[] = $line;
}
}
var_dump($name);
var_dump($othername);
Outputs:
array(2) { [0]=> string(3) "abc" [1]=> string(3) "def" } array(3) { [0]=> string(3) "123" [1]=> string(3) "456" [2]=> string(3) "789" }

This isn't really a regex thing. What you want to do is iterate through the array, and check each item. You'd search through the value there for a '{' or a '}'. When you see an opening bracket, you'd set the key in your "condensed" array that you want to index into, and then check subsequent items for a closing bracket. When you see the closing bracket, you'd change the key that you're indexing into in your "condensed" array or whatever and begin anew. When the value isn't a bracket, append it. It'll look something like this (sorry for the bad PHP):
$big_array = array();
$temp_key = NULL;
foreach (array as $key => $value) {
if (strpo($value, '{')) {
$big_array[$key] = array();
$temp_key = $key;
} else if (strpos($value, '}')) {
$temp_key = NULL;
} else {
$big_array[$temp_key].append($value); // or whatever
}
}
This assumes that you having a matching open / close bracket structure , you'd want to add in a ton of error checking of course.

Related

Simplify PHP array with same items

I have this PHP array:
$this->user_list = array( 0 => 'Not paid',1 => 'Not paid', 2 => 'Not paid', 7 => 'Waiting, 15 => 'Waiting', 10 => 'Cancelled' );
How can I simplify this array as the id numbers are different, but some of them have same status?
I tried it like this:
$this->user_list = array( [0,1,2 => 'Not paid'],[7,15 => 'Waiting'],10 => 'Cancelled' );
but it doesn't work as expected.
Basically I want to achieve this:
echo $this->user_list[15] should give me Waiting, echo $this->user_list[10] should give me Cancelled, etc. So this is working in my first array very well, I am just thinking about grouping duplicate names there.
As mentioned by other contributors, there is no native support in the PHP grammar for your intended use case. As clearly stated in the PHP: Arrays documentation:
An array can be created using the array() language construct. It takes any number of comma-separated key => value pairs as arguments.
So basically each element in an array is a key => value pair, which means you cannot associate multiple keys to a single element.
This also explains why your first tentative didn't work:
$this->user_list = array( [0,1,2 => 'Not paid'],[7,15 => 'Waiting'],10 => 'Cancelled' );
If you don't specify a key for an element, PHP uses a progressive index (0, 1, ...). So basically in the example above, the first zero is not actually a key, but a value, and PHP binds it to the key = 0. Maybe it could be easier for you to understand how it works if you print a var_dump or print_r of $this->user_list. You would get something similar to the following structure (NOTE: I have simplified the structure to make it more clear):
[
0 => [
0 => 0
1 => 1
2 => "Not paid"
],
1 => [
0 => 7,
15 => "Waiting"
],
10 => "Cancelled"
]
So how do we resolve this problem? Well... actually there is no need to contort the structure by swapping keys with values as other contributors seem to suggest. Changing the structure might simplify your "data entry" work but might also create big issues in other parts of the program because who knows, maybe accessing the invoice data by "ID" is simply more efficient than by "status" ... or something.
Since PHP does not provide such a feature out of the box, I believe a better solution would be to develop our own function; a good starting point could be the one in the example below.
function explode_array($config, $sep = ',') {
$res = [];
foreach($config as $configKey => $value) {
// split key values
$keys = explode($sep, $configKey);
foreach($keys as $key) {
$res[$key] = $value;
}
}
return $res;
}
$config = [
'0,1,2' => 'Not paid',
'7,15' => 'Waiting',
'10' => 'Cancelled'
];
$myArr = explode_array($config);
print_r($myArr);
The idea is quite simple: since we cannot use an array as key we leverage the next best data type, that is a CSV string. Please note there is no error handling in the above code, so the first thing you may want to do is adding some validation code to the explode_array (or however you wish to name it) function.
you should use like this. if id number is invoice id or something else and other value is there status about it.
$arr = array(
'Not paid' => [0,1,2] ,
'Waiting' => [5,6],
'Cancelled' =>[8]
);
foreach($arr as $key => $val){
foreach($val as $keys => $vals){
echo "invoiceid ".$vals ." status ".$key;
echo"<br>";
}
}
// for only one status you can use like this
foreach($arr['Not paid'] as $key => $val){
echo $val;
echo"<br>";
}
just try to run this and check output.
PHP has no built-in function or structure for handling cases like this. I'd use a simple array value-cloning function to map your duplicates. Simply have one instance of each status, then map the aliases, and then run a function that clones them in. As follows:
// Status list:
$ulist = [ 0 => 'Not paid', 7 => 'Waiting', 10 => 'Cancelled' ];
// Alternative IDs list, mapped to above source IDs:
$aliases = [ 0 => [1,2], 7 => [15] ];
// Function to clone array values:
function clone_values(array &$arr, array $aliases)
{
foreach($aliases as $src => $tgts) {
foreach($tgts as $tgt) {
$arr[$tgt] = $arr[$src];
}
}
ksort($arr); // If the order matters
}
// Let's clone:
clone_values($ulist, $aliases);
This results in the following array:
array(6) {
[0] · string(8) "Not paid"
[1] · string(8) "Not paid"
[2] · string(8) "Not paid"
[7] · string(7) "Waiting"
[10] · string(9) "Cancelled"
[15] · string(7) "Waiting"
}
....which can be accessed as you expect, here $ulist[2] => Not paid, etc. If the use case is as simple as illustrated in the OP, I'd personally just spell it out as is. There's no dramatic complexity to it. However, if you have dozens of aliases, mapping and cloning begins to make sense.
As said in the comments, you can't have multiple keys with one value. The best way is to use the keyword => [ number, number, number...] construction.
//set a result array
$result = [];
//loop the original array
foreach ( $this->user_list as $number => $keyword ){
//if the keyword doesn't exist in the result, create one
if(!isset ( $result [ $keyword ] ) ) $result[ $keyword ] = [];
//add the number to the keyword-array
$result[ $keyword ] [] = $number;
}

Multi level array assignment

Here is the example PHP array representation
array(
"test1" => array(
"test1subtest1" => array(..)
),
"test2" => array(
"test2subtest1" => array(..)
)
)
So, here is the question: is there any tool in PHP which can be used to assign values to multidimensional array with random depth and index names? It suppose to look like this:
$valuetoassing = "TESTVALUE";
$testarray = array();
array_assign_multidimensional($testarray, $valuetoassing, array("test1", "test1subtest1", "test1subtest1subtest1"));
Problem is that I do not know what depth the array will have, so I can not code it. Index names are also generated at the run time.
EDIT: I figured that my particular case can be solved using some kind of linked list (stored as array with items that contain actual data and pointer to the index of the next element), but I'm sure I'll meet this problem again in the future so I will not close the question right now.
This is pretty easy to do using references.
function array_assign_multidimensional(&$input_array, $value, $list){
$assignee = &$input_array;
foreach($list as $key){
$assignee = &$assignee[$key];
}
$assignee = $value;
}
$input = array(
'firstLayer' => array(
'secondLayer' => array(
'test' => 'old'
)
),
'randomOutlyingValue' => ''
);
array_assign_multidimensional($input, 'new', array('firstLayer', 'secondLayer', 'test'));
var_dump($input);
/*
array(2) {
["firstLayer"]=>
array(1) {
["secondLayer"]=>
array(1) {
["test"]=>
string(3) "new"
}
}
["randomOutlyingValue"]=>
string(0) ""
}
*/

Intersection of two multidimensional arrays in PHP

I am having the following array defined:
array(
'name'=>'Blue',
'age'=>'0',
'skin'=>array(
'White Skin','Tanned Skin'
),
'eye'=>array(
'Black','Brown','Honey'
),
'personality'=>array(
'Intelligent','Warm','Trustworthy','Sweet'
),
'ocassion'=>array(
'Every day wear','Celebrations','Restaurant Dinner','Feasts','Visiting friends'
),
'hair'=>'All Colors',
'style'=>array(
'Loved to be admired','Center of attention'
),
'description'=>'Blue lens are perfect for any..'
);
and I am trying to find the number of matches, from an HTML form into this array. A possible return from the HTML form, in array format would be:
Array
(
[age] => 16
[skin] => Tanned Skin
[eye] => Brown
[personality] => Array
(
[0] => Intelligent
[1] => Warm
[2] => Trustworthy
)
[ocassion] => Weddings
[hair] => Dark Brown
[style] => Array
(
[0] => Style Queen
[1] => Testing val
)
)
I have tried iterating trough each key of the first array, but failed to achieve what I want, and also I've tried using the function array_intersect_assoc($stack,$search) but it seems it won't find the exact matches because the $search array ( second example ) has some key=>value pairs that are of type string, and it cannot match any occurrence into the first array because the value is actually an array, and not a string.
Can someone point me an idea or can let me know what's best to do over here?
I have tried a lot of things in the last 3 hours but no success.
Ok, so how about this.
The source data:
$demands = array(
'name'=>'Blue',
'age'=>'0',
'skin'=>array(
'White Skin','Tanned Skin'
),
'eye'=>array(
'Black','Brown','Honey'
),
'personality'=>array(
'Intelligent','Warm','Trustworthy','Sweet'
),
'ocassion'=>array(
'Every day wear','Celebrations','Restaurant Dinner','Feasts','Visiting friends'
),
'hair'=>'All Colors',
'style'=>array(
'Loved to be admired','Center of attention'
),
'description'=>'Blue lens are perfect for any..'
);
$possible_match = array(
'age'=>'16',
'skin'=>'Tanned Skin',
'eye'=>'Brown',
'personality'=>array(
'Intelligent','Warm','Trustworthy'
),
'ocassion'=>array(
'Weddings'
),
'hair'=>'Dark Brown',
'style'=>array(
'Style Queen','Testing value'
)
);
And the match-making algorithm:
$result = array();
$count_matches = 0;
// Go through all the demands
foreach ($demands as $key => $value){
// If there's a matching key in the possible match array
if (isset($possible_match[$key])){
// If there are more demanded values
if (is_array($value)){
// Let all demanded values be lowercase
$value = array_map('strtolower', $value);
// If there are more possible matching values
if (is_array($possible_match[$key])){
// Let all possibly matching values be lowercase, too
$possible_match[$key] = array_map('strtolower', $possible_match[$key]);
// And then do the intersect.
$intersect = array_intersect($value, $possible_match[$key]);
if ($intersect){
// If that intersect is not empty, add that to the resulting array
$result[$key] = $intersect;
$count_matches += count($intersect);
};
} else {
// If there's only one possible matching value, search that
// value in the demaned array
if (in_array(strtolower($possible_match[$key]), $value, true)){
// And add it to the results
$result[$key][] = strtolower($possible_match[$key]);
$count_matches++;
}
}
} else {
if (is_array($possible_match[$key])){
// If there are more possible matching values but the demand is a string,
// find that string in those possible values
$possible_match[$key] = array_map('strtolower', $possible_match[$key]);
if (in_array(strtolower($value), $possible_match[$key], true)){
// And add it to the results
$result[$key] = $value;
$count_matches++;
}
} else {
// If the demanded value is only one (= it's a string and not an array)
// and the possible match is also a string, do a lowercase compare
// + if there's a word "all" in the demanded value, pass it at all times ;D
if (strtolower($possible_match[$key]) == strtolower($value)
|| stripos($value, "all") !== false){
// And add it to the resulting array
$result[$key] = strtolower($value);
$count_matches++;
}
}
}
}
}
var_dump ($result);
var_dump ($count_matches);
There may be some opportunities for optimizing, but the basic idea should be there :)
The result:
array (size=4)
'skin' =>
array (size=1)
0 => string 'tanned skin' (length=11)
'eye' =>
array (size=1)
0 => string 'brown' (length=5)
'personality' =>
array (size=3)
0 => string 'intelligent' (length=11)
1 => string 'warm' (length=4)
2 => string 'trustworthy' (length=11)
'hair' => string 'all colors' (length=10)
Plus the count, if you'd like:
int 6

PHP Array needs fixed (consolidated/merged)

I have this multidimensional array which I'll name "original":
$original=
array
0 =>
array
'animal' => 'cats'
'quantity' => 1
1 =>
array
'animal' => 'dogs'
'quantity' => '1'
2 =>
array
'animal' => 'cats'
'quantity' => '3'
However, I want to merge internal arrays with the same animal to produce this new array (with quantities combined):
$new=
array
0 =>
array
'animal' => 'cats'
'quantity' => 4
1 =>
array
'animal' => 'dogs'
'quantity' => '1'
I understand that there are similar questions on stackoverflow, but not similar enough for me to be able to figure out how to use the feedback those questions have gotted to apply to this specific example. Yes, I know I probably look stupid to a lot of you, but please remember that there was a time when you too didn't know crap about working with arrays :)
I've tried the following code, but get Fatal error: Unsupported operand types (Referring to line 11). And if I got that error to go away, I'm not sure if this code would even produce what I'm trying to achieve.
$new = array();
foreach($original as $entity){
if(!isset($new[$entity["animal"]])){
$new[$entity["animal"]] = array(
"animal" => $entity["animal"],
"quantity" => 0,
);
}
$new[$entity["animal"]] += $entity["quantity"];
}
So, I don't know what I'm doing and I could really use some help from the experts.
To try to give a super clear question, here goes... What changes do I need to make to the code so that it will take $original and turn it into $new? If the code I provided is totally wrong, could you provide an alternative example that would do the trick? Also, the only language I am familiar with is PHP, so please provide an example using only PHP.
Thank you
You're very close.
$new[$entity["animal"]] += $entity["quantity"];
needs to be
$new[$entity["animal"]]['quantity'] += $entity["quantity"];
In your if ( !isset [...] ) line, you're setting $new[$entity['animal']] to an array, so you need to access the 'quantity' field of that array before trying to add the new quantity value to it.
One of the reasons why your code is not working is that you're using the animal name as the array index, not the integer index which is used in your desired output.
Try this:
$new = array(); // Desired output
$map = array(); // Map animal names to index in $new
$idx = 0; // What is the next index we can use
foreach ($original as $entity) {
$animal = $entity['animal'];
// If we haven't saved the animal yet, put it in the $map and $new array
if(!isset($map[$animal])) {
$map[$animal] = $idx++;
$new[$map[$animal]] = $entity;
}
else {
$new[$map[$animal]]['quantity'] += $entity['quantity'];
}
}
This works:
$new = array();
$seen = array();
foreach($original as $entity) {
// If this is the first time we're encountering the animal
if (!in_array($entity['animal'], $seen)) {
$new[] = $entity;
$seen[] = $entity['animal'];
// Otherwise, if this animal is already in the new array...
} else {
// Find the index of the animal in the new array...
foreach($new as $index => $new_entity) {
if ($new_entity['animal'] == $entity['animal']) {
// Add to the quantity
$new[$index]['quantity'] += $entity['quantity'];
}
}
}
}
Your example was using the animal name as the index, yet the actual index is just an integer.
However, I think the resulting array would be easier to use and easier to read if it was formatting like this instead:
array('cats' => 4, 'dogs' => 1)
That would require different but simpler code than above... but, it wouldn't be a direct response to your question.

php store array in array by reference

I have the following problem. There are some php lines.
In the var_dump of $testArrayA, the "def" entry with test2 is NOT there because it was added
after $testArrayB was added to $testArrayA.
It seems to me that in my case, that $testArrayB is not stored by reference in $testArrayA. How can I store it per reference, what do I have to make to have "def" entry in the var_dump?
Thanks alot in advance
$testArrayA = [];
$testArrayB = [];
$testArrayB["ghi"] = "test1";
$testArrayA["abc"] = $testArrayB;
$testArrayB["def"] = "test2";
The var_dump:
array(1) {
["abc"]=>
array(1) {
["ghi"]=>
string(5) "test1"
}
}
This is simply a matter of passing by reference:
$testArrayA = [];
$testArrayB = [];
$testArrayB["ghi"] = "test1";
$testArrayA["abc"] = &$testArrayB;
$testArrayB["def"] = "test2";
var_dump($testArrayA);
array (size=1)
'abc' => &
array (size=2)
'ghi' => string 'test1' (length=5)
'def' => string 'test2' (length=5)
Use:
$testArrayA["abc"] = &$testArrayB;
Note:
Different with C's pointer, references in PHP are a means to access the same variable content by different names.
in the php manual
Array assignment always involves value copying. Use the reference operator to copy an array by reference.
$testArrayA = null;
$testArrayB = null;
$testArrayB["ghi"] = "test1";
$testArrayA["abc"] = $testArrayB;
$testArrayB["def"] = "test2";
print_r($testArrayA);
echo ("<br>");
print_r($testArrayB);
Array ( [abc] => Array ( [ghi] => test1 ) )
Array ( [ghi] => test1 [def] => test2 )
The 'def' entry is a different reference with the 'ghi',
but they all belong testArrayB.
$testArrayA["abc"] = $testArrayB;
This code only is value reference ,not address reference.

Categories