Dynamically edit dict tree in Python - php

I have a piece of PHP code that I am trying to port over to Python that I am unsure how to get working without references.
Essentially it's a config class that works like a tree, each key can have a simple value, or it's own set of keys and values. Part of the class requires being able to set one specific part of the tree without having to send an entire new dict for a root key.
{ "caching": { "enabled": true }}
For example, the above could be a simple configuration. And calling the below code would change true to false
Config.set('caching:enabled', false);
In order to accomplish this in PHP I use references
class Config
{
private static $aValues;
public static function set($key, $value)
{
if(strpos($key, ':')) {
$aKeys = explode(':', $key);
$iCount = count($aKeys);
} else {
$aKeys = array($key);
$iCount = 1
}
$mData = &self::$aValues
for($i = 0; $i < $iCount; ++$i)
{
if(!isset($mData[$aKeys[$i])) {
$mData[$aKeys[$i]] = array();
}
$mData = &$mData[$aKeys[$i]];
if($i == ($iCount - 1)) {
$mData = $value;
}
}
}
}
But if I try to do something similar in Python
_dmValues = dict()
def set(key, value):
global _dmValues
if key.find(':'):
aKey = key.split(':')
iCount = len(key)
else:
aKey = (key,)
iCount = 1
mData = _dmValues;
for i in range(0, iCount):
if key[i] not in mData.keys():
mData[key[i]] = dict()
mData = mData[key[i]]
if i == (iCount - 1):
mData = value
It doesn't work, mData is the right value, but since I have written to it, it is no longer a reference.
How can I go about doing this? Is it even possible in Python, or should I just re-write my logic from scratch and give up on a perfect port?

You can make your set method as follows:
_dmValues = { "caching": { "enabled": True }}
def set(key, value):
global _dmValues
key1,key2 = key.split(':')
mData = _dmValues;
if key1 in mData:
if key2 in mData[key1]:
mData[key1][key2] = value
set('caching:enabled', False)
print(_dmValues) # {'caching': {'enabled': False}}
Though probably it would be better to remove the global value and pass reference to the dict as an argument:
def set(mData, key, value):
key1,key2 = key.split(':')
if key1 in mData:
if key2 in mData[key1]:
mData[key1][key2] = value
set(_dmValues, 'caching:enabled', False)
print(_dmValues) # {'caching': {'enabled': False}}

I played around with it more and realised I had the solution, I was just applying it improperly.
Each dictionary, even if it's part of a key of another dictionary, can be passed around by reference. Meaning if I change a key in that dictionary, it will change in the parent as well. Unfortunately I was changing the variable that was a reference to the dictionary, not the dictionary itself.
This works perfectly
mData = _dm_Values
for i in range(0, iCount):
if i == (iCount - 1):
mData[key[i]] = value
else:
if key[i] not in mData.keys():
mData[key[i]] = dict()
mData = mData[key[i]]

Related

Boolean expressions: postfix to multiple infix strings

Given following (infix) expression:
(country = be or country = nl) and
(language = en or language = nl) and
message contains twitter
I'd like to create the following 4 infix notations:
message contains twitter and country = be and language = en
message contains twitter and country = be and language = en
message contains twitter and country = nl and language = nl
message contains twitter and country = nl and language = nl
So, basically, I would like to get rid of all OR's.
I already have a postfix notation for the first expression, so I'm currently trying to process that to get the desired notation. This particular situation, however, causes trouble.
(For illustration purposes, the postfix notation for this query would be:)
country be = country nl = or language en = language = nl or and message twitter contains and
Does anyone know of an algorithm to achieve this?
Break the problem into two steps: postfix to multiple postfix, postfix to infix. Each step is performed by "interpreting" a postfix expression.
For the postfix to multiple postfix interpreter: the stack values are collections of postfix expressions. The interpretation rules are as follows.
<predicate>: push a one-element collection containing <predicate>.
AND: pop the top two collections into C1 and C2. With two nested loops,
create a collection containing x y AND for all x in C1 and y in C2.
Push this collection.
OR: pop the top two collections into C1 and C2. Push the union of C1 and C2.
For the postfix to infix interpreter: the stack values are infix expressions.
<predicate>: push <predicate>.
AND: pop two expressions into x and y. Push the expression (x) and (y).
These steps could be combined, but I wanted to present two examples of this technique.
It might be easiest to work with a tree representation. Use the shunting yard algorithm to build a binary tree representing the equation. A node in the tree might be:
class Node {
const OP = 'operator';
const LEAF = 'leaf';
$type = null; // Will be eight Node::OP or Node::LEAF
$op = null; // could be 'or' or 'and' 'contains';
$value = null; // used for leaf eg 'twitter'
$left = null;
$right = null;
}
although you could use sub-classes. In the shunting yard algorithm you want the change the output steps to produce a tree.
Once you have a tree representation you need several algorithms.
First you need an algorithm to copy a tree
public function copy($node) {
if($node->type == Node::LEAF) {
$node2 = new Node();
$node2->type = Node::LEAF;
$node2->value = $node->value;
return $node2;
}
else {
$left = copy($node->left);
$right = copy($node->right);
$node2 = new Node();
$node2->type = Node::OP;
$node2->op = $node->op;
$node2->left = $node->left;
$node2->right = $node->right;
return $node2;
}
}
Next the algorithm to find the first 'or' operator node.
function findOr($node) {
if($node->type == Node::OP && $node->op == 'or') {
return $node;
} else if($node->type == Node::OP ) {
$leftRes = findOr($node->$left);
if( is_null($leftRes) ) {
$rightRes = findOr($node->$right); // will be null or a found node
return $rightRes;
} else {
return $leftRes; // found one on the left, no need to walk rest of tree
}
} else {
return null;
}
}
and finally an algorithm copyLR giving either the left (true) or right (false) branch. It behaves as copy unless the node matches $target when either the left or right branch is returned.
public function copyLR($node,$target,$leftRight) {
if($node == $target) {
if($leftRight)
return copy($node->left);
else
return copy($node->right);
}
else if($node->type == Node::LEAF) {
$node2 = new Node();
$node2->type = Node::LEAF;
$node2->value = $node->value;
return $node2;
}
else {
$left = copy($node->left,$target,$leftRight);
$right = copy($node->right,$target,$leftRight);
$node2 = new Node();
$node2->type = Node::OP;
$node2->op = $node->op;
$node2->left = $node->left;
$node2->right = $node->right;
return $node2;
}
}
The pieces are now put together
$root = parse(); // result from the parsing step
$queue = array($root);
$output = array();
while( count($queue) > 0) {
$base = array_shift($queue);
$target = findOr($base);
if(is_null($target)) {
$output[] = $base; // no or operators found so output
} else {
// an 'or' operator found
$left = copyLR($base,$target,true); // copy the left
$right = copyLR($base,$target,false); // copy the right
array_push($left); // push both onto the end of the queue
array_push($right);
}
}

PHP - array_push() & $array[] doesn't work?

1. PHP function.
I've created validating function, here it is in shorter version:
function my_function($input) {
$settings = my_source(); // function taht outputs a long array
foreach ($settings as $setting) {
$id = $setting['id'];
$foo = $setting['foo'];
$option = get_option('my_theme_settings');
if($foo == "bar") {
$valid_input[$id] = $input[$id];
}
}
return $valid_input;
};
Basically it takes $input and saves it as $valid_input. When it gets new $input it overwrites the old #valid_inpu and so on.
I want to create an additional $valid_input[$id] array that will not overwrite itself, but just push new elements inside.
2. Array_push() that doesn't work.
So the new updated code will look like that:
function my_function($input) {
$settings = my_source(); // function taht outputs a long array
foreach ($settings as $setting) {
$id = $setting['id'];
$foo = $setting['foo'];
$option = get_option('my_theme_settings');
if($foo == "bar") {
$valid_input[$id] = $input[$id];
}
else if($foo == "noupdate") { // it doesn't work
$valid_input[$id] = array();
array_push($valid_input[$id], $input[$id]);
}
}
return $valid_input;
};
As mentioned in comment above - this doesn't work, input always overwrites the option, it creates an array but it always contains only one element that is being erased with the new one (I guess array_push should prevent that behavior, right?).
3. The same happens with $array[] =
function my_function($input) {
$settings = my_source(); // function taht outputs a long array
foreach ($settings as $setting) {
$id = $setting['id'];
$foo = $setting['foo'];
$option = get_option('my_theme_settings');
if($foo == "bar") {
$valid_input[$id] = $input[$id];
}
else if($foo == "noupdate") { // it doesn't work
$valid_input[$id][] = $input[$id];
}
}
return $valid_input;
};
Still it overwrites the old value of $valid_input instead of pushing an element.
Any ideas? Maybe there's something wrong with the code? This whole function a Wordpress callback for function called register_setting(), but I guess it's mostly PHP related as folks on WPSE can't help me.
4. EDIT
This does exactly what I want, but why point 3. doesn't work then?
else if($foo == "noupdate") { // it doesn't work
$valid_input[$id][] = 'something';
$valid_input[$id][] = 'something_else';
$valid_input[$id][] = 'something_else2';
}
$valid_input[$id] needs to be set to array before you treat it as one.
$valid_input[$id] = array();
array_push( $valid_input[$id], "some stuff");
Same deal with [] notation
$valid_input[$id] = array();
$valid_input[$id][] = "some stuff";
To check if the array has been declared, so this:
if(!is_array($valid_input[$id]){
$valid_input[$id] = array();
}
The thing is that objects are passed as reference. you need to clone the objects before using the array_push function. here is a sample function that will clone an object:
function DeepCopy($ObjectToCopy) {
return unserialize(serialize($ObjectToCopy));
}
then you can use it this way
array_push($MyObjectsArray, DeepCopy($MyObject));
is it possible that you are trying to push a new value to the array with a key value that already exists? i would test for an existing key value in your array before trying to push a value/key pair to it. example:
if ( !isset( $arr[ $key ] ) ) {
$arr[ $key ] = $value;
} else {
echo " duplicate key value ";
}
Either array_push() or a variable used with the array append operator [] need to actually be an array or these won't work. Double check that whatever is in $valid_input[$id] is an array before doing array operations on the variable. Check by doing:
if (is_array($valid_input[$id])) {
// your code
}

PHP dynamically accessing variable value

I want to dynamically access value of variable, let's say I have this array:
$aData = array(
'test' => 123
);
Standard approach to print the test key value would be:
print $aData['test'];
However, if I have to work with string representation of variable (for dynamic purposes)
$sItem = '$aData[\'test\']';
how can I achieve to print aData key named test? Neither of examples provided below works
print $$sItem;
print eval($sItem);
What would be the solution?
Your eval example is lacking the return value:
print eval("return $sItem;");
should do it:
$aData['test'] = 'foo';
$sItem = '$aData[\'test\']';
print eval("return $sItem;"); # foo
But it's not recommended to use eval normally. You can go into hell's kitchen with it because eval is evil.
Instead just parse the string and return the value:
$aData['test'] = 'foo';
$sItem = '$aData[\'test\']';
$r = sscanf($sItem, '$%[a-zA-Z][\'%[a-zA-Z]\']', $vName, $vKey);
if ($r === 2)
{
$result = ${$vName}[$vKey];
}
else
{
$result = NULL;
}
print $result; # foo
This can be done with some other form of regular expression as well.
As your syntax is very close to PHP an actually a subset of it, there is some alternative you can do if you want to validate the input before using eval. The method is to check against PHP tokens and only allow a subset. This does not validate the string (e.g. syntax and if a variable is actually set) but makes it more strict:
function validate_tokens($str, array $valid)
{
$vchk = array_flip($valid);
$tokens = token_get_all(sprintf('<?php %s', $str));
array_shift($tokens);
foreach($tokens as $token)
if (!isset($vchk[$token])) return false;
return true;
}
You just give an array of valid tokens to that function. Those are the PHP tokens, in your case those are:
T_LNUMBER (305) (probably)
T_VARIABLE (309)
T_CONSTANT_ENCAPSED_STRING (315)
You then just can use it and it works with more complicated keys as well:
$aData['test'] = 'foo';
$aData['te\\\'[]st']['more'] = 'bar';
$sItem = '$aData[\'test\']';
$vValue = NULL;
if (validate_tokens($sItem, array(309, 315, '[', ']')))
{
$vValue = eval("return $sItem;");
}
I used this in another answer of the question reliably convert string containing PHP array info to array.
No eval necessary if you have (or can get) the array name and key into separate variables:
$aData = array(
'test' => 123
);
$arrayname = 'aData';
$keyname = 'test';
print ${$arrayname}[$keyname]; // 123
You can just use it like an ordinary array:
$key = "test";
print $aData[$key];
Likewise $aData could itself be an entry in a larger array store.
As alternative, extracting the potential array keys using a regex and traversing an anonymous array (should have mentioned that in your question, if) with references would be possible. See Set multi-dimensional array by key path from array values? and similar topics.
Personally I'm using a construct like this to utilize dynamic variable paths like varname[keyname] instead (similar to how PHP interprets GET parameters). It's just an eval in sheeps clothing (do not agree with the eval scaremongering though):
$val = preg_replace("/^(\w)+(\[(\w+)])$/e", '$\1["\3"]', "aData[test]");
The only solution in your case is to use Eval().
But please be very very very careful when doing this! Eval will evaluate (and execute) any argument you pass to it as PHP. So if you will feed it something that comes from users, then anyone could execute any PHP code on your server, which goes without saying is a security hole the size of Grand canyon!.
edit: you will have to put a "print" or "echo" inside your $sItem variable somehow. It will either have to be in $sItem ($sItem = 'echo $aData[\'test\']';) or you will have to write your Eval() like this: Eval ( 'echo ' . $sData ).
$sItem = '$aData[\'test\']';
eval('$someVar = '.$sItem.';');
echo $someVar;
Use eval() with high caution as others aldready explained.
You could use this method
function getRecursive($path, array $data) {
// transform "foo['bar']" and 'foo["bar"]' to "foo[bar]"
$path = preg_replace('#\[(?:"|\')(.+)(?:"|\')\]#Uis', '[\1]', $path);
// get root
$i = strpos($path, '[');
$rootKey = substr($path, 0, $i);
if (!isset($data[$rootKey])) {
return null;
}
$value = $data[$rootKey];
$length = strlen($path);
$currentKey = null;
for (; $i < $length; ++$i) {
$char = $path[$i];
switch ($char) {
case '[':
if ($currentKey !== null) {
throw new InvalidArgumentException(sprintf('Malformed path, unexpected "[" at position %u', $i));
}
$currentKey = '';
break;
case ']':
if ($currentKey === null) {
throw new InvalidArgumentException(sprintf('Malformed path, unexpected "]" at position %u', $i));
}
if (!isset($value[$currentKey])) {
return null;
}
$value = $value[$currentKey];
if (!is_array($value)) {
return $value;
}
$currentKey = null;
break;
default:
if ($currentKey === null) {
throw new InvalidArgumentException(sprintf('Malformed path, unexpected "%s" at position %u', $char, $i));
}
$currentKey .= $char;
break;
}
}
if ($currentKey !== null) {
throw new InvalidArgumentException('Malformed path, must be and with "]"');
}
return $value;
}

Most efficient way to search for object in an array by a specific property's value

What would be the fastest, most efficient way to implement a search method that will return an object with a qualifying id?
Sample object array:
$array = [
(object) ['id' => 'one', 'color' => 'white'],
(object) ['id' => 'two', 'color' => 'red'],
(object) ['id' => 'three', 'color' => 'blue']
];
What do I write inside of:
function findObjectById($id){
}
The desired result would return the object at $array[0] if I called:
$obj = findObjectById('one')
Otherwise, it would return false if I passed 'four' as the parameter.
You can iterate that objects:
function findObjectById($id){
$array = array( /* your array of objects */ );
foreach ( $array as $element ) {
if ( $id == $element->id ) {
return $element;
}
}
return false;
}
Edit:
Faster way is to have an array with keys equals to objects' ids (if unique);
Then you can build your function as follow:
function findObjectById($id){
$array = array( /* your array of objects with ids as keys */ );
if ( isset( $array[$id] ) ) {
return $array[$id];
}
return false;
}
It's an old question but for the canonical reference as it was missing in the pure form:
$obj = array_column($array, null, 'id')['one'] ?? false;
The false is per the questions requirement to return false. It represents the non-matching value, e.g. you can make it null for example as an alternative suggestion.
This works transparently since PHP 7.0. In case you (still) have an older version, there are user-space implementations of it that can be used as a drop-in replacement.
However array_column also means to copy a whole array. This might not be wanted.
Instead it could be used to index the array and then map over with array_flip:
$index = array_column($array, 'id');
$map = array_flip($index);
$obj = $array[$map['one'] ?? null] ?? false;
On the index the search problem might still be the same, the map just offers the index in the original array so there is a reference system.
Keep in mind thought that this might not be necessary as PHP has copy-on-write. So there might be less duplication as intentionally thought. So this is to show some options.
Another option is to go through the whole array and unless the object is already found, check for a match. One way to do this is with array_reduce:
$obj = array_reduce($array, static function ($carry, $item) {
return $carry === false && $item->id === 'one' ? $item : $carry;
}, false);
This variant again is with the returning false requirement for no-match.
It is a bit more straight forward with null:
$obj = array_reduce($array, static function ($carry, $item) {
return $carry ?? ($item->id === 'one' ? $item : $carry);
}, null);
And a different no-match requirement can then be added with $obj = ...) ?? false; for example.
Fully exposing to foreach within a function of its own even has the benefit to directly exit on match:
$result = null;
foreach ($array as $object) {
if ($object->id === 'one') {
$result = $object;
break;
}
}
unset($object);
$obj = $result ?? false;
This is effectively the original answer by hsz, which shows how universally it can be applied.
You can use the function array_search of php like this
$key=array_search("one", array_column(json_decode(json_encode($array),TRUE), 'color'));
var_dump($array[$key]);
i: is the index of item in array
1: is the property value looking for
$arr: Array looking inside
'ID': the property key
$i = array_search(1, array_column($arr, 'ID'));
$element = ($i !== false ? $arr[$i] : null);
Well, you would would have to loop through them and check compare the ID's unless your array is sorted (by ID) in which case you can implement a searching algorithm like binary search or something of that sort to make it quicker.
My suggestion would be to first sort the arrays using a sorting algorithm (binary sort, insertion sort or quick sort) if the array is not sorted already. Then you can implement a search algorithm which should improve performance and I think that's as good as it gets.
http://www.algolist.net/Algorithms/Binary_search
This is my absolute favorite algorithm for very quickly finding what I need in a very large array, quickly. It is a Binary Search Algorithm implementation I created and use extensively in my PHP code. It hands-down beats straight-forward iterative search routines. You can vary it a multitude of ways to fit your need, but the basic algorithm remains the same.
To use it (this variation), the array must be sorted, by the index you want to find, in lowest-to-highest order.
function quick_find(&$array, $property, $value_to_find, &$first_index) {
$l = 0;
$r = count($array) - 1;
$m = 0;
while ($l <= $r) {
$m = floor(($l + $r) / 2);
if ($array[$m]->{$property} < $value_to_find) {
$l = $m + 1;
} else if ($array[$m]->{$property} > $value_to_find) {
$r = $m - 1;
} else {
$first_index = $m;
return $array[$m];
}
}
return FALSE;
}
And to test it out:
/* Define a class to put into our array of objects */
class test_object {
public $index;
public $whatever_you_want;
public function __construct( $index_to_assign ) {
$this->index = $index_to_assign;
$this->whatever_you_want = rand(1, 10000000);
}
}
/* Initialize an empty array we will fill with our objects */
$my_array = array();
/* Get a random starting index to simulate data (possibly loaded from a database) */
$my_index = rand(1256, 30000);
/* Say we are needing to locate the record with this index */
$index_to_locate = $my_index + rand(200, 30234);
/*
* Fill "$my_array()" with ONE MILLION objects of type "test_object"
*
* 1,000,000 objects may take a little bit to generate. If you don't
* feel patient, you may lower the number!
*
*/
for ($i = 0; $i < 1000000; $i++) {
$searchable_object = new test_object($my_index); // Create the object
array_push($my_array, $searchable_object); // Add it to the "$my_array" array
$my_index++; /* Increment our unique index */
}
echo "Searching array of ".count($my_array)." objects for index: " . $index_to_locate ."\n\n";
$index_found = -1; // Variable into which the array-index at which our object was found will be placed upon return of the function.
$object = quick_find($my_array, "index", $index_to_locate, $index_found);
if ($object == NULL) {
echo "Index $index_to_locate was not contained in the array.\n";
} else {
echo "Object found at index $index_found!\n";
print_r($object);
}
echo "\n\n";
Now, a few notes:
You MAY use this to find non-unique indexes; the array MUST still be sorted in ascending order. Then, when it finds an element matching your criteria, you must walk the array backwards to find the first element, or forward to find the last. It will add a few "hops" to your search, but it will still most likely be faster than iterating a large array.
For STRING indexes, you can change the arithmetic comparisons (i.e. " > " and " < " ) in quick_find() to PHP's function "strcasecmp()". Just make sure the STRING indexes are sorted the same way (for the example implementation): Alphabetically and Ascending.
And if you want to have a version that can search arrays of objects sorted in EITHER ascending OR decending order:
function quick_find_a(&$array, $property, $value_to_find, &$first_index) {
$l = 0;
$r = count($array) - 1;
$m = 0;
while ($l <= $r) {
$m = floor(($l + $r) / 2);
if ($array[$m]->{$property} < $value_to_find) {
$l = $m + 1;
} else if ($array[$m]->{$property} > $value_to_find) {
$r = $m - 1;
} else {
$first_index = $m;
return $array[$m];
}
}
return FALSE;
}
function quick_find_d(&$array, $property, $value_to_find, &$first_index) {
$l = 0;
$r = count($array) - 1;
$m = 0;
while ($l <= $r) {
$m = floor(($l + $r) / 2);
if ($value_to_find > $array[$m]->{$property}) {
$r = $m - 1;
} else if ($value_to_find < $array[$m]->{$property}) {
$l = $m + 1;
} else {
$first_index = $m;
return $array[$m];
}
}
return FALSE;
}
function quick_find(&$array, $property, $value_to_find, &$first_index) {
if ($array[0]->{$property} < $array[count($array)-1]->{$property}) {
return quick_find_a($array, $property, $value_to_find, $first_index);
} else {
return quick_find_d($array, $property, $value_to_find, $first_index);
}
}
The thing with performance of data structures is not only how to get but mostly how to store my data.
If you are free to design your array, use an associative array:
$array['one']->id = 'one';
$array['one']->color = 'white';
$array['two']->id = 'two';
$array['two']->color = 'red';
$array['three']->id = 'three';
$array['three']->color = 'blue';
Finding is then the most cheap: $one = $array['one];
UPDATE:
If you cannot modify your array constitution, you could create a separate array which maps ids to indexes. Finding an object this way does not cost any time:
$map['one'] = 0;
$map['two'] = 1;
$map['three'] = 2;
...
getObjectById() then first lookups the index of the id within the original array and secondly returns the right object:
$index = $map[$id];
return $array[$index];
Something I like to do in these situations is to create a referential array, thus avoiding having to re-copy the object but having the power to use the reference to it like the object itself.
$array['one']->id = 'one';
$array['one']->color = 'white';
$array['two']->id = 'two';
$array['two']->color = 'red';
$array['three']->id = 'three';
$array['three']->color = 'blue';
Then we can create a simple referential array:
$ref = array();
foreach ( $array as $row )
$ref[$row->id] = &$array[$row->id];
Now we can simply test if an instance exists in the array and even use it like the original object if we wanted:
if ( isset( $ref['one'] ) )
echo $ref['one']->color;
would output:
white
If the id in question did not exist, the isset() would return false, so there's no need to iterate the original object over and over looking for a value...we just use PHP's isset() function and avoid using a separate function altogether.
Please note when using references that you want use the "&" with the original array and not the iterator, so using &$row would not give you what you want.
This is definitely not efficient, O(N). But it looks sexy:
$result = array_reduce($array, function ($found, $obj) use ($id) {
return $obj['id'] == $id ? $obj : $found;
}, null);
addendum:
I see hakre already posted something akin to this.
Here is what I use. Reusable functions that loop through an array of objects. The second one allows you to retrieve a single object directly out of all matches (the first one to match criteria).
function get_objects_where($match, $objects) {
if ($match == '' || !is_array($match)) return array ();
$wanted_objects = array ();
foreach ($objects as $object) {
$wanted = false;
foreach ($match as $k => $v) {
if (is_object($object) && isset($object->$k) && $object->$k == $v) {
$wanted = true;
} else {
$wanted = false;
break;
};
};
if ($wanted) $wanted_objects[] = $object;
};
return $wanted_objects;
};
function get_object_where($match, $objects) {
if ($match == '' || !is_array($match)) return (object) array ();
$wanted_objects = get_objects_where($match, $objects);
return count($wanted_objects) > 0 ? $wanted_objects[0] : (object) array ();
};
The easiest way:
function objectToArray($obj) {
return json_decode(json_encode($obj), true);
}

Array manipulation in PHP

I am using PHP. I need to implement the following logic:
My $List has the following data:
array('id' =>'1','flag'=>'1','place1' =>'val2' ,'place2'=>'val3')
array('id'=> '1','flag'=>'0','place1' =>'val3' ,'place2'=>'val7')
array('id'=> '1','flag'=>'0','place1' =>'val4' ,'place2'=>'val8')
array('id'=> '1','flag'=>'0','place1' =>'val5' ,'place2'=>'val9')
array('id'=> '2','flag'=>'1','place1' =>'val10','place2'=>'val11')
array('id'=> '3','flag'=>'1','place1' =>'val11','place2'=>'val14')
My logic:
if (flag == 0){
Store the values of place1 ,Place2
in the Index of id
/*example
$store[1]= {place1}+{place2}
hence output:
store[1]= {val3+val4+val5}+{val7+val8+val9}
similarly
store[2]= empty
store[3]= empty
*/
}
else{
Print the Values of place1,place2 of its index
/* example
print place1[1]="val2" ,place2[1]='val3'
place1[2]="val10",place2[2]='val11'
place1[3]="val11",place2[3]='val14'
*/
}
My partial snippet has:
foreach ($List as $key => $val)
{
if($val['flag']!==0){
//how to implementation
}
else{
echo '$val['place1']'.'$val['place2']';
}
}
What is the best way to proceed?
Assumed that you actually have numeric values in place1 and place2 and thus commutative addition is allowed. If you really have text and need the braces, then I would join the two parts with another pass over store later (but then I would not understand the {0}+{0}).
I tried to keep your quotation marks in the else part. Not tested at all.
$store = array();
foreach ($List as $key => $val)
{
$id = $val['id'];
if (!array_key_exists($id, $store)) {
$store[$id] = 0;
}
if ($val['flag'] == 0) {
$store[$id] += $val['place1'];
$store[$id] += $val['place2'];
} else {
printf("place1[%d]=\"%s\",place2[%d]='%s'\n", $id, $val['place1'], $id, $val['place2']);
}
}
There is a nice function for filtering data out of an array: array___filter. The function requires a callback function, a function that will make the decision whether an element should stay in the array. We create a filter function using create_function and extract the interesting part of the array.
$reducingFunction = create_function('$value', 'return $value["flag"] == 0;');
$reduced = array_filter($list, $reducingFunction);
The next neat function (array_walk) is one that transforms the elements of our array (i.e. $value = $value['param1']), so we create another callback function:
$extractingFunction = create_function('&$value, $key, $index', '$value = $value[$index];');
$valuesParam1 = $reduced;
$valuesParam2 = $reduced;
array_walk($valuesParam1, $extractingFunction, 'param1');
array_walk($valuesParam2, $extractingFunction, 'param1');
Now we have two arrays containing all the data we need:
$store = array(1 => implode('', $valuesParam1).implode('', $valuesParam2));
I don't seem to understand what you want the array $store to contain else. Or what the else-branch should do, so this would require further explanation.
The code newbie friendly :
$store = array(); // Don't forget to initialize your vars
foreach ($List as $key => $val)
{
if ($val['flag']==0)
{
// we check if $store[$val['id']] contains any value first and set it to zero
if (isset($store[$val['id']]))
{
// we add the previous value with the new one
$store[$val['id'] = $store[$val['id']] + $val['place1'] + $val['place2'];
}
else
{
// first time seeing this id, we set the value to 0
$store[$val['id']] = 0;
}
}
else
{
echo '$val['place1']'.'$val['place2']'."\n"; // adding line break (use </ br> if in a web browser)
}
}
I didn't get if "flag" should be 0 or not, so tweak it on.
Version 2 :
If "flag" is just used for boolean logic, you'd may just replace the above code with :
if (!$val['flag'])
Then you can use PHP shortcuts to sum up :
$store[$val['id'] += $val['place1'] + $val['place2'];
Bracelets are not required for one line operations :
if (isset($store[$val['id']]))
$store[$val['id'] = $store[$val['id']] + $val['place1'] + $val['place2'];
else
$store[$val['id']] = 0;
And you don't need the "$key" var either.
So the code becomes :
$store = array();
foreach ($List as $val)
if (!$val['flag'])
if (isset($store[$val['id']]))
$store[$val['id'] += $val['place1'] + $val['place2'];
else
$store[$val['id']] = 0;
else
echo '$val['place1']'.'$val['place2']'."\n";
Version 3 :
if / else/ set operations can be shorten using the ternary operator (beware it's less readable and slower):
$store[$val['id'] = isset($store[$val['id']]) ? 0 : $store[$val['id']] + $val['place1'] + $val['place2'];
So the code becomes :
$store = array();
foreach ($List as $val)
if (!$val['flag'])
$store[$val['id'] = isset($store[$val['id']]) ? 0 : $store[$val['id']] + $val['place1'] + $val['place2'];
else
echo '$val['place1']'.'$val['place2']'."\n";
Pro version
Once you get this one, check soulmerge's code. It is really smarter to use fonctional programming when you can since it's smaller, and result can be piped to each others, so the code is more reusable. What's more, using generators consume often less memory.

Categories