What is the php implementation of underscore's _.findWhere({key:'val'})?
This is how the method is documented by Underscorejs.org:
_.findWhere(list, properties)
Looks through the list and returns the first value that matches all of the key-value pairs listed in properties.
I wrote these little functions. Might be handy.
function where($list, $props)
{
$result = array_filter(
$list,
function ($e) use ($props)
{
$count = 0;
foreach ($props as $key => $value)
{
if ($value == $e[$key])
{
$count += 1;
}
return $count == count($props);
}
}
);
return $result;
}
function findWhere($list, $props)
{
$result = where($list, $props);
return array_values($result)[0];
}
There is none. Underscore is a collection of functions written in JS. It has nothing to do with PHP.
Related
I have array of arrays - tree structure of main menu.
I try to find in this tree one node with needed slug and return this node with all it's childs.
I write little recurcive function
<?php
$tree = '[{"id":1,"structure":1,"parent":0,"slug":"medicinskaya-ge","child":{"2":{"id":2,"structure":1.1,"parent":1,"slug":"dnk-diagnostika","child":{"3":{"id":3,"structure":"1.1.1","parent":2,"slug":"dnk-diagnostika","datafile":"ssz","template":"ssz"},"4":{"id":4,"structure":"1.1.2","parent":2,"slug":"dnk-diagnostika","child":{"5":{"id":5,"structure":"1.1.2.1","parent":4,"slug":"dnk-diagnostika"},"6":{"id":6,"structure":"1.1.2.2","parent":4,"slug":"testirovanie-ge"},"7":{"id":7,"structure":"1.1.2.3","parent":4,"slug":"dnk-diagnostika"}}},"8":{"id":8,"structure":"1.1.3","parent":2,"slug":"dnk-diagnostika"},"9":{"id":9,"structure":"1.1.4","parent":2,"slug":"texnologiya-kol"}}}}}]';
$tree = json_decode($tree, true);
function getSlugData(string $slug, array $data)
{
foreach ($data as $row) {
if ($row['slug'] == $slug) {
return $row;
}
if (isset($row['child'])) {
//return $this->getSlugData($slug, $row['child']);
}
}
return [];
}
$result = getSlugData('testirovanie-ge', $tree);
print_r($result);
But as a result I have an empty array. If I print_r($row) when $row['slug'] == $slug - It appears on screen.
if ($row['slug'] == $slug) {
exit(print_r($row));
return $row;
}
What's my mistake?
In programming, recursion is a useful and powerful mechanism that allows a function to call itself directly or indirectly, that is, a function is said to be recursive if it contains at least one explicit or implicit call to itself.
I modified your code a bit and got the solution below.
$tree = '[{"id":1,"structure":1,"parent":0,"slug":"medicinskaya-ge","child":{"2":{"id":2,"structure":1.1,"parent":1,"slug":"dnk-diagnostika","child":{"3":{"id":3,"structure":"1.1.1","parent":2,"slug":"dnk-diagnostika","datafile":"ssz","template":"ssz"},"4":{"id":4,"structure":"1.1.2","parent":2,"slug":"dnk-diagnostika","child":{"5":{"id":5,"structure":"1.1.2.1","parent":4,"slug":"dnk-diagnostika"},"6":{"id":6,"structure":"1.1.2.2","parent":4,"slug":"testirovanie-ge"},"7":{"id":7,"structure":"1.1.2.3","parent":4,"slug":"dnk-diagnostika"}}},"8":{"id":8,"structure":"1.1.3","parent":2,"slug":"dnk-diagnostika"},"9":{"id":9,"structure":"1.1.4","parent":2,"slug":"texnologiya-kol"}}}}}]';
$tree = json_decode($tree, true);
//print_r($tree);
//die();
function getSlugData(string $slug, array $data, string $key = 'slug')
{
$result = [];
foreach ($data as $row) {
// Checks if the key exists and is the desired value for that key in the array
if (isset($row[$key]) && $row[$key] === $slug) {
return $row;
}
// If it is an array, apply recursion by calling getSlugData again
if (is_array($row)) {
$result = getSlugData($slug, $row, $key);
if ($result !== []) {
return $result;
}
}
}
return $result;
}
print_r(getSlugData('testirovanie-ge', $tree));
print_r(getSlugData('texnologiya-kol', $tree));
print_r(getSlugData('nothing-here', $tree));
die();
In the recursive function, you must not break the loop early unless you find your slug match. If a non-slug-match has a child element, you must iterate it and potentially pass up a match in subsequent recursive calls.
Code: (Demo)
function getSlugData(string $slug, array $data): array
{
foreach ($data as $row) {
if ($row['slug'] === $slug) {
return $row;
}
if (isset($row['child'])) {
$deeper = getSlugData($slug, $row['child']);
if ($deeper) {
return $deeper;
}
}
}
return [];
}
P.s. you aren't calling a class method, so $this-> is inappropriate.
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]
How to refactor this snippet of code, to reduce indentation level by one?
I just wonder is it possible in PHP to write this code in a diffrent way, with just one level of indentation.
The code:
private function isArrayMatchingCriteria(array $array) {
foreach($array as $element) {
if (! $this->isElementMatchingCriteria($element) {
return false;
}
}
return true;
}
Please take into consideration, that:
this code doesn't always iterate over all array elements - so combination of count + array_filter / array_map isn't the same
it is easy to do by introducing a dedicated object attribute serving as a flag, but I'm looking for a way without introducing new attributes
If you're just looking to remove indentation, you could use:
private function isArrayMatchingCriteria(array $array) {
foreach($array as $element) {
if (!$this->isElementMatchingCriteria($element)) return false;
}
return true;
}
Use array_map, something like this:
class MyClass
{
private function isElementMatchingCriteria( $element )
{
// DUMMY, replace with actual code
if ( $element == "foo" || $element == "bar" ) {
return true;
} else {
return false;
}
} // end is Element Matching Criteria
public function isArrayMatchingCriteria(array $array)
{
$results = array_map( array( $this, "isElementMatchingCriteria"), $array );
$isMatch = true;
foreach ( $results as $result ) {
$isMatch = $isMatch && $result;
} // end foreach
return $isMatch;
} // end function isArrayMatchingCriteria
} // end MyClass
$myClass = new MyClass();
$array = array( "foo", "bar", "baz" );
$result = $myClass->isArrayMatchingCriteria( $array );
print_r( $result );
Here is what I want to do:
$newArray = array();
foreach($student as $s){
$newArray[$s->id][$s->grade] = $s;
}
I want to sort the students by their grades (more of a group than a sort) but I just want the grades to be sorted not the id. I could have don't this:
$newArray[$s->id] = $s->grade
asort($newArray)
but I need the remaining data in $s. Also, there is huge chunk of data associated with each student which I want to maintain.
How can I achieve such a sorting?
Edit:
Sine you're working in a framework, best declare your sort callback as a member function (inside the same class as where you'll be needing it, of course):
private function sortCB(array $a, array $b)
{//the array type hinting in arguments is optional
$i = array_keys($a);//but highly recommended
$j = array_keys($b);
if (end($i) === end($j))
{
return 0;
}
//replace '>' with '<' if you want to sort descending
return (end($i) > end($j) ? 1 : -1);//this is ascending
}
Then, in the method where the actual sorting is needed:
uasort($theArray,array($this,'sortCB'));
For more examples, see the docs. I've added a full class example at the end of this (bulky) answer
I've tried this on writecodeonline, which isn't all too good at this kind of stuff, but this did work:
$foo = array_fill_keys(array('foo','bar','q','Bond'),array());
$i = '256';
foreach($foo as $k=>$v)
{
$foo[$k][$i] = $k;
$i = (string)((int)$i%2 === 0 ? ((int)$i/2)+1 : (int)$i*3);
}
function sortCB($a,$b)
{
$i = array_keys($a);
$j = array_keys($b);
if (end($i) === end($j))
{
return 0;
}
return (end($i) > end($j) ? 1 : -1);
}
uasort($foo,'sortCB');
var_dump($foo);
But since you're using a framework, you might do well declaring that function as a member function private function sortCB(array $a,array $b), and use it like so:
uasort($foo,array($this, 'sortCB'));
There might be some more info on how best to use this callback function in a class context here
Full example + usage (tested and working):
class test
{
public $foo = null;
public function __construct()
{
$this->foo = array_fill_keys(array('foo','bar','q','Bond'),array());
$i = '256';
foreach($this->foo as $k=>$v)
{
$this->foo[$k][$i] = $k;
$i = (string)((int)$i%2 === 0 ? ((int)$i/2)+1 : (int)$i*3);
}
}
private function sortCB($a,$b)
{
$i = array_keys($a);
$j = array_keys($b);
if (end($i) === end($j))
{
return 0;
}
return (end($i) > end($j) ? 1 : -1);
}
public function sortFoo()
{
uasort($this->foo,array($this,'sortCB'));
print_r($this->foo);
return $this->foo;
}
}
$bar = new test();
$arr = $bar->sortFoo();
You can do something like:
foreach($student as $s){
$newArray[$s->id] = $s;
}
usort($newArray, function ($a, $b) { return $a->grade - $b->grade; });
Edit
For later versions that don't support anonymous functions you can define comparison function first:
function sortByGrade($a, $b)
{
return $a->grade - $b->grade;
}
usort($newArray, 'sortByGrade');
But if you get this data from db it would be easier to order it in your sql query. If you use ORM you can use its associated method.
I have this anonymous function $build_tree within another function that works fine in PHP 5.3
function nest_list($list) {
$index = array();
index_nodes($list, $index);
$build_tree = function(&$value, $key) use ($index, &$updated) {
if(array_key_exists($key, $index)) {
$value = $index[$key];
$updated = true;
todel($key); }
};
do {
$updated = false;
array_walk_recursive($list, $build_tree);
} while($updated);
return $list;
}
function index_nodes($nodes, &$index) {
foreach($nodes as $key => $value) {
if ($value) {
$index[$key] = $value;
index_nodes($value, $index);
}
}
}
How can I convert this into PHP 5.2 compatible code?
Generally, you could do this using an object's method (callbacks can be either a function, or an object's method; the latter allows you to maintain state). Something like this (not tested):
class BuildTree {
public $index, $updated = false;
public function __construct($index) {
$this->index = $index;
}
function foo(&$value, $key) {
if(array_key_exists($key, $this->index)) {
$value = $this-.index[$key];
$this->updated = true;
todel($key); }
}
}
do {
$build_tree_obj = new BuildTree($index);
array_walk_recursive($list, array($build_tree_obj, 'foo'));
} while($build_tree_obj->updated);
However, array_walk_recursive has a special feature that allows us to pass a third argument, which is a value that will be passed into every call of the function. Although the value is passed by value, we can cleverly use objects (reference types in PHP 5) to maintain state (from How to "flatten" a multi-dimensional array to simple one in PHP?):
$build_tree = create_function('&$value, $key, $obj', '
if(array_key_exists($key, $index)) {
$value = $index[$key];
$updated = true;
todel($key); }
');
do {
$obj = (object)array('updated' => false);
array_walk_recursive($list, $build_tree, $obj);
} while($obj->updated);
I don't think this is possible without changing the way the function is called, because there is no mechanism in PHP 5.3 for a lambda function to change a variable from the scope it is called in (in this case $updated).
You could return $updated like this:
$build_tree = create_function('&$value,$key,$updated','
$index = '.var_export($index).';
if(array_key_exists($key, $index)) {
$value = $index[$key];
$updated = true;
todel($key); }
return $updated;
');
but then you have to call it like this:
$updated = $build_tree('the value','the key',$updated);