I'm making a array class and want a value to be able to be returned by a higher order function. The idea is that its a instance constant or method returned value such that I can skip the value in a map.
In other languages making an array or some compound value, like ['skip'] will make it pointer equal such that I can then use the operator for pointer equal and it will not be equal to other arrays with the exact same content, but my problem is that ['skip'] === ['skip'] is true so even with === the two values are the same.
Here is an example of usage of my code where I accedentally have the same value as I used to skip:
namespace Test;
use Common\Domain\Collection;
$arr = new Collection();
$arr[] = 1;
$arr[] = 2;
$arr[] = 3;
$arr[] = 4;
echo count($arr); // prints 4
$arr2 = $arr->map(function ($v) {
return $v % 2 == 0 ? Collection::SKIP : ["skip"];
});
echo count($arr2); // prints 0, but should be 2
Is there a way to get a unique value or work around this somehow?
Here is code that implements Collection:
namespace Common\Domain;;
class Collection implements \Iterator, \Countable, \ArrayAccess
{
const SKIP = ["skip"];
private $arr = [];
public function map(callable $fn, bool $keepKeys = false) :Collection
{
$arr = new static();
$nOrder = 0;
foreach($this->arr as $key => $value) {
$result = call_user_func($fn, $value, $key, $nOrder, $this);
if($result !== self::SKIP) {
if($keepKeys) {
$arr[$key] = $result;
} else {
$arr[] = $result;
}
}
}
return $arr;
}
// implementation of interfaces \Iterator, \Countable, \ArrayAccess
public function current()
{
return current($this->arr);
}
public function next()
{
next($this->arr);
}
public function key()
{
return key($this->arr);
}
public function valid()
{
return isset($this->arr[$this->key()]);
}
public function rewind()
{
reset($this->arr);
}
public function count()
{
return count($this->arr);
}
public function offsetExists($offset)
{
return array_key_exists($offset, $this->arr);
}
public function offsetGet($offset)
{
return $this->arr[$offset];
}
public function offsetSet($offset, $value)
{
$this->arr[$offset] = $value;
}
public function offsetUnset($offset)
{
unset($this->arr[$offset]);
}
}
I guess you are looking for Java-type enumerations, which doesn't exist in PHP. My best guess on your problem would be to use an object instead of a constant, that you would instantiate statically for a convenient use. Then, in the loop of your map function, you check the value with an instanceof instead of the basic equality operator, against the class you defined.
So, here :
class UniqueValue
{
public static function get()
{
return new self();
}
}
Then :
$arr2 = $arr->map(function ($v) {
return $v % 2 == 0 ? UniqueValue::get() : ["skip"];
});
And inside your collection :
public function map(callable $fn, bool $keepKeys = false) :Collection
{
$arr = new static();
$nOrder = 0;
foreach($this->arr as $key => $value) {
$result = call_user_func($fn, $value, $key, $nOrder, $this);
if($result ! instanceof UniqueValue) {
if($keepKeys) {
$arr[$key] = $result;
} else {
$arr[] = $result;
}
}
}
return $arr;
}
This is the quickest approach I can think of. If your array contains data from "outside" I don't think it's possible in any way that it matches against a class check from your own code.
I would solve this by implementing another method for this. The method delete would map a function over the collection and remove any elements where the function returns false.
e.g.
class Collection
{
// ...
public function delete($func)
{
$result = new static();
foreach($this->arr as $item)
{
if($func($item) !== false) $result[] = $item;
}
}
}
// example
$arr = new Collection();
$arr[] = 1;
$arr[] = 2;
$arr[] = 3;
$arr[] = 4;
echo count($arr); // prints 4
$arr2 = $arr->delete(function ($v) {
return $v % 2 ? true : false;
});
var_dump($arr2); // prints [2, 4]
Related
I have some script for long random xml and I need to find value when I know key.
I tried to use array_walk_recursive - but when I used it - I took value only when I used echo. When I used return I took only true or false...
I need to take back a variables for next processing.
Can you help me please?
class ClassName{
private $array;
private $key ;
public $value;
public $val;
function getKey($key) {
$this->key = $key;
return $key;
}
function getFind($value, $key)
{
static $i = 0;
if ($key === ($this->key)) {
$value = $value[$i];
$i++;
return $value;
}
}
}
$xml_simple = simplexml_load_file('./logs/xml_in1.xml');
$json = json_encode($xml_simple);
$array = json_decode($json, TRUE);
$obj = new ClassName();
$obj_key = 'pracovnik';
$obj->getKey($obj_key);
print_r(array_walk_recursive($array,[$obj,"getFind"]));
print_r( $obj->value);
The return value of array_walk_recursive is:
Returns true on success or false on failure.
What you might so as an idea is to use an array where you can add values to when this if clause is true:
if ($key === $this->key) {
Then you could create another method to get the result:
For example
class ClassName
{
private $key;
private $result = [];
function setKey($key) {
$this->key = $key;
}
function find($value, $key) {
if ($key === $this->key) {
$this->result[$key][] = $value;
}
}
function getResult(){
return $this->result;
}
}
$xml_simple = simplexml_load_file('./logs/xml_in1.xml');
$json = json_encode($xml_simple);
$array = json_decode($json, TRUE);
$obj = new ClassName();
$obj->setKey('pracovnik');
array_walk_recursive($array, [$obj, "find"]);
print_r($obj->getResult());
A few notes about the code that you tried:
You have a line after the return statement return $value; that will never be executed
You have declared but not using public $val; and private $array;
I think function getKey is better named setKey as you are only setting the key
I have a binary tree and node class that can create nodes and then recursively traverse the root for pre, post and in-order node orders. This code works when in JS, but for some reason infinitely loops with a warning of "Cannot use '$this' in non-object context." when returning $this in the addSide() function. What is causing this infinite loop, and how can I fix it?
<?php
class Node {
public $value;
public $right = null;
public $left = null;
function __constructor($value) {
$this->value = $value;
}
}
class BinaryTree {
public $root;
function __constructor() {}
function create($value) {
$newNode = new Node($value);
if (!$this->root) {
$this->root = $newNode;
return $this; //no warning
}
$current = $this->root;
function addSide($side, $current, $newNode) {
if (!$current->$side) {
$current->$side = $newNode;
return $this; //Warning: "Cannot use '$this' in non-object context."
}
$current = $current->$side;
};
while (true) {
if ($value === $current->value) return $this;
if ($value < $current->value) addSide("left", $current, $newNode);
else addSide("right", $current, $newNode);
}
}
function preOrder() {
$visited = [];
$current = $this->root;
function traversePreOrder($node) {
array_push($visited, $node->value);
if ($node->left) traversePreOrder($node->left);
if ($node->right) traversePreOrder($node->right);
};
traversePreOrder($current);
return $visited;
}
function postOrder() {
$visited = [];
$current = $this->root;
function traversePostOrder($node) {
if ($node->left) traversePostOrder($node->left);
if ($node->right) traversePostOrder($node->right);
array_push($visited, $node->value);
};
traversePostOrder($current);
return $visited;
}
function inOrder() {
$visited = [];
$current = $this->root;
function traverseInOrder($node) {
if ($node->left) traverseInOrder($node->left);
array_push($visited, $node->value);
if ($node->right) traverseInOrder($node->right);
};
traverseInOrder($current);
return $visited;
}
}
$tree = new BinaryTree();
$tree->create(50);
$tree->create(30);
$tree->create(45);
$tree->create(12);
$tree->create(29);
echo("inOrder: ". $tree->inOrder());
echo("preOrder: ". $tree->preOrder());
echo("postOrder: ". $tree->postOrder());
Since you don't seem to be from a PHP background, here are some of the things to note down:
It is __construct() and not __constructor(). This served to be a major problem in the code during value comparisons.
No need to create functions inside functions. This can lead to cannot redeclare function issues when a method is called twice.
When calling a method from another method inside a class, $this-> is necessary unless the function being called is an inbuilt function in PHP or at least available during code execution.
You seem to be creating a Binary Search Tree instead of just a Binary Tree.
Pass $visited by reference when collecting values during traversal.
You can't print arrays using echo. Use print_r() or use implode() to convert the array to string using a delimiter(say ,) and then print it using echo.
In create(), you sometimes return a node and sometimes $this. Both are not the same. Former one is an object of the Node class and the latter one is the object of the BinaryTree class.
In create() method, you simply need to traverse left or right from the current code according to the given value, which can be achieved using a simple while loop.
Corrected Code:
<?php
class Node {
public $value;
public $right = null;
public $left = null;
function __construct($value) {
$this->value = $value;
}
}
class BinaryTree {
public $root;
function __construct() {
$this->root = null;
}
function create($value) {
$newNode = new Node($value);
if ($this->root === null) {
$this->root = $newNode;
return $newNode; //no warning
}
$current = $this->root;
while($current !== null){
if($current->value > $value){
if($current->left === null){
$current->left = $newNode;
break;
}else{
$current = $current->left;
}
}else if($current->value < $value){
if($current->right === null){
$current->right = $newNode;
break;
}else{
$current = $current->right;
}
}else{
throw new \Exception("Node with $value already exists.");
}
}
return $newNode;
}
function preOrder() {
$visited = [];
$current = $this->root;
$this->traversePreOrder($current,$visited);
return $visited;
}
function traversePreOrder($node,&$visited) {
array_push($visited, $node->value);
if ($node->left !== null) $this->traversePreOrder($node->left,$visited);
if ($node->right !== null) $this->traversePreOrder($node->right,$visited);
}
function postOrder() {
$visited = [];
$current = $this->root;
$this->traversePostOrder($current,$visited);
return $visited;
}
function traversePostOrder($node,&$visited) {
if ($node->left !== null) $this->traversePostOrder($node->left,$visited);
if ($node->right !== null) $this->traversePostOrder($node->right,$visited);
array_push($visited, $node->value);
}
function inOrder() {
$visited = [];
$current = $this->root;
$this->traverseInOrder($current,$visited);
return $visited;
}
function traverseInOrder($node,&$visited) {
if ($node->left != null) $this->traverseInOrder($node->left,$visited);
array_push($visited, $node->value);
if ($node->right !== null) $this->traverseInOrder($node->right,$visited);
}
}
$tree = new BinaryTree();
$tree->create(50);
$tree->create(30);
$tree->create(45);
$tree->create(12);
$tree->create(29);
echo "inOrder: ". implode(",",$tree->inOrder()),PHP_EOL;
echo "preOrder: ". implode(",",$tree->preOrder()),PHP_EOL;
echo "postOrder: ". implode(",",$tree->postOrder()),PHP_EOL;
Online Demo
I have a class that is basically a wrapper to an Array and implements IteratorAggregate.
When a new object of the class is created, it stores it's value in a protected variable called $value. The value can be a string, integer, etc.. or a traversable object (like an array).
This object is "recursive" since when a traversable object (like an array) is passed to the constructor, he creates another instance of the class. Here's the constructor to clarify:
class ListItem implements \IteratorAggregate
{
protected $readOnly = false;
protected $key;
protected $value;
public function __construct($key, $value, $readOnly = false)
{
if($readOnly) $this->readOnly = true;
if(is_numeric($key)) $key = 'index'.$key;
$this->key = $key;
if (is_array($value) || $value instanceof Traversable) {
$this->value = array();
foreach($value as $k => $v) {
if(is_numeric($k)) $k = 'index'.$k;
$this->value[$k] = new ListItem($k, $v, $readOnly);
}
} else {
$this->value = $value;
}
}
public function __toString()
{
if ( is_array($this->value) ) {
return 'ListItem OBJECT(' . count($this->value) . ')';
} else {
return $this->value;
}
}
Right now, I'm trying to write a simple sorting method for the class.
To sort by Key, this works like a charm:
$success = ksort($this->value, $comparison);
but to sort by value, asort does not work since the actual value I'm trying to sort is stored inside the value property.
So I tried using uasort, like this:
$success = uasort($this->value, function ($a, $b)
{
if ($a->value == $b->value) return 0;
else if($a->value < $b->value) return -1;
else return 1;
});
but for some unclear reason i get the following error:
Warning: uasort() [function.uasort]: Array was modified by the user
comparison function in /* /* /* /ListItem.php on line 129
Q. Why does this happen if I'm just accessing $value for comparison not actually changing anything?
It seems a closure (or anonymous function) is in the global scope which means uasort could not access private or protected members of the ListItem object (although other ListItem Objects can access their sibling's private/protected properties)
this solved the problem: (casting to string)
$success = uasort($this->value, function ($objA, $objB) use ($comparison)
{
$a = (string) $objA;
$b = (string) $objB;
if($comparison == ListItem::SORT_NUMERIC) {
if (is_numeric($a)) $a = (int) $a;
if (is_numeric($b)) $b = (int) $b;
}
if ($a == $b) return 0;
else if($a < $b) return -1;
else return 1;
});
The goal is to pass a specific array element through custom_format().
Example: If $hierarchy = '4:0:2', then $data[4][0][2] = custom_format($data[4][0][2]).
Does anyone know how to replicate the following code without relying on eval()?
Current code:
$hierarchy = '4:0:2';
$hierarchy = str_replace(':', '][', $hierarchy);
eval("\$data[$hierarchy] = custom_format(\$data[$hierarchy]);");
Thanks in advance.
An overly verbose yet elegant option is the following:
class MyArray implements ArrayAccess {
public function offsetExists($offset) {
if(!is_array($offset))
$offset = explode(':', $value);
$key = array_shift($offset);
if($key !== NULL) {
if($this->$key InstanceOf MyArray) {
return(isset($this->$key[$offset]));
}
}
}
public function offsetGet($offset) {
if(!is_array($offset))
$offset = explode(':', $value);
$key = array_shift($offset);
if($key !== NULL) {
if($this->$key InstanceOf MyArray) {
return($this->$key[$offset]);
}
}
}
public function offsetSet($offset, $value) {
if(!is_array($offset))
$offset = explode(':', $value);
$key = array_shift($offset);
if($key !== NULL) {
if(!($this->$key InstanceOf MyArray)) {
$this->$key = new MyArray;
}
$this->$key[$offset] = $value;
}
}
public function offsetUnset($offset) {
if(!is_array($offset))
$offset = explode(':', $value);
$key = array_shift($offset);
if($key !== NULL) {
if($this->$key InstanceOf MyArray) {
return(unset($this->$key[$offset]));
}
if(count($offset) == 0) {
return(unset($this->$key));
}
}
}
}
This does imply using MyArray everywhere you need this kind of array behaviour and perhaps creating a static method that recursively converts arrays and they array children into MyArray objects so that they will respond consistently to this behavior.
One concrete example is the need to change the offsetGet method, to check if $value is an array then to use the conversion function to convert it to a MyArray if you want to access its elements.
How about something like this:
<?php
$hierarchy = '4:0:2';
list($a,$b,$c) = explode(':',$hierarchy);
echo $data[$a][$b][$c];
?>
This question already exists:
Closed 11 years ago.
Possible Duplicate:
Any way to access array directly after method call?
In C# and other languages, I can do something like this
$value = $obj->getArray()[0];
But not in PHP. Any workarounds or am I doomed to do this all the time?
$array = $obj->getArray();
$value = $array[0];
No, you can't do it without using array_shift (Which only gets the first element). If you want to access the third or fourth, most likely you'd want to do a function like this:
function elemnt($array, $element)
{
return $array[$element];
}
$value = element($obj->getArray(), 4);
Also, see this question, as it is an exact duplicate: Any way to access array directly after method call?
I think you are doomed to do it that way :(
You can do this:
$res = array_pop(array_slice(somefunc(1), $i, 1));
If this is a one-off or occasional thing where the situation in your example holds true, and you're retrieving the first element of the return array, you can use:
$value = array_shift($obj->getArray());
If this is a pervasive need and you often need to retrieve elements other than the first (or last, for which you can use array_pop()), then I'd arrange to have a utility function available like so:
function elementOf($array, $index = 0) {
return $array[$index];
}
$value = elementOf($obj->getArray());
$otherValue = elementOf($obj->getArray(), 2);
Well, maybe this could help you, in php spl, pretty usefull, you can make you a Special Array for you:
<?php
class OArray extends ArrayObject{
public function __set($name, $val) {
$this[$name] = $val;
}
public function __get($name) {
return $this[$name];
}
}
class O{
function __construct(){
$this->array = new OArray();
$this->array[] = 1;
$this->array[] = 2;
$this->array[] = 3;
$this->array[] = 4;
}
function getArray(){
return $this->array;
}
}
$o = new O();
var_dump( $o->getArray()->{1} );
<?php
class MyArray implements Iterator ,ArrayAccess
{
private $var = array();
//-- ArrayAccess
public function offsetSet($offset, $value) {
if (is_null($offset)) {
$this->var[] = $value;
} else {
$this->var[$offset] = $value;
}
}
public function offsetExists($offset) {
return isset($this->var[$offset]);
}
public function offsetUnset($offset) {
unset($this->var[$offset]);
}
public function offsetGet($offset) {
return isset($this->var[$offset]) ? $this->var[$offset] : null;
}//-- Iterator
public function __construct($array){
if (is_array($array)) {
$this->var = $array;
}
}
public function rewind() {
reset($this->var);
}
public function current() {
return current($this->var);
}
public function key() {
return key($this->var);
}
public function next() {
return next($this->var);
}
public function valid() {
return ($this->current() !== false);
}
}
$values = array(
"one" => 1,
"two" => 2,
"three" => 3,
);
$it = new MyArray($values);
foreach ($it as $a => $b) {
print "$a: $b<br>";
}
echo $it["one"]."<br>";?>