Related
Given a string that contains values separated by dots:
property.entry.item
What is the best way to convert that to a key for an associative array?
$result['imported_data']['property']['entry']['item']
The string may be of any length, with any number of dots and contain an value:
people.arizona.phoenix.smith
I've tried the following without success:
//found a dot, means we are expecting output from a previous function
if( preg_match('[.]',$value)) {
//check for previous function output
if(!is_null($result['import'])) {
$chained_result_array = explode('.',$value);
//make sure we have an array to work with
if(is_array($chained_result_array)) {
$array_key = '';
foreach($chained_result_array as $key) {
$array_key .= '[\''.$key.'\']';
}
}
die(print_r(${result.'[\'import\']'.$array_key}));
}
}
I was thinking I could convert the string to a variable variable, but I get an array to string conversion error.
You can explode the string into an array and loop through the array. (DEMO)
/**
* This is a test array
*/
$testArray['property']['entry']['item'] = 'Hello World';
/**
* This is the path
*/
$string = 'property.entry.item';
/**
* This is the function
*/
$array = explode('.', $string);
foreach($array as $i){
if(!isset($tmp)){
$tmp = &$testArray[$i];
} else {
$tmp = $tmp[$i];
}
}
var_dump( $tmp ); // output = Hello World
Split the string into parts, and itterate the array, accessing each element in turn:
function arrayDotNotation($array, $dotString){
foreach(explode('.', $dotString) as $section){
$array = $array[$section];
}
return $array;
}
$array = ['one'=>['two'=>['three'=>'hello']]];
$string = 'one.two.three';
echo arrayDotNotation($array, $string); //outputs hello
Live example: http://codepad.viper-7.com/Vu8Hhy
You should really check to see if keys exist before you reference them. Otherwise, you're going to spew a lot of warnings.
function getProp($array, $propname) {
foreach(explode('.', $propname) as $node) {
if(isset($array[$node]))
$array = &$array[$node];
else
return null;
}
return $array;
}
Now you can do things like:
$x = array(
'name' => array(
'first' => 'Joe',
'last' => 'Bloe',
),
'age' => 27,
'employer' => array(
'current' => array(
'name' => 'Some Company',
)
)
);
assert(getProp($x, 'age') == 27);
assert(getProp($x, 'name.first') == 'Joe');
assert(getProp($x, 'employer.current.name') == 'Some Company');
assert(getProp($x, 'badthing') === NULL);
assert(getProp($x, 'address.zip') === NULL);
Or, if you are only interested in the import section of the tree:
getProp($x['import'], 'some.path');
Given a string that contains values separated by dots:
property.entry.item
What is the best way to convert that to a key for an associative array?
$result['imported_data']['property']['entry']['item']
The string may be of any length, with any number of dots and contain an value:
people.arizona.phoenix.smith
I've tried the following without success:
//found a dot, means we are expecting output from a previous function
if( preg_match('[.]',$value)) {
//check for previous function output
if(!is_null($result['import'])) {
$chained_result_array = explode('.',$value);
//make sure we have an array to work with
if(is_array($chained_result_array)) {
$array_key = '';
foreach($chained_result_array as $key) {
$array_key .= '[\''.$key.'\']';
}
}
die(print_r(${result.'[\'import\']'.$array_key}));
}
}
I was thinking I could convert the string to a variable variable, but I get an array to string conversion error.
You can explode the string into an array and loop through the array. (DEMO)
/**
* This is a test array
*/
$testArray['property']['entry']['item'] = 'Hello World';
/**
* This is the path
*/
$string = 'property.entry.item';
/**
* This is the function
*/
$array = explode('.', $string);
foreach($array as $i){
if(!isset($tmp)){
$tmp = &$testArray[$i];
} else {
$tmp = $tmp[$i];
}
}
var_dump( $tmp ); // output = Hello World
Split the string into parts, and itterate the array, accessing each element in turn:
function arrayDotNotation($array, $dotString){
foreach(explode('.', $dotString) as $section){
$array = $array[$section];
}
return $array;
}
$array = ['one'=>['two'=>['three'=>'hello']]];
$string = 'one.two.three';
echo arrayDotNotation($array, $string); //outputs hello
Live example: http://codepad.viper-7.com/Vu8Hhy
You should really check to see if keys exist before you reference them. Otherwise, you're going to spew a lot of warnings.
function getProp($array, $propname) {
foreach(explode('.', $propname) as $node) {
if(isset($array[$node]))
$array = &$array[$node];
else
return null;
}
return $array;
}
Now you can do things like:
$x = array(
'name' => array(
'first' => 'Joe',
'last' => 'Bloe',
),
'age' => 27,
'employer' => array(
'current' => array(
'name' => 'Some Company',
)
)
);
assert(getProp($x, 'age') == 27);
assert(getProp($x, 'name.first') == 'Joe');
assert(getProp($x, 'employer.current.name') == 'Some Company');
assert(getProp($x, 'badthing') === NULL);
assert(getProp($x, 'address.zip') === NULL);
Or, if you are only interested in the import section of the tree:
getProp($x['import'], 'some.path');
This question already has answers here:
Remove all elements from array that do not start with a certain string
(10 answers)
Closed last year.
If I have array like:
array [
y => 35
x => 51
z => 35
c_3 => 4
c_1 => 54
c_6 => 53
c_9 => 52
]
I want to get array of:
array [c_3=>4, c_1=>54, c_6=>53, c_9=>52]
How can I filter out the elements which do not have keys starting with c_?
Try this :
$filtred = array();
foreach($yourArray as $key => $value)
if(preg_match('/c_\d/',$key))
$filtred[] = $value;
print_r($filtred);
Try This
//your array
$arr1 = array (
"y" => 35,
"x" => 51,
"z" => 35,
"c_3" => 4,
"c_1" => 54,
"c_6" => 53,
"c_9" => 52
);
// Array with keys you want
$arr2 = array (
"c_3" => '',
"c_1" => '',
"c_6" => '',
"c_9" => ''
);
//use array_intersect_key to find the common ;)
print_r(array_intersect_key($arr1,$arr2));
Check this solution with array_filter()
$arr = [
'y' => 35,
'x' => 51,
'z' => 35,
'c_3' => 4,
'c_1' => 54,
'c_6' => 53,
'c_9' => 52,
];
$filtered = array_filter($arr, function($v) use($arr){
return preg_match('#c_\d#', array_search($v, $arr));
});
Solution below will work in PHP >= 5.6.0
$filtered = array_filter($arr, function($k){
return preg_match('#c_\d#', $k);
}, ARRAY_FILTER_USE_KEY);
Both solutions work I've checked them.
You can try using array_filter().
There are some interesting examples on the php documentation page. One of them covers filtering array keys.
I have tried the above solution but none of the solutions above worked for me in PHP 5.6
So, I tried and used different solution which worked for me...
$subArray = array();
foreach($arryDetails as $key => $value)
if(preg_match('/^c_/',$key))
$subArray[$key] = $value;
print_r($subArray);
Use array_slice: http://php.net/manual/en/function.array-slice.php if you want just select known keys. Here you have solution for filtering arrays by keys: PHP: How to use array_filter() to filter array keys?
I actually had similar question and lookout for the answers here, but nothing really matched my expectations.
I wanted something more reusable which I could use throughout all my projects, so I ended up writing this function:
/**
* Create a new a array containing only the key with a certain $pattern or $subString.
*
* Note: You cannot use both $subString and $pattern at once ($substring has precedence on $pattern).
*
* #param $errorMessage String to return the error message.
* #param $source Array in which we must extract data.
* #param $subString String/Int/Array substring (or array of substring) to seek in the key name.
* #param $pattern String regex for validating the key.
* #param $useValueAsKey Boolean if true it will use the value as the key in the new array (scalar values only).
*/
public static function extractSubArray(&$errorMessage, array &$source, $subString ="", $pattern = "", $useValueAsKey=false){
$newArray = array();
$errorMessage = "";
$dbManager = GeneralDbManager::getInstance();
if (empty($source)){
$errorMessage = $dbManager->getErrorMessage("SOURCE_ARRAY_IS_EMPTY_ERR", "The array supplied is empty.");
}
elseif(empty($subString) and empty($pattern)){
$errorMessage = $dbManager->getErrorMessage("NO_SUBSTR_OR_PATTERN_ERR", "There are no substring or pattern to match the keys in the array.");
}
elseif (!empty($subString) and !(is_string($subString) or is_numeric($subString) or is_array($subString))){
$errorMessage = $dbManager->getErrorMessage("INVALID_MATCH_SUBSTRING_ERR", "The substring you supplied to match the keys is invalid.");
}
elseif(!empty($pattern) and !RegularExpression::testRegex($pattern)){
$errorMessage = $dbManager->getErrorMessage("INVALID_MATCH_PATTERN_ERR", "The regular expression you supplied to match the keys is invalid.");
}
else{
foreach ($source as $key => $value){
if (self::isKeyMatchSubstring($key, $subString, $pattern)){
if ($useValueAsKey and (is_string($value) or is_numeric($value))){
$newArray[$value] = $value;
}
else{
$newArray[$key] = $value;
}
}
}
if (empty($newArray)){
$errorMessage = $dbManager->getErrorMessage("INVALID_MATCH_PATTERN_ERR", "We were not able to match any keys in the array with the substring or the regular expression.");
}
}
unset($dbManager);
return $newArray;
}
/**
* Validate that the $key contains the $substring (string or array) or match the $pattern.
*
* #param $key String to validate
* #param $subString String/Int/Array containing the subString to seek.
* #param $pattern String regex for validating the key.
*/
private static function isKeyMatchSubstring($key, $subString, $pattern){
$matched = false;
if (!empty($subString)){
if (is_string($subString) or is_numeric($subString)){
if(strpos(strtolower($key), strtolower($subString)) !== false){
$matched = true;
}
}
else{ //array
foreach($subString as $testString){
$matched = self::isKeyMatchSubstring($key, $testString, "");
if ($matched){
break;
}
}
}
}
elseif(!empty($pattern)){
$matched = preg_match($pattern, $key);
}
return $matched;
}
It uses a function named testRegex(), so I guess I can supply that code too:
/**
* Make sure that a regular expression works in PHP.
*
* #param $regex String the regular expression to validate.
*
* #return Boolean
*/
public static function testRegex($regex){
$isValid = false;
if (!empty($regex) and is_string($regex)){
if (#preg_match($regex, null) !== false){
$isValid = true;
}
}
return $isValid;
}
And, of course, how good would be a function without unit tests?
public function testExtractSubArray(){
$errorMessage = "";
$source = array();
//no data
$this->assertEmpty(Tool::extractSubArray($errorMessage, $source, "", "", false));
$this->assertNotEmpty($errorMessage);
//no substring or pattern
$source = array(1 => 1);
$this->assertEmpty(Tool::extractSubArray($errorMessage, $source, "", "", false));
$this->assertNotEmpty($errorMessage);
//invalid substring
$dbmanager = GeneralDbManager::getInstance();
$this->assertEmpty(Tool::extractSubArray($errorMessage, $source, $dbmanager, "", false));
$this->assertNotEmpty($errorMessage);
unset($dbmanager);
//invalid pattern
$this->assertEmpty(Tool::extractSubArray($errorMessage, $source, "", "[]123", false));
$this->assertNotEmpty($errorMessage);
//no match
$this->assertEmpty(Tool::extractSubArray($errorMessage, $source, "pokemon", "", false));
$this->assertNotEmpty($errorMessage);
//valid substring
$source = array("woot1" => "homer", "woot2" => "bart", "woot3" => "lisa", "doh1" => "marge", "doh2" => "simpson");
$newArray = Tool::extractSubArray($errorMessage, $source, "WOOT", "", false);
$this->assertNotEmpty($newArray);
$this->assertEmpty($errorMessage);
$this->assertContains("homer", $newArray);
$this->assertContains("bart", $newArray);
$this->assertContains("lisa", $newArray);
$this->assertNotContains("marge", $newArray);
$this->assertNotContains("simpson", $newArray);
//use value as key
$newArray = Tool::extractSubArray($errorMessage, $source, "WOOT", "", true);
$this->assertTrue(array_key_exists("homer", $newArray));
$this->assertTrue(array_key_exists("bart", $newArray));
$this->assertTrue(array_key_exists("lisa", $newArray));
//substring array
$source = array("stan_march" => "normal", "kyle_brofloski" => "jew", "eric_cartman" => "asshole", "kenny_mccormick" => "dead", "butters_stotch" => "pushover");
$newArray = Tool::extractSubArray($errorMessage, $source, array("stan", "kyle", "cartman"), "", false);
$this->assertNotEmpty($newArray);
$this->assertEmpty($errorMessage);
$this->assertContains("normal", $newArray);
$this->assertContains("jew", $newArray);
$this->assertContains("asshole", $newArray);
$this->assertNotContains("dead", $newArray);
$this->assertNotContains("pushover", $newArray);
//regex
$source = array("jonathan#woot.ca" => 1, "jonathan" => 2, "12345" => 3, "more $$$" => 4, "$?%$?%$%?" => 5);
$newArray = Tool::extractSubArray($errorMessage, $source, "", RegularExpression::ALPHA_NUMERIC, false);
$this->assertNotEmpty($newArray);
$this->assertEmpty($errorMessage);
$this->assertNotContains(1, $newArray);
$this->assertContains(2, $newArray);
$this->assertContains(3, $newArray);
$this->assertNotContains(4, $newArray);
$this->assertNotContains(5, $newArray);
}
Additional note: This code uses getErrorMessage() method to fetch the translation of the error message in the database. Since those error messages are meant for debugging purpose, they can be hard coded directly in the code for increased performance (no need to instantiate the class for fetching the error message and to open a connection to the database).
I am trying to replace $1, $2, $3 variables in a URL with another URL.
You can copy paste my example below and see my solution.
But I feel like there is a more elegant way with an array mapping type function or a better preg_replace type of thing. I just need a kick in the right direction, can you help?
<?php
/**
* Key = The DESIRED string
* Value = The ORIGINAL value
*
* Desired Result: project/EXAMPLE/something/OTHER
*/
$data = array(
'project/$1/details/$2' => 'newby/EXAMPLE/something/OTHER'
);
foreach($data as $desiredString => $findMe)
{
/**
* Turn these URI's into arrays
*/
$desiredString = explode('/', $desiredString);
$findMe = explode('/', $findMe);
/**
* Store the array position of the match
*/
$positions = array();
foreach($desiredString as $key => $value) {
/**
* Look for $1, $2, $3, etc..
*/
if (preg_match('#(\$\d)#', $value)) {
$positions[$key] = $value;
}
}
/**
* Loop through the positions
*/
foreach($positions as $key => $value){
$desiredString[$key] = $findMe[$key];
}
/**
* The final result
*/
echo implode('/', $desiredString);
}
Sometimes you are out of luck and the functions you need to solve a problem directly just aren't there. This happens with every language regardless of how many libraries and builtins it has.
We're going to have to write some code. We also need to solve a particular problem. Ultimately, we want our solution to the problem to be just as clean as if we had the ideal functions given to us in the first place. Therefore, whatever code we write, we want most of it to be out of the way, which probably means we want most of the code in a separate function or class. But we don't just want to just throw around arbitrary code because all of our functions and classes should be reusable.
My approach then is to extract a useful general pattern out of the solution, write that as a function, and then rewrite the original solution using that function (which will simplify it). To find that general pattern I made the problem bigger so it might be applicable to more situations.
I ended up making the function array array_multi_walk(callback $callback [, array $array1 [, array $array2 ... ]]). This function walks over each array simultaneously and uses $callback to select which element to keep.
This is what the solution looks like using this function.
$chooser = function($a, $b) {
return strlen($a) >= 2 && $a[0] == '$' && ctype_digit($a[1])
? $b : $a;
};
$data = array(
'project/$1/details/$2' => 'newby/EXAMPLE/something/OTHER'
);
$explodeSlashes = function($a) { return explode('/', $a); };
$find = array_map($explodeSlashes, array_keys($data));
$replace = array_map($explodeSlashes, array_values($data));
$solution = array_multi_walk(
function($f, $r) use ($chooser) {
return array_multi_walk($chooser, $f, $r);
},
$find, $replace);
And, as desired, array_multi_walk can be used for other problems. For example, this sums all elements.
$sum = function() {
return array_sum(func_get_args());
};
var_dump(array_multi_walk($sum, array(1,2,3), array(1,2,3), array(10)));
// prints the array (12, 4, 6)
You might want to make some tweaks to array_multi_walk. For example, it might be better if the callback takes the elements by array, rather than separate arguments. Maybe there should be option flags to stop when any array runs out of elements, instead of filling nulls.
Here is the implementation of array_multi_walk that I came up with.
function array_multi_walk($callback)
{
$arrays = array_slice(func_get_args(), 1);
$numArrays = count($arrays);
if (count($arrays) == 0) return array();
$result = array();
for ($i = 0; ; ++$i) {
$elementsAti = array();
$allNull = true;
for ($j = 0; $j < $numArrays; ++$j) {
$element = array_key_exists($i, $arrays[$j]) ? $arrays[$j][$i] : null;
$elementsAti[] = $element;
$allNull = $allNull && $element === null;
}
if ($allNull) break;
$result[] = call_user_func_array($callback, $elementsAti);
}
return $result;
}
So at the end of the day, we had to write some code, but not only is the solution to the original problem slick, we also gained a generic, reusable piece of code to help us out later.
Why there should not be $2,$4 but $1,$2 ?if you can change your array then it can be solved in 3 or 4 lines codes.
$data = array(
'project/$2/details/$4' => 'newby/EXAMPLE/something/OTHER'
);
foreach($data as $desiredString => $findMe)
{
$regexp = "#(".implode(')/(',explode('/',$findMe)).")#i";
echo preg_replace($regexp,$desiredString,$findMe);
}
I've shortened your code by removing comments for better readability. I'm using array_map and the mapping function decides what value to return:
<?php
function replaceDollarSigns($desired, $replace)
{
return preg_match('#(\$\d)#', $desired) ? $replace : $desired;
}
$data = array(
'project/$1/details/$2' => 'newby/EXAMPLE/something/OTHER',
);
foreach($data as $desiredString => $findMe)
{
$desiredString = explode('/', $desiredString);
$findMe = explode('/', $findMe);
var_dump(implode('/', array_map('replaceDollarSigns', $desiredString, $findMe)));
}
?>
Working example: http://ideone.com/qVLmn
You can also omit the function by using create_function:
<?php
$data = array(
'project/$1/details/$2' => 'newby/EXAMPLE/something/OTHER',
);
foreach($data as $desiredString => $findMe)
{
$desiredString = explode('/', $desiredString);
$findMe = explode('/', $findMe);
$result = array_map(
create_function(
'$desired, $replace',
'return preg_match(\'#(\$\d)#\', $desired) ? $replace : $desired;'
),
$desiredString,
$findMe);
var_dump(implode('/', $result));
}
?>
Working example: http://ideone.com/OC0Ak
Just saying, why don't use an array pattern/replacement in preg_replace? Something like this:
<?php
/**
* Key = The DESIRED string
* Value = The ORIGINAL value
*
* Desired Result: project/EXAMPLE/something/OTHER
*/
$data = array(
'project/$1/details/$2' => 'newby/EXAMPLE/details/OTHER'
);
$string = 'project/$1/details/$2';
$pattern[0] = '/\$1/';
$pattern[1] = '/\$2/';
$replacement[0] = 'EXAMPLE';
$replacement[1] = 'OTHER';
$result = preg_replace($pattern, $replacement, $string);
echo $result;
I think that it's much easier than what you're looking for. You can see that it works here: http://codepad.org/rCslRmgs
Perhaps there's some reason to keep the array key => value to accomplish the replace?
I'm building a platform. Somewhere in my code, there's an array that looks like this (PHP):
$entries = array('p01','p02','g01','g02','a001','a002')
I need to write a script that filters the array based on the first letter. For example, asking for those with the starting letter "p" would give me
$filtered_entries = array('p01','p02');
Similarly, if I asked for those with starting letter "g" or "a" it would give me those as well. Any idea how to accomplish this?
There is an array_filter() function in PHP which you can use to accomplish this:
$filtered = array_filter($array, create_function('$a', 'return $a[0] == "' . $letter . '";'));
I'll leave it to you to generalize the function to handle all the letters.
See: http://www.php.net/manual/en/function.array-filter.php
class FirstCharFilter {
public $char = 'p';
function filter(array $array){
return array_filter($array,array($this,'checkFirstChar'));
}
public function checkFirstChar($a){
return $a[0] == $this->char;
}
}
$filter = new FirstCharFilter();
$filter->char = 'p';
var_dump($filter->filter($array));
$filter->char = 'g';
var_dump($filter->filter($array));
Or if you only need to loop, extend FilterIterator:
class FirstCharIterator extends FilterIterator {
public $char = '';
function accept(){
$string = $this->current();
return is_string($string) && $string[0] == $this->char;
}
}
$iter = new FirstCharIterator(new ArrayIterator($array));
$iter->char = 'p';
foreach($iter as $item) echo $item."\n";
$entries = array('p01','p02','g01','g02','a001','a002');
print_r(
preg_grep('~^p~', $entries) // or preg_grep("~^$letter~",.....
);
http://php.net/manual/en/function.preg-grep.php
function filter_array($array, $letter){
$filtered_array=array();
foreach($array as $key=>$val){
if($val[0]==$letter){
$filtered_array[]=$val;
}
}
return $filtered_array;
}
use it like this to get all p's
$entries = array('p01','p02','g01','g02','a001','a002')
$filtered=filter_array($entries, 'p');
$entries = array('p01','p02','g01','g02','a001','a002');
$filterVar = null;
function filterFunction($v) {
global $filterVar;
if (substr($v,0,1) == $filterVar) {
return $v;
}
}
$filterVar = 'a';
$newEntries = array_filter($entries,'filterFunction');
var_dump($newEntries);
Here's one way of generating filter functions using a closure.
function filter_factory($letter) {
return function ($input) use ($letter) {
return is_string($input) && $input[0] === $letter;
};
}
$entries = array('p01','p02','g01','g02','a001','a002');
$p_entries = array_filter($entries, filter_factory('p'));
This type of solution is much more intuitive and dynamic.
In this example, there are several types of solutions:
Search in the first letters
Sensitive to capital letters
is_array() so if it tends to avoid several errors
<?php
/*
* Search within an asociative array
* Examples:
* $array = array('1_p01','1_P02','2_g01','2_g02','3_a001','3_a002');
* find_in_array($array,'2');
* return: array( 2 => '2_g01',3 => '2_g02')
*
* find_in_array($array,'2',false);
* return: array( 1 => '1_P02')
*
* find_in_array($array,'P0',false,false);
* return: array( 0 => '1_p01',1 => '1_P02')
*
*/
function find_in_array($array, $find='', $FirstChar=true, $CaseInsensitive=true){
if ( is_array($array) ){
return preg_grep("/".($FirstChar ? '^':'')."{$find}/".($CaseInsensitive ? '':'i'), $array);
}
}
$array = array('1_p01','1_P02','2_g01','2_g02','3_a001','3_a002');
$a = find_in_array($array,'2');
var_export($a);
/*
Return:
array (
2 => '2_g01',
3 => '2_g02'
)
*/
$a = find_in_array($array,'P0',false);
var_export($a);
/*
Return:
array (
1 => '1_P02'
)
*/
$a = find_in_array($array,'P0',false,false);
var_export($a);
/*
Return:
array (
0 => '1_p01',
1 => '1_P02'
)
*/