I'm not sure if my memory is wrong, but when I last used PHP (years ago), I vaguely remember doing something like this:
$firstVariable, $secondVariable = explode(' ', 'Foo Bar');
Note that the above is incorrect syntax, however in this example it would assign 'Foo' to $firstVariable, and 'Bar' to $secondVariable.
What is the correct syntax for this?
Thanks.
list($firstVar, $secondVar) = explode(' ', 'Foo Bar');
list() is what you are after.
From php7.1, you can do Symmetric array destructuring.
https://www.php.net/manual/en/migration71.new-features.php#migration71.new-features.symmetric-array-destructuring
Code: (Demo)
$array = [1, 2, 3];
[$a, $b, $c] = $array;
echo "$a $b $c";
// displays: 1 2 3
Without calling list().
This is the new modern approach when "swapping" element positions without declaring a temporary variable. See examples here and here and here and here.
For in-depth breakdown and examples, have a long look at this post: https://sebastiandedeyne.com/the-list-function-and-practical-uses-of-array-destructuring-in-php/
As for using explode() with list() or array destructuring, if you are not guaranteed a certain number of elements,
it is best practice to declare the 3rd parameter of explode() to limit the number of generated elements. This will not force the production of so many elements; rather it will merely tell php to stop exploding when that number of elements is achieved.
[$firstVariable, $secondVariable] = explode(' ', $stringToBeHalved, 2);
If you aren't 100% assured that your exploded data will provide balanced data to the other side of the assignment operator, you can implement a maximum with the technique above and use something akin to array_replace() to provide a minimum number of elements on the right side of the assignment operator.
Code: (Demo)
$strings = [
"1,2,3,4",
"1,2,3",
"1,2"
];
$minElements = 3;
$defElements = array_fill(0, $minElements, null);
foreach ($strings as $string) {
[$a, $b, $c] = array_replace($defElements, explode(',', $string, $minElements));
var_export($a);
echo ' _ ';
var_export($b);
echo ' _ ';
var_export($c);
echo "\n-----------------\n";
}
Output:
'1' _ '2' _ '3,4'
-----------------
'1' _ '2' _ '3'
-----------------
'1' _ '2' _ NULL
-----------------
In fact, symmetric array destructuring even permits accessing the same element/value more than once -- which may feel a little awkward at first glance.
For example, you can push a solitary value into two arrays like this:
[0 => $array1[], 0 => $array2[]] = ['zeroIndexedElement'];
Read more at: While destructuring an array, can the same element value be accessed more than once?
And one more technique that is very seldom advised is to call extract(). This function can be dangerous if you don't have complete control of the data that it is processing because it will push all of the data's keys into the global scope as variable names -- potentially overwriting variables and leading to script vulnerability or breakage.
To prepare the data for extraction, convert the indexed array into an associative array by assigning keys.
Code: (Demo)
$threeKeys = ['a', 'b', 'c'];
foreach ($strings as $string) {
extract(array_combine($threeKeys, explode(',', $string, 3)));
// now you can use $a, $b, and $c
}
The above demo shows some of the Warnings generated when not providing balanced/expected volumes of data. So the moral of this story is to be careful and eliminate possible fringe cases by ensuring the data processors will always receive what they require.
First a few examples with list() alone, then 2 examples with list() combined with explode().
The examples on the PHP manual page for list() are especially illuminating:
Basically, your list can be as long as you want, but it is an absolute list. In other words the order of the items in the array obviously matters, and to skip things, you have to leave the corresponding spots empty in your list().
Finally, you can't list string.
<?php
$info = array('coffee', 'brown', 'caffeine');
// Listing all the variables
list($drink, $color, $power) = $info;
echo "$drink is $color and $power makes it special.\n";
// Listing some of them
list($drink, , $power) = $info;
echo "$drink has $power.\n";
// Or let's skip to only the third one
list( , , $power) = $info;
echo "I need $power!\n";
// list() doesn't work with strings
list($bar) = "abcde";
var_dump($bar); // NULL
?>
A few cases by example:
Applying list(), explode() and Arrays to dysfunctional relationships:
<?php
// What they say.
list($firstVar, $secondVar, , , $thirdVar) = explode(' ', 'I love to hate you');
// What you hear.
// Disaplays: I love you
echo "$firstVar $secondVar $thirdVar";
?>
Finally, you can use list() in conjunction with arrays. $VARIABLE[] stores an item into the last slot in an array. The order things are stored should be noted, since it's probably the reverse of what you expect:
<?php
list(, $Var[], ,$Var[] , $Var[]) = explode(' ', 'I love to hate you');
// Displays:
// Array ( [0] => you [1] => hate [2] => love )
print_r($Var);
?>
The explanation of why the order things are stored in is as it is is given in the warning on the list() manual page:
list() assigns the values starting with the right-most parameter. If you are
using plain variables, you don't have to worry about this. But if you are
using arrays with indices you usually expect the order of the indices in
the array the same you wrote in the list() from left to right; which it isn't.
It's assigned in the reverse order.
Related
This is fairly confusing, but I'll try to explain as best I can...
I've got a MYSQL table full of strings like this:
{3}12{2}3{5}52
{3}7{2}44
{3}15{2}2{4}132{5}52{6}22
{3}15{2}3{4}168{5}52
Each string is a combination of product options and option values. The numbers inside the { } are the option, for example {3} = Color. The number immediately following each { } number is that option's value, for example 12 = Blue. I've already got the PHP code that knows how to parse these strings and deliver the information correctly, with one exception: For reasons that are probably too convoluted to get into here, the order of the options needs to be 3,4,2,5,6. (To try to modify the rest of the system to accept the current order would be too monumental a task.) It's fine if a particular combination doesn't have all five options, for instance "{3}7{2}44" delivers the expected result. The problem is just with combinations that include option 2 AND option 4-- their order needs to be switched so that any combination that includes both options 2 and 4, the {4} and its corresponding value comes before the {2} and it's corresponding value.
I've tried bringing the column into Excel and using Text to Columns, splitting them up by the "{" and "}" characters and re-ordering the columns, but since not every string yields the same number of columns, the order gets messed up in other ways (like option 5 coming before option 2).
I've also experimented with using PHP to explode each string into an array (which I thought I could then re-sort) using "}" as the delimiter, but I had no luck with that either because then the numbers blend together in other ways that make them unusable.
TL;DR: I have a bunch of strings like the ones quoted above. In every string that contains both a "{2}" and a "{4}", the placement of both of those values needs to be switched, so that the {4} and the number that follows it comes before the {2} and the number that follows it. In other words:
{3}15{2}3{4}168{5}52
needs to become
{3}15{4}168{2}3{5}52
The closest I've been able to come to a solution, in pseudocode, would be something like:
for each string,
if "{4}" is present in this string AND "{2}" is present in this string,
take the "{4}" and every digit that follows it UNTIL you hit another "{" and store that substring as a variable, then remove it from the string.
then, insert that substring back into the string, at a position starting immediately before the "{2}".
I hope that makes some kind of sense...
Is there any way with PHP, Excel, Notepad++, regular expressions, etc., that I can do this? Any help would be insanely appreciated.
EDITED TO ADD: After several people posted solutions, which I tried, I realized that it would be crucial to mention that my host is running PHP 5.2.17, which doesn't seem to allow for usort with custom sorting. If I could upvote everyone's solution (all of which I tried in PHP Sandbox and all of which worked), I would, but my rep is too low.
How would something like this work for you. The first 9 lines just transform your string into an array with each element being an array of the option number and value. The Order establishes an order for the items to appear in and the last does a usort utilizing the order array for positions.
$str = "{3}15{2}2{4}132{5}52{6}22";
$matches = array();
preg_match_all('/\{([0-9]+)\}([0-9]+)/', $str, $matches);
array_shift($matches);
$options = array();
for($x = 0; $x < count($matches[0]); $x++){
$options[] = array($matches[0][$x], $matches[1][$x]);
}
$order = [3,4,2,5,6];
usort($options, function($a, $b) use ($order) {
return array_search($a[0], $order) - array_search($b[0], $order);
});
To get you data back into the required format you would just
$str = "";
foreach($options as $opt){
$str.="{".$opt[0]."}".$opt[1];
}
On of the bonuses here is that when you add a new options type inserting adjusting the order is just a matter of inserting the option number in the correct position of the $order array.
First of all, those options should probably be in a separate table. You're breaking all kinds of normalization rules stuffing those things into a string like that.
But if you really want to parse that out in php, split the string into a key=>value array with something like this:
$options = [];
$pairs = explode('{', $option_string);
foreach($pairs as $pair) {
list($key,$value) = explode('}', $pair);
$options[$key] = $value;
}
I think this will give you:
$options[3]=15;
$options[2]=3;
$options[4]=168;
$options[5]=52;
Another option would be to use some sort of existing serialization (either serialize() or json_encode() in php) instead of rolling your own:
$options_string = json_encode($options);
// store $options_string in db
then
// get $options_string from db
$options = json_decode($options_string);
Here's a neat solution:
$order = array(3, 4, 2, 5, 6);
$string = '{3}15{2}3{4}168{5}52';
$split = preg_split('#\b(?={)#', $string);
usort($split, function($a, $b) use ($order) {
$a = array_search(preg_replace('#^{(\d+)}\d+$#', '$1', $a), $order);
$b = array_search(preg_replace('#^{(\d+)}\d+$#', '$1', $b), $order);
return $a - $b;
});
$split = implode('', $split);
var_dump($split);
Suppose a string:
$str = 'a_b_c';
I want match all possible combination with a, b, c with above. For example:
b_a_c, c_a_b, a_c_b..etc will be give true when compare with above $str.
NOTE:
$str may be random. eg: a_b, k_l_m_n etc
I would split your string into an array, and then compare it to an array of elements to match on.
$originalList = explode('_', 'a_b_c');
$matchList = array('a', 'b', 'c');
$diff = array_diff($matchList, $originalList);
if (!empty($diff)) {
// At least one of the elements in $matchList is not in $originalList
}
Beware of duplicate elements and what not, depending on how your data comes in.
Documentation:
array_diff()
explode()
There is no builtin way to quickly do this. Your task can be accomplished many different ways which will vary on how general they are. You make no mention of null values or checking the formatting of the string, so something like this might work for your purpose:
function all_combos($str,$vals) {
$s=explode("_",$str);
foreach($s as $c) {
if(!in_array($s,$vals)) return false;
}
return true;
}
Call like all_combos("b_c_a",array("a","b","c"));
base on my understanding, this how list() work.
list($A1,$A2,$A3) = array($B1,B2,B3);
So with the help of list() we can assign value out from array accordingly.
here is my question... how to generate a dynamic list()?
1). base on database return result, I'm not sure how many of it but I assign it all into array
2). so we can use count(array) to know how many of it.
3). so then HOW CAN I GENERATE/PREPARE a list for it?
Example: client A, have 3 kids, name Apple, Boy, Cat
so I use list($kid1, $kid2, $kid3) for it.
but when client B, have more then 3 kids, I only get first 3
or if client C, have only 1 kids, then error encounter.
I know if base on the situation above, there is many way to solve it without using list()
but I wish to know or find out the solution with using list().
How to generate dynamic list() base on count of array()
thanks guys/gals
If you have a variable number of elements, use arrays for them! It does not make sense to extract them into individual variables if you do not know how many variables you'll be dealing with. Say you did extract those values into variables $kid1 through $kidN, what is the code following this going to do? You have no idea how many variables there are in the scope now, and you have no practical method of finding out or iterating them next to testing whether $kid1 through $kidN are isset or not. That's insane use of variables. Just use arrays.
Having said that, variable variables:
$i = 1;
foreach ($array as $value) {
$varname = 'kid' . $i++;
$$varname = $value;
}
You can create a lambda expression with create_function() for this. The list() will be only accessible within the expression.
This creates variables $A1, $A2, .... $AN for each element in your array:
$list = array("a", "b", "c", "d");
extract(array_combine(array_map(function($i) {
return "A" . $i;
}, range(1, count($list))), $list));
echo implode(" ", array($A1, $A2, $A3, $A4)), PHP_EOL;
You can modify the name of the variables in the array_map callback. I hope I'll never see code like that in production ;)
This is not what PHP's list is meant for. From the official PHP docs
list is not really a function, but a language construct.
list() is used to assign a list of variables in one operation.
In other words, the compiler does not actually invoke a function but directly compiles your code into allocations for variables and assignment.
You can specifically skip to a given element, by setting commas as follows:
list($var1, , $var2) = Array($B1, B2, B3);
echo "$var1 is before $var2 \n";
or take the third element
list( , , $var3) = Array($B1, B2, B3);
(I am assuming B2, B3 are constants? Or are you missing a $?)
Specifically using list, you can use PHP's variable variables to create variables from an arbitrary one-dimensional array as follows:
$arr = array("arrindex0" => "apple", "banana", "pear");
reset($arr);
while (list($key, $val) = each($arr)) {
$key = is_numeric($key) ? "someprefix_" . $key : $key;
echo "key sdf: $key <br />\n";
$$key = $val;
}
var_dump($arrindex0, $someprefix_0, $someprefix_1);
Result
string 'apple' (length=5)
string 'banana' (length=6)
string 'pear' (length=4)
This must be a gotcha of some sort, but I just can’t see it. I’ve taken it out of my application and tested it in a separate script, and it performs the same way. Here’s the extracted code, but it has the same problem independently.
<?php
$actions[1] = '14-pictures-of-trees';
var_dump($actions[1]); // 1
$url = explode('-', $actions[1], 2);
var_dump($url); // 2
list($id, $url) = $url;
var_dump($id); // 3
var_dump($url); // 4
Here’s the results:
string '14-pictures-of-trees' (length=20)
array
0 => string '14' (length=2)
1 => string 'pictures-of-trees' (length=17)
string 'p' (length=1)
string 'pictures-of-trees' (length=17)
I’m expecting 3 to return string '14' but it doesn’t!
list assigns its arguments starting with the last and ending with the first. This is fully explained in the manual, specifically two warnings:
Warning
list() assigns the values starting with the right-most parameter. If you are using plain variables, you don't have to worry about this. But if you are using arrays with indices you usually expect the order of the indices in the array the same you wrote in the list() from left to right; which it isn't. It's assigned in the reverse order.
Warning
Modification of the array during list() execution (e.g. using list($a, $b) = $b) results in undefined behavior.
Therefore, your expression is basically like this:
$url = $url[1]; // $url is now "pictures-of-trees"
$id = $url[0];
Accessing a string as if were an array of characters, $id is now the first character of $url. Hence p.
Instead, use a different variable name for $url or assign them directly:
list($id, $url) = explode('-', $actions[1], 2);
What's happened is the variable $url is modified and overwritten with the second element of the previous array $url before $id is assigned. This is because list() loads variables from right to left, not left to right.
When you access $url[0] once it has been converted to a string in the list() call, you get the first character, p, and that is what becomes stored in $id.
See the notes on the list() docs for info on the right to left assignments...
list overwrites the $url variable before creating $id.
From the manual:
Modification of the array during list() execution (e.g. using list($a,
$b) = $b) results in undefined behavior.
Using a different variable on the left hand side solves the problem:
list($id, $urx) = $url;
This is because you're re-assigning the variable $url while it's being used by the list() construct. If you change the variable name, you'll get the result that you're expecting. Keep in mind that list() assigns the values in a right-to-left fashion!
<?php
$actions[1] = '14-pictures-of-trees';
$url = explode('-', $actions[1], 2);
var_dump($url); // 2
list($id, $url2) = $url;
var_dump($id); // 3
var_dump($url2); // 4
?>
The problem lies with the statement
list($id, $url) = $url;
You are modifying the array while you process it.
I have a function (for ease, I'll just use count()) that I want to apply to maybe 4-5 different variables. Right now, I am doing this:
$a = count($a);
$b = count($b);
$c = count($c);
$d = count($d);
Is there a better way? I know arrays can use the array_map function, but I want the values to remain as separate values, instead of values inside of an array.
Thanks.
I know you said you don't want the values to be in an array, but how about just creating an array specifically for looping through the values? i.e.:
$arr = Array($a, $b, $c, $d);
foreach ($arr as &$var)
{
$var = count($var);
}
I'm not sure if that really is much tidier than the original way, though.
If you have a bunch of repeating variables to collect data your code is poorly designed and should just be using an array to store the values, instead of dozens of variables. So perhaps you want something like:
$totals = array("Visa"=>0,"Mastercard"=>0,"Discover"=>0,"AmericanExpress"=>0);
then you simply add to your array element (say from a while loop from your SQL or whatever you are doing)
$totals['Visa'] += $row['total'];
But if you really want to go down this route, you could use the tools given to you, if you want to do this with a large batch then an array is a good choice. Then foreach the array and use variable variables, like so:
$variables = array('a','b','c'...);
foreach ( $variables as $var )
{
${$var} = count(${var});
}
What Ben and TravisO said, but use array_walk for a little cleaner code:
$arr = Array($a, $b, $c, $d);
array_walk($arr, count);
You can use extract to get the values back out again.
//test data
$a = range(1, rand(4,9));
$b = range(1, rand(4,9));
$c = range(1, rand(4,9));
//the code
$arr = array('a' => $a, 'b' => $b, 'c' => $c);
$arr = array_map('count', $arr);
extract($arr);
//whats the count now then?
echo "a is $a, b is $b and c is $c.\n";
How do you measure "better"? You might be able to come up with something clever and shorter, but what you have seems like it's easiest to understand, which is job 1. I'd leave it as it is, but assign to new variables (e.g. $sum_a, ...).