I am trying to remove an element from a multidimentional array in PHP. Here is the code :
<?php
$tset = "ST3";
$gettc = "gettingtc1";
$getbid = "gettingbid1";
$getresultsid = "gettingresid1";
$users[$tset] = array();
$users[$tset][] = array( "testcase"=>"$gettc",
"buildid"=>"$getbid",
"resultsid"=>"$getresultsid"
);
$tset = "TEJ";
$gettc = "ggettingtc2";
$getbid = "gettingbid2";
$getresultsid = "gettingresid2";
$users[$tset][] = array( "testcase"=>"$gettc",
"buildid"=>"$getbid",
"resultsid"=>"$getresultsid"
);
$tset = "ST3";
$gettc = "ggettingtc12";
$getbid = "gettingbid13";
$getresultsid = "gettigresid14";
$users[$tset][] = array( "testcase"=>"$gettc",
"buildid"=>"$getbid",
"resultsid"=>"$getresultsid"
);
foreach ($users as $val => $yy)
{
echo "For $val the array is :";
foreach($yy as $uy)
{
echo $uy['testcase'].$uy['buildid'].$uy['resultsid'];
}
echo '<br>';
}
$ser = "gettingresid1";
$to = array_searchRecursive($ser,$users);
if($to <> 0)
{
print_r($to);
}
else
{
echo "not";
}
function array_searchRecursive( $needle, $haystack, $strict=true, $path=array() )
{
if( !is_array($haystack) ) {
return false;
}
foreach( $haystack as $key => $val ) {
if( is_array($val) && $subPath = array_searchRecursive($needle, $val, $strict, $path) ) {
$path = array_merge($path, array($key), $subPath);
return $path;
} elseif( (!$strict && $val == $needle) || ($strict && $val === $needle) ) {
$path[] = $key;
return $path;
}
}
return false;
}
?>
Where I am stuck is : $to holds the array that has my search element. But the results $to is holding should be removed from the original array $users.
Any help.
Thanks.
I think you want to use unset()
//assuming $to contains the key in $users that needs to be removed
//from the array
unset($users[$to]);
But as $to contains an array of keys to the element rather than a single key to the element, you would need to write your own functions to do what you want.
The function that does what you want is below, and will remove the element of the given array which has the address in $keys.
/**
* Removes the element with the keys in the array $keys
*
* #param haystack the array that contains the keys and values
* #param keys the array of keys that define the element to remove
* #return the new array
*/
function array_remove_bykeys( array $haystack, array $keys ){
//check if element couldn't be found
if ( empty($keys) ){
return $haystack;
}
$key = array_shift($keys);
if ( is_array($haystack[$key]) ){
$haystack[$key] = array_remove_bykeys($haystack[$key], $keys);
}else if ( isset($haystack[$key]) ){
unset($haystack[$key]);
}
return $haystack;
}
The other method would be to delete all the keys with the value you were looking for.
/**
* Removes all elements from that array that has the value $needle
* #param $haystack the origanal array
* #param $needle the value to search for
* #return a new array with the value removed
*/
function array_remove_recursive( array $haystack, $needle ){
foreach( $haystack as $key => $value ) {
if( is_array($value) ) {
$haystack[$key] = array_remove_recursive($value, $needle);
} else if ( $value === $needle ){
unset($haystack[$key]);
}
}
return $haystack;
}
For completeness (Although defiantly not recommended), here is a eval version:
$keys = array(...);//an array of keys
//$arr is the variable name that contains the value that you want to remove
eval ('unset($arr[\''.implode('\'][\'', $keys).'\']);');
Pass $users to array_searchRecursive by reference (add '&' to $haystack):
function array_searchRecursive( $needle, &$haystack, $strict=true, $path=array()
and then in array_searchRecursive, just before each return statement:
unset($haystack[$key]);
Related
I need PHP function to compare order of elements in two arrays. First array is standard which saves correct order, the second is array to compare.
Elements in array to compare can be repeated.
Array to compare can contain not all elements that are in etalon array.
Example:
<?php
// Standard
$standard = array(
'taxonomy',
'post_meta',
'author',
'date',
'post_meta_num'
);
// Valid order
$valid_order = array(
'taxonomy',
'taxonomy',
'post_meta',
'date'
);
// Invalid order, 'author' is before 'post_meta'
$invalid_order = array(
'taxonomy',
'author',
'author',
'post_meta'
);
?>
I tried to find something on StackOverflow, but already existing answers are not compatible with my task. This function works correctly only if array to compare contains all elements from standard.
<?php
function compare( $standard, $to_compare ){
if( ! is_array( $standard ) || ! is_array( $to_compare ) ){
return false;
}
$i = 0;
foreach ( $to_compare as $value ) {
if( $value === $standard[$i] ){
$i++;
}
}
return ( $i == count( $standard ) );
}
?>
In the end the function should return true if order in standard and array to compare is equal and false if is not equal.
Thank you.
Assuming that there can be no boolean values in the arrays, you can do the following:
<?php
/**
* #param string[] $standard
* #param string[] $to_compare
* #return bool
*/
function compare(array $standard, array $to_compare) : bool {
$standard_item = reset($standard);
foreach ($to_compare as $item) {
// Skip standard items assuming they are all optional
while ($item !== $standard_item) {
$standard_item = next($standard);
if ($standard_item === false) {
return false;
}
}
if ($standard_item === false || $item !== $standard_item) {
return false;
}
}
return true;
}
If you want to support false values in the standard array, the code above might be modified so that the the items are referred to by indices, e.g. $standard[$i]. But this approach has its drawback as well -- the keys must be numeric and sequential. For a more generic solution I would probably use an iterator such as ArrayIterator.
you want to use usort()
https://www.php.net/manual/fr/function.usort.php
it would look something like
function custom_sort(&$my_array) {
return usort($my_array, function ($a, $b) {
global $etalon;
$a_key = array_search($a, $etalon);
$b_key = array_search($b, $etalon);
if (($a_key === FALSE) || ($b_key === FALSE) || ($a_key == $b_key)) {
return 0;
}
($a_key < $b_key) ? -1 : 1;
});
}
custom_sort($valid_order);
custom_sort($invalid_order)
Another solution that can help :
// Etalon
$etalon = array(
'taxonomy',
'post_meta',
'author',
'date',
'post_meta_num'
);
// Valid order
$valid_order = array(
'taxonomy',
'taxonomy',
'post_meta',
'date'
);
// Invalid order, 'author' is before 'post_meta'
$invalid_order = array(
'taxonomy',
'author',
'author',
'post_meta'
);
function checkOrder($array , $etalon)
{
$array = array_values(array_unique($array));
$array = array_intersect($array, $etalon );
foreach($array as $key => $value){
if(!in_array($array[$key],$etalon) || array_search($array[$key], $etalon)<$key){
return false;
}
}
return true;
}
var_dump(checkOrder($valid_order,$etalon)); // true
var_dump(checkOrder($invalid_order,$etalon)); // false
A possible solution which I find easy to follow:
class Transition
{
private string $fromValue;
private array $toValues;
public function __construct(string $fromValue, array $toValues)
{
$this->fromValue = $fromValue;
$this->toValues = $toValues;
}
public function getFromValue(): string
{
return $this->fromValue;
}
public function getToValues(): array
{
return $this->toValues;
}
}
function prepareTransitionsMap(array $orderDefinitions): array
{
$transitions = [];
$definitionsCount = count($orderDefinitions);
foreach ($orderDefinitions as $i => $fromValue) {
$toValues = [];
for ($j = $i; $j < $definitionsCount; ++$j) {
$toValues[] = $orderDefinitions[$j];
}
$transitions[$fromValue] = new Transition($fromValue, $toValues);
}
return $transitions;
}
function isArrayOrderValid(array $orderDefinitions, array $valuesToCheck): bool
{
$valuesCount = count($valuesToCheck);
if ($valuesCount === 0) {
return true;
}
$definitionsCount = count($orderDefinitions);
if ($definitionsCount === 0) {
return false;
}
$transitionsMap = prepareTransitionsMap($orderDefinitions);
foreach ($valuesToCheck as $i => $iValue) {
$valueToCheck = $iValue;
// value is no defined at all
if (!array_key_exists($valueToCheck, $transitionsMap)) {
return false;
}
// value is the last in the array
if (!array_key_exists($i + 1, $valuesToCheck)) {
return true;
}
$nextValue = $valuesToCheck[$i + 1];
$transition = $transitionsMap[$valueToCheck];
if (!in_array($nextValue, $transition->getToValues(), true)) {
return false;
}
}
return true;
}
isArrayOrderValid($standard, $valid_order); // true
isArrayOrderValid($standard, $invalid_order); // false
I have index ponter, for example 5-1-key2-3.
And my array has its address:
array(
'5'=>array(
'1'=>array(
'key2'=>array(
'3'=>'here it is - 5-1-key2-3 key address'
)
)
)
)
which equals to
$arr[5][1][key2][3]='here it is - 5-1-key2-3 key address';
I know I can build the recursive function to access this value.
But I'm curious is it possible to achieve this without recursion and building user's functions and/or loops.
Probably it can be done with variable variables feature in php.
You can use this code
$keys = explode('-', '5-1-key2-3');
// start from the root of the array and progress through the elements
$temp = $arr;
foreach ($keys as $key_value)
{
$temp = $temp[$key_value];
}
// this will give you $arr["5"]["1"]["key2"]["3"] element value
echo $temp;
modifications after I got better understanding of the question I think you can do it with eval:
<?php
function getArrValuesFromString($string, $arr) {
$stringArr = '$arr[\'' . str_replace('-', "']['", $string) . '\']';
eval("\$t = " . $stringArr . ";");
return $t;
}
$arr[5][1]['key2'][3] = '1here it is - 5-1-key2-3 key address';
$string = '5-1-key2-3';
echo getArrValuesFromString($string, $arr); //1here it is - 5-1-key2-3 key address
EDIT :
Here is a way I deprecate so much, because of security, but if you are sure of what you are doing :
$key = 'a-b-c-d';
$array = <<your array>>;
$keys = explode('-', $key);
// we can surely do something better like checking for each one if its a string or int then adding or not the `'`
$final_key = "['".implode("']['", $keys)."']";
$result = eval("return \$array{$final_key};");
There is a class I wrote inspired from something I read on the web don't really remember where but anyway, this can helps you :
/**
* Class MultidimensionalHelper
*
* Some help about multidimensional arrays
* like dynamic array_key_exists, set, and get functions
*
* #package Core\Utils\Arrays
*/
class MultidimensionalHelper
{
protected $keySeparator = '.';
/**
* #return string
*/
public function keySeparator()
{
return $this->keySeparator;
}
/**
* #param string $keySeparator
*/
public function setKeySeparator($keySeparator)
{
$this->keySeparator = $keySeparator;
}
/**
* Multidimensional array dynamic array_key_exists
*
* #param $key String Needle
* #param $array Array Haystack
* #return bool True if found, false either
*/
public function exists($key, $array)
{
$keys = explode($this->keySeparator(), $key);
$tmp = $array;
foreach($keys as $k)
{
if(!array_key_exists($k, $tmp))
{
return false;
}
$tmp = $tmp[$k];
}
return true;
}
/**
* Multidimensional array dynamic getter
*
*
* #param $key String Needle
* #param $array Array Haystack
* #return mixed Null if key not exists or the content of the key
*/
public function get($key, $array)
{
$keys = explode($this->keySeparator(), $key);
$lkey = array_pop($keys);
$tmp = $array;
foreach($keys as $k)
{
if(!isset($tmp[$k]))
{
return null;
}
$tmp = $tmp[$k];
}
return $tmp[$lkey];
}
/**
* Multidimensional array dynamic setter
*
* #param String $key
* #param Mixed $value
* #param Array $array Array to modify
* #param Bool $return
* #return Array If $return is set to TRUE (bool), this function
* returns the modified array instead of directly modifying it.
*/
public function set($key, $value, &$array)
{
$keys = explode($this->keySeparator(), $key);
$lkey = array_pop($keys);
$tmp = &$array;
foreach($keys as $k)
{
if(!isset($tmp[$k]) || !is_array($tmp[$k]))
{
$tmp[$k] = array();
}
$tmp = &$tmp[$k];
}
$tmp[$lkey] = $value;
unset($tmp);
}
}
Then use :
$MDH = new MultidimensionalHelper();
$MDH->setKeySeparator('-');
$arr = [
'a' => [
'b' => [
'c' => 'good value',
],
'c' => 'wrong value',
],
'b' => [
'c' => 'wrong value',
]
];
$key = 'a-b-c';
$val = $MDH->get($key, $arr);
var_dump($val);
Here is the content of the get function if you don't find it in Class code :
public function get($key, $array)
{
$keys = explode($this->keySeparator(), $key);
$lkey = array_pop($keys);
$tmp = $array;
foreach($keys as $k)
{
if(!isset($tmp[$k]))
{
return null;
}
$tmp = $tmp[$k];
}
return $tmp[$lkey];
}
For a project we have created a recursive direcory iterator inside a class
the class is as follows
class Helpers {
public static function fs_to_array($directory){
$iritator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($directory), \RecursiveIteratorIterator::CHILD_FIRST);
$array_result = array();
foreach ($iritator as $splFileInfo) {
$fn = $splFileInfo->getFilename();
if ($splFileInfo->isDir()){
if ($fn == '..' || $fn == '.' ){
continue;
}
$rec_path = array($fn => array());
}else{
continue;
}
for ($depth = $iritator->getDepth() - 1; $depth >= 0; $depth--) {
$rec_path = array($iritator->getSubIterator($depth)->current()->getFilename() => $rec_path);
}
$array_result = array_merge_recursive($array_result, $rec_path);
}
return $array_result;
}
}
it takes a directory as an argument and returns the dir structure in the following form
Array
(
[dir3] => Array
(
[dir_in_dir3] => Array
(
)
)
[dir1] => Array
(
[dir_in_dir1] => Array
(
)
)
[dir2] => Array
(
)
)
I would like these to be allphavetically sorted.
How Can i make this with the iterator?
Thanks everybody in advance!
An iterator can't be sorted directly and the directory iterators also have not support for sorting of the underlying data. But you can convert the RecursiveIteratorIterator to an array with iterator_to_array() and then sort the array with usort() and a custom callback function using getPathName() on the elements. You could also use a CallbackFilterIterator() before the conversion to reduce the size of your array.
EDIT example:
$directory = '...';
$it = new CallbackFilterIterator(
new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($directory), RecursiveIteratorIterator::CHILD_FIRST
), function ($entry) {
// filtering unwanted elements to keep array small
$fn = $entry->getFilename();
if (!$entry->isDir()) {
return false;
} else if ($fn == '..' || $fn == '.') {
return false;
} else {
return true;
}
}
);
$array = iterator_to_array($it);
// sorting entries
uasort($array, function ($a, $b) {
return strcmp($a->getPathname(), $b->getPathname());
});
// do whatever you want - can be uses just like the RecursiveIteratorIterator before
foreach ($array as $v) {
var_dump($v->getPathname());
}
I'm trying to write a recursive array iterator function in which the function will return a result set of all sets that are specified by '$needle'. Where $needle = key
Here is my function:
function recursive($needle, $array, $holder = array()) {
foreach ($array as $key => $value) {
if (gettype($value) == 'array') {
if ($key != $needle) {
recursive($needle, $value);
} elseif ($key == $needle) {
if (!empty($value)) {
array_push($holder, $value);
}
}
}
}
return $holder;
}
But I'm not getting all the results back and instead get a few empty results, if I don't specify the !empty($value), although the input array does not have any empty sets. What am I doing wrong?
You don't need to reinvent the wheel since PHP has standard Recursive Iterator API:
//$array is your multi-dimensional array
$result = [];
$search = 'foo';
$iterator = new RecursiveIteratorIterator(
new RecursiveArrayIterator(
$array,
RecursiveArrayIterator::CHILD_ARRAYS_ONLY
)
);
foreach($iterator as $key=>$value)
{
if($search==$key && $value!=='')
{
$result[] = $value;
}
}
-note, that, since you're searching for value by key - in common case $value will hold entire subsection.
If you want to do this in your own recursive function, here's one:
function recursive($needle, $array, $holder = [])
{
$holder = [];
foreach($array as $key=>$value)
{
if($key===$needle && $value!=='')
{
$holder = array_merge($holder, [$value]);
}
if(is_array($value))
{
$holder = array_merge($holder, recursive($needle, $value, $holder));
}
}
return $holder;
}
More fine-grained control is perhaps possible with true (tm) recursive array traversal via RecursiveIterator interface and some key filters and array conversion functions:
$needle = '0';
$array = [[1]];
$it = new KeyFilter(
new RecursiveIteratorIterator(
new MyRecursiveArrayIterator($array)
, RecursiveIteratorIterator::SELF_FIRST
)
, $needle
);
$result = iterator_to_array($it, FALSE);
var_dump($result);
Providing an exemplary result as:
array(2) {
[0] =>
array(1) {
[0] =>
int(1)
}
[1] =>
int(1)
}
Full code example (Demo):
<?php
/**
* #link http://stackoverflow.com/q/19709410/367456
*/
Class MyRecursiveArrayIterator extends ArrayIterator implements RecursiveIterator
{
public function hasChildren()
{
$current = $this->current();
return is_array($current) && count($current);
}
public function getChildren()
{
return new self($this->current());
}
}
class KeyFilter extends RegexIterator
{
public function __construct(Iterator $iterator, $key)
{
parent::__construct(
$iterator, '/' . preg_quote($key) . '/', NULL, RegexIterator::USE_KEY
);
}
}
$needle = '0';
$array = [[1]];
$it = new KeyFilter(
new RecursiveIteratorIterator(
new MyRecursiveArrayIterator($array)
, RecursiveIteratorIterator::SELF_FIRST
)
, $needle
);
$result = iterator_to_array($it, FALSE);
var_dump($result);
A tiny modification of your construction:
$holder = recursive($needle, $value, $holder);
Ay?
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'
)
*/