I'm trying to convert some python code into PHP and I have some difficulties to get the same output.
Actually, I think the problem is about working with a dictionary by reference in Python, more difficult in PHP
Here is my Python code :
import json
from pprint import pprint
a=["/",
"/page1.html",
"/cocktails/receipe/page1.html",
"/cocktails/receipe/page2.html",
"/cocktails/page3.html",
"/article/magazine",
"/article/mood/page1.html"]
def create(path,dictionaryandarray):
#print(path[0],dictionary)
if not path:
return
for ele in dictionaryandarray:
if 'name' in ele and ele['name'] == path[0]:
ele.setdefault('children',[])
if (path[1:]):
create(path[1:],ele['children'])
return
newvalue={'name':path[0]}
if (path[1:]):
newvalue['children']=[]
dictionaryandarray.append(newvalue)
if (path[1:]):
create(path[1:], dictionaryandarray[-1]['children'])
d = []
for i in a:
parts = [j for j in i.split('/') if j != '']
create(parts ,d)
data={'name':'/','children':d}
data=json.dumps(data, indent=4, sort_keys=False)
# pprint(data)
print(data)
and here is my PHP code :
<?php
$rows=[
"page1.html",
"/cocktails/receipe/page1.html",
"/cocktails/receipe/page2.html",
"/cocktails/page3.html",
"/article/magazine",
"/article/mood/page1.html"];
$res = [];
$i=0;
foreach($rows as $row){
$suffix = preg_replace("#https?://[^/]*#", "", $row);
$parts = array_values(array_filter(preg_split("#[/\?]#", $suffix)));
create($parts, $res);
}
$data=['name' => '/','children' =>$res];
$data= json_encode($data, true);
header('Content-Type: application/json');
echo $data;
function create($path, &$res){
if (empty($path))
return;
if (is_null($res))
return;
foreach ($res as $key => $ele){
if (array_key_exists("name", $ele) && $ele['name'] == $path[0]){
if (!array_key_exists("children", $ele)){
$res[$key]['children'] = [];
}
if (count($path) > 1){
create(array_slice($path, 1), $res[$key]['children']);
}
return;
}
}
$newvalue = ["name" => $path[0]];
if (count($path)>1){
$newvalue['children'] = [];
}
$res[] = $newvalue;
if (count($path)> 1){
create(array_slice($path, 1), end($res)['children']);
}
}
what I'm getting is that the $res variable is not populated as python do. I tryed to pass it by reference but get the same problem.
May be there is a way to populate it the same way than Python do, but I don't know how to do.
Any help is appreciated, thanks
PHP's foreach is implemented as call-by-value instead of call-by-reference. So your $ele['children'] assignments are doing nothing. Also, mix doing return with pass-by-reference doesn't help.
You may convert your create() to full call-by-reference style like this:
<?php
function create($path, &$res){
if (empty($path))
return;
if (is_null($res))
return;
foreach ($res as $key => $ele){
if (array_key_exists("name", $ele) && $ele['name'] == $path[0]){
if (!array_key_exists("children", $ele)){
$res[$key]['children'] = [];
}
if (count($path) > 1){
create(array_slice($path, 1), $res[$key]['children']);
}
return;
}
}
$newvalue = ["name" => $path[0]];
if (count($path)>1){
$newvalue['children'] = [];
}
$res[] = $newvalue;
if (count($path)> 1){
create(array_slice($path, 1), end($res)['children']);
}
}
Then you should simply rewrite $res = create($parts, $res); to create($parts, $res);. Things should work.
Related
I can easily write to and read from a sub-array in the session array.
$_SESSION['a']['b']['c']['value']=123;
$val=$_SESSION['a']['b']['c']['value'];
Instead of hard coding the "location" where the value is written, I would like it to be definable via a string or some other way. The following will obviously not work, but hopefully will better explain the intent.
$prefix="['a']['b']['c']"; //defined in config page, etc
$_SESSION.$prefix.['value']=123;
$val=$_SESSION.$prefix.['value'];
How can this be accomplished?
PropertyAccess
There is an excellent Symfony component for such tasks, named PropertyAccess. You can use it as follows:
$persons = array('a' => array('b' => 5.7));
$accessor = PropertyAccess::createPropertyAccessor();
echo $accessor->getValue($persons, '[a][b]'); // 5.7
You can install it using Composer as described in docs or fetch directly from GitHub.
Custom solution
This is a complete solution, I'm really impressed that it works... but it works! Check the code below, assert()'s demonstrate the usage:
<?php
function arrayPropertyPathGet(array $arr, $path) {
$parts = explode('.', $path);
$ret = $arr;
foreach($parts as $part) {
$ret = $ret[$part];
}
return $ret;
}
function arrayPropertyPathSet(array &$arr, $path, $value) {
$parts = explode('.', $path);
$tmp = &$arr;
foreach($parts as $part) {
if(!isset($tmp[$part])) { return false; }
$tmp = &$tmp[$part];
}
$tmp = $value;
return true;
}
$test = array('a' => array('b' => 'value'));
assert('value' === arrayPropertyPathGet($test, 'a.b'));
assert(true === arrayPropertyPathSet($test, 'a.b', 'other'));
assert('other' === arrayPropertyPathGet($test, 'a.b'));
Side note
As a theoretical side note (do not use this for anything other than learning purposes) you can experiment with eval(), such as:
eval("$value = $persons['a']['b']");
I faced the same problem a few times ago, and as I didn't find any solution, I made one by myself, if that can help you in anyway (only the interesting part) :
class ArrayAccessor {
private $prefix;
function setPrefix() {
$this->prefix = array();
for ($i = 0; $i < func_num_args(); ++$i) {
$this->prefix[] = func_get_arg($i);
}
}
function getFromPrefix(array $array) {
$tmp_array = $array;
foreach ($this->prefix as $pre) {
if (isset ($tmp_array[$pre])) {
$tmp_array = $tmp_array[$pre];
} else {
return null;
}
}
return $tmp_array;
}
}
$Access = new ArrayAccessor();
$Access->setPrefix('Hi', 'Toto');
$MyTestArray['Hi']['Toto'] = 'Works';
var_dump ($Access->getFromPrefix($MyTestArray));
$Access->setPrefix('Hi');
var_dump ($Access->getFromPrefix($MyTestArray));
$Access->setPrefix('No');
var_dump ($Access->getFromPrefix($MyTestArray));
Result :
string(5) "Works"
array(1) {
["Toto"]=>
string(5) "Works"
}
NULL
I have an array which contains bunch of strings, and I would like to find all of the possible combinations no matter how it's being sorted that match with given string/word.
$dictionary = ['flow', 'stack', 'stackover', 'over', 'code'];
input: stackoverflow
output:
#1 -> ['stack', 'over', 'flow']
#2 -> ['stackover', 'flow']
What I've tried is, I need to exclude the array's element which doesn't contain in an input string, then tried to match every single merged element with it but I'm not sure and get stuck with this. Can anyone help me to figure the way out of this? thank you in advance, here are my code so far
<?php
$dict = ['flow', 'stack', 'stackover', 'over', 'code'];
$word = 'stackoverflow';
$dictHas = [];
foreach ($dict as $w) {
if (strpos($word, $w) !== false) {
$dictHas[] = $w;
}
}
$result = [];
foreach ($dictHas as $el) {
foreach ($dictHas as $wo) {
$merge = $el . $wo;
if ($merge == $word) {
} elseif ((strpos($word, $merge) !== false) {
}
}
}
print_r($result);
For problems like this you want to use backtracking
function splitString($string, $dict)
{
$result = [];
//if the string is already empty return empty array
if (empty($string)) {
return $result;
}
foreach ($dict as $idx => $term) {
if (strpos($string, $term) === 0) {
//if the term is at the start of string
//get the rest of string
$substr = substr($string, strlen($term));
//if all of string has been processed return only current term
if (empty($substr)) {
return [[$term]];
}
//get the dictionary without used term
$subDict = $dict;
unset($subDict[$idx]);
//get results of splitting the rest of string
$sub = splitString($substr, $subDict);
//merge them with current term
if (!empty($sub)) {
foreach ($sub as $subResult) {
$result[] = array_merge([$term], $subResult);
}
}
}
}
return $result;
}
$input = "stackoverflow";
$dict = ['flow', 'stack', 'stackover', 'over', 'code'];
$output = splitString($input, $dict);
I have following url:
www.example.com/index.php/search/search_data/Doctor:a/Gender:Male/Language:Urdu/
and I want to convert it to associative array like
$data=array(
'Doctor'=> 'a',
'Gender'=> 'Male',
'Language'=> 'Urdu'
);
I have tried to do this using codeIgniter's URI class function
$this->uri->uri_to_assoc(n)
but as it accepts the data to be separated via '/' but I am having data with ':' as separator.
please help me.
I don't think there's an easier way to do this, rather than to do it manually.
First, retrieve the total segments, loop through, see if it contains ":", then add it into the array.
$segments = $this->uri->segment_array();
$search_array = array();
foreach($segments as $segment) {
if (strpos($segment, ":") !== FALSE) {
$e_array = explode(":", $segment);
$search_array[$e_array[0]] = $e_array[1];
}
}
Running that snippet somewhere will give you desirable results, $search_array will be an associative array with key => value.
You could hack the URI.php file. Change lines 431 - 439 to;
if (strpos($seg, ":") !== FALSE) {
list($parameter, $value) = explode(':', $seg);
if ($i % 2) {
$retval[$parameter] = $value;
} else {
$retval[$parameter] = $value;
$lastval = $seg;
}
} else {
if ($i % 2) {
$retval[$lastval] = $seg;
} else {
$retval[$seg] = FALSE;
$lastval = $seg;
}
}
Is it possible in PHP to extract values from an array with a particular key path and return an array of those values? I'll explain with an example:
$user =
array (
array(
'id' => 1,
'email' =>'asd#example.com',
'project' => array ('project_id' => 222, 'project_name' => 'design')
),
array(
'id' => 2,
'email' =>'asd2#example.com',
'project' => array ('project_id' => 333, 'project_name' => 'design')
)
);
/** I have to write a function something like: */
$projectIds = extractValuesWithKey($user, array('project', 'project_id'));
print_r($projectIds);
Output:
Array(
[0] => 222,
[1] => 333
)
I would have gone for a different approach (not that there's anything wrong with the array-function-based answers) by using a recursive iterator to flatten the array which makes the key-path comparison fairly simple.
function extractValuesWithKey($array, $keys) {
$iterator = new RecursiveIteratorIterator(new RecursiveArrayIterator($array));
$keys_count = count($keys);
// No point going deeper than we have to!
$iterator->setMaxDepth($keys_count);
$result = array();
foreach ($iterator as $value) {
// Skip any level that can never match our keys
if ($iterator->getDepth() !== $keys_count) {
continue;
}
// Build key path to current item for comparison
$key_path = array();
for ($depth = 1; $depth <= $keys_count; $depth++) {
$key_path[] = $iterator->getSubIterator($depth)->key();
}
// If key paths match, add to results
if ($key_path === $keys) {
$result[] = $value;
}
}
return $result;
}
To make the whole thing more useful, you could even wrap the code into a custom FilterIterator rather than a basic function, but I guess that's probably a different question entirely.
Well, that's easier than you think.
function extractValuesWithKey($array, $parts) {
$return = array();
$rawParts = $parts;
foreach ($array as $value) {
$tmp = $value;
$found = true;
foreach ($parts as $key) {
if (!is_array($tmp) || !isset($tmp[$key])) {
$found = false;
continue;
} else {
$tmp = $tmp[$key];
}
}
if ($found) {
$return[] = $tmp;
}
}
return $return;
}
If the 'key path' isn't dynamic, you can do a one-liner with array_map:
$projectIds = array_map(function($arr) { return $arr['project']['project_id']; }, $user);
Alternatively, for dynamic paths:
function extractValuesWithKey($users, $path) {
return array_map(function($array) use ($path) {
array_walk($path, function($key) use (&$array) { $array = $array[$key]; });
return $array;
}, $users);
}
The closures/anonymous functions only work with PHP 5.3+, and I've no idea how this would compare performance-wise to a double foreach loop. Note also that there's no error checking to ensure that the path exists.
I also used a similiar function in one of my projects, maybe you find this useful:
function extractValuesWithKey($data, $path) {
if(!count($path)) return false;
$currentPathKey = $path[0];
if(isset($data[$currentPathKey])) {
$value = $data[$currentPathKey];
return is_array($value) ? extractValuesWithKey($value, array_slice($path, 1)) : $value;
}
else {
$tmp = array();
foreach($data as $key => $value) {
if(is_array($value)) $tmp[] = extractValuesWithKey($value, $path);
}
return $tmp;
}
}
Can i parse a plist file with php and kind of get it into an array, like the $_POST[''] so i could call $_POST['body'] and get the string that has the <key> body ?
CFPropertyList - A PHP Implementation Of Apple's plist (PropertyList)
Googling for "php plist parser" turned up this blog post that seems to be able to do what you are asking for.
Took a look at some of the libraries out there but they have external requirements and seem overkill. Here's a function that simply puts the data in to associative arrays. This worked on a couple of exported itunes plist files I tried.
// pass in the full plist file contents
function parse_plist($plist) {
$result = false;
$depth = [];
$key = false;
$lines = explode("\n", $plist);
foreach ($lines as $line) {
$line = trim($line);
if ($line) {
if ($line == '<dict>') {
if ($result) {
if ($key) {
// adding a new dictionary, the line above this one should've had the key
$depth[count($depth) - 1][$key] = [];
$depth[] =& $depth[count($depth) - 1][$key];
$key = false;
} else {
// adding a dictionary to an array
$depth[] = [];
}
} else {
// starting the first dictionary which doesn't have a key
$result = [];
$depth[] =& $result;
}
} else if ($line == '</dict>' || $line == '</array>') {
array_pop($depth);
} else if ($line == '<array>') {
$depth[] = [];
} else if (preg_match('/^\<key\>(.+)\<\/key\>\<.+\>(.+)\<\/.+\>$/', $line, $matches)) {
// <key>Major Version</key><integer>1</integer>
$depth[count($depth) - 1][$matches[1]] = $matches[2];
} else if (preg_match('/^\<key\>(.+)\<\/key\>\<(true|false)\/\>$/', $line, $matches)) {
// <key>Show Content Ratings</key><true/>
$depth[count($depth) - 1][$matches[1]] = ($matches[2] == 'true' ? 1 : 0);
} else if (preg_match('/^\<key\>(.+)\<\/key\>$/', $line, $matches)) {
// <key>1917</key>
$key = $matches[1];
}
}
}
return $result;
}