Is there a php function like python's zip? - php

Python has a nice zip() function. Is there a PHP equivalent?

As long as all the arrays are the same length, you can use array_map with null as the first argument.
array_map(null, $a, $b, $c, ...);
If some of the arrays are shorter, they will be padded with nulls to the length of the longest, unlike python where the returned result is the length of the shortest array.

array_combine comes close.
Otherwise nothing like coding it yourself:
function array_zip($a1, $a2) {
for($i = 0; $i < min(length($a1), length($a2)); $i++) {
$out[$i] = [$a1[$i], $a2[$i]];
}
return $out;
}

Try this function to create an array of arrays similar to Python’s zip:
function zip() {
$args = func_get_args();
$zipped = array();
$n = count($args);
for ($i=0; $i<$n; ++$i) {
reset($args[$i]);
}
while ($n) {
$tmp = array();
for ($i=0; $i<$n; ++$i) {
if (key($args[$i]) === null) {
break 2;
}
$tmp[] = current($args[$i]);
next($args[$i]);
}
$zipped[] = $tmp;
}
return $zipped;
}
You can pass this function as many array as you want with as many items as you want.

This works exactly as Python's zip() function, and is compatible also with PHP < 5.3:
function zip() {
$params = func_get_args();
if (count($params) === 1){ // this case could be probably cleaner
// single iterable passed
$result = array();
foreach ($params[0] as $item){
$result[] = array($item);
};
return $result;
};
$result = call_user_func_array('array_map',array_merge(array(null),$params));
$length = min(array_map('count', $params));
return array_slice($result, 0, $length);
};
It merges the arrays in the manner Python's zip() does and does not return elements found after reaching the end of the shortest array.
The following:
zip(array(1,2,3,4,5),array('a','b'));
gives the following result:
array(array(1,'a'), array(2,'b'))
and the following:
zip(array(1,2,3,4,5),array('a','b'),array('x','y','z'));
gives the following result:
array(array(1,'a','x'), array(2,'b','y'))
Check this demonstration for a proof of the above.
EDIT: Added support for receiving single argument (array_map behaves differently in that case; thanks Josiah).

Solution
The solution matching zip() very closely, and using builtin PHP functions at the same time, is:
array_slice(
array_map(null, $a, $b, $c), // zips values
0, // begins selection before first element
min(array_map('count', array($a, $b, $c))) // ends after shortest ends
);
Why not simple array_map(null, $a, $b, $c) call?
As I already mentioned in my comment, I tend to favor nabnabit's solution (array_map(null, $a, $b, ...)), but in a slightly modified way (shown above).
In general this:
array_map(null, $a, $b, $c);
is counterpart for Python's:
itertools.izip_longest(a, b, c, fillvalue=None)
(wrap it in list() if you want list instead of iterator). Because of this, it does not exactly fit the requirement to mimic zip()'s behaviour (unless all the arrays have the same length).

You can find zip as well as other Python functions in Non-standard PHP library. Including operator module and defaultarray.
use function nspl\a\zip;
$pairs = zip([1, 2, 3], ['a', 'b', 'c']);

I wrote a zip() functions for my PHP implementation of enum.
The code has been modified to allow for a Python-style zip() as well as Ruby-style. The difference is explained in the comments:
/*
* This is a Python/Ruby style zip()
*
* zip(array $a1, array $a2, ... array $an, [bool $python=true])
*
* The last argument is an optional bool that determines the how the function
* handles when the array arguments are different in length
*
* By default, it does it the Python way, that is, the returned array will
* be truncated to the length of the shortest argument
*
* If set to FALSE, it does it the Ruby way, and NULL values are used to
* fill the undefined entries
*
*/
function zip() {
$args = func_get_args();
$ruby = array_pop($args);
if (is_array($ruby))
$args[] = $ruby;
$counts = array_map('count', $args);
$count = ($ruby) ? min($counts) : max($counts);
$zipped = array();
for ($i = 0; $i < $count; $i++) {
for ($j = 0; $j < count($args); $j++) {
$val = (isset($args[$j][$i])) ? $args[$j][$i] : null;
$zipped[$i][$j] = $val;
}
}
return $zipped;
}
Example:
$pythonzip = zip(array(1,2,3), array(4,5), array(6,7,8));
$rubyzip = zip(array(1,2,3), array(4,5), array(6,7,8), false);
echo '<pre>';
print_r($pythonzip);
print_r($rubyzip);
echo '<pre>';

// create
$a = array("a", "c", "e", "g", "h", "i");
$b = array("b", "d", "f");
$zip_array = array();
// get length of the longest array
$count = count(max($a, $b));
// zip arrays
for($n=0;$n<$count;$n++){
if (array_key_exists($n,$a)){
$zip_array[] = $a[$n];
}
if (array_key_exists($n,$b)){
$zip_array[] = $b[$n];
}
}
// test result
echo '<pre>'; print_r($zip_array); echo '<pre>';

function zip() {
$zip = [];
$arrays = func_get_args();
if ($arrays) {
$count = min(array_map('count', $arrays));
for ($i = 0; $i < $count; $i++) {
foreach ($arrays as $array) {
$zip[$i][] = $array[$i];
}
}
}
return $zip;
}

This works like in Python
function zip(...$arrays) {
return array_filter(
array_map(null, ...(count($arrays) > 1 ? $arrays : array_merge($arrays, [[]]))),
fn($z) => count($z) === count(array_filter($z)) || count($arrays) === 1
);
}

/**
* Takes an arbitrary number of arrays and "zips" them together into a single
* array, taking one value from each array and putting them into a sub-array,
* before moving onto the next.
*
* If arrays are uneven lengths, will stop at the length of the shortest array.
*/
function array_zip(...$arrays) {
$result = [];
$args = array_map('array_values',$arrays);
$min = min(array_map('count',$args));
for($i=0; $i<$min; ++$i) {
$result[$i] = [];
foreach($args as $j=>$arr) {
$result[$i][$j] = $arr[$i];
}
}
return $result;
}
Usage:
print_r(array_zip(['a','b','c'],[1,2,3],['x','y']));
Output:
Array
(
[0] => Array
(
[0] => a
[1] => 1
[2] => x
)
[1] => Array
(
[0] => b
[1] => 2
[2] => y
)
)

To overcome the issues with passing a single array to map_array, you can pass this function...unfortunately you can't pass "array" as it's not a real function but a builtin thingy.
function make_array() { return func_get_args(); }

Dedicated to those that feel like it should be related to array_combine:
function array_zip($a, $b)
{
$b = array_combine(
$a,
$b
);
$a = array_combine(
$a,
$a
);
return array_values(array_merge_recursive($a,$b));
}

you can see array_map method:
$arr1 = ['get', 'method'];
$arr2 = ['post'];
$ret = array_map(null, $arr1, $arr2);
output:
[['get', 'method'], ['post', null]]
php function.array-map

Related

PHP get random item from API [duplicate]

I have an array called $ran = array(1,2,3,4);
I need to get a random value out of this array and store it in a variable, how can I do this?
You can also do just:
$k = array_rand($array);
$v = $array[$k];
This is the way to do it when you have an associative array.
PHP provides a function just for that: array_rand()
http://php.net/manual/en/function.array-rand.php
$ran = array(1,2,3,4);
$randomElement = $ran[array_rand($ran, 1)];
$value = $array[array_rand($array)];
You can use mt_rand()
$random = $ran[mt_rand(0, count($ran) - 1)];
This comes in handy as a function as well if you need the value
function random_value($array, $default=null)
{
$k = mt_rand(0, count($array) - 1);
return isset($array[$k])? $array[$k]: $default;
}
You could use the array_rand function to select a random key from your array like below.
$array = array("one", "two", "three", "four", "five", "six");
echo $array[array_rand($array, 1)];
or you could use the rand and count functions to select a random index.
$array = array("one", "two", "three", "four", "five", "six");
echo $array[rand(0, count($array) - 1)];
Derived from Laravel Collection::random():
function array_random($array, $amount = 1)
{
$keys = array_rand($array, $amount);
if ($amount == 1) {
return $array[$keys];
}
$results = [];
foreach ($keys as $key) {
$results[] = $array[$key];
}
return $results;
}
Usage:
$items = ['foo', 'bar', 'baz', 'lorem'=>'ipsum'];
array_random($items); // 'bar'
array_random($items, 2); // ['foo', 'ipsum']
A few notes:
$amount has to be less than or equal to count($array).
array_rand() doesn't shuffle keys (since PHP 5.2.10, see 48224), so your picked items will always be in original order. Use shuffle() afterwards if needed.
Documentation: array_rand(), shuffle()
edit: The Laravel function has noticeably grown since then, see Laravel 5.4's Arr::random(). Here is something more elaborate, derived from the grown-up Laravel function:
function array_random($array, $number = null)
{
$requested = ($number === null) ? 1 : $number;
$count = count($array);
if ($requested > $count) {
throw new \RangeException(
"You requested {$requested} items, but there are only {$count} items available."
);
}
if ($number === null) {
return $array[array_rand($array)];
}
if ((int) $number === 0) {
return [];
}
$keys = (array) array_rand($array, $number);
$results = [];
foreach ($keys as $key) {
$results[] = $array[$key];
}
return $results;
}
A few highlights:
Throw exception if there are not enough items available
array_random($array, 1) returns an array of one item (#19826)
Support value "0" for the number of items (#20439)
The array_rand function seems to have an uneven distribution on large arrays, not every array item is equally likely to get picked. Using shuffle on the array and then taking the first element doesn't have this problem:
$myArray = array(1, 2, 3, 4, 5);
// Random shuffle
shuffle($myArray);
// First element is random now
$randomValue = $myArray[0];
Another approach through flipping array to get direct value.
Snippet
$array = [ 'Name1' => 'John', 'Name2' => 'Jane', 'Name3' => 'Jonny' ];
$val = array_rand(array_flip($array));
array_rand return key not value. So, we're flipping value as key.
Note:
PHP key alway be an unique key, so when array is flipped, duplicate value as a key will be overwritten.
$rand = rand(1,4);
or, for arrays specifically:
$array = array('a value', 'another value', 'just some value', 'not some value');
$rand = $array[ rand(0, count($array)-1) ];
On-liner:
echo $array[array_rand($array,1)]
In my case, I have to get 2 values what are objects. I share this simple solution.
$ran = array("a","b","c","d");
$ranval = array_map(function($i) use($ran){return $ran[$i];},array_rand($ran,2));
One line: $ran[rand(0, count($ran) - 1)]
I needed one line version for short array:
($array = [1, 2, 3, 4])[mt_rand(0, count($array) - 1)]
or if array is fixed:
[1, 2, 3, 4][mt_rand(0, 3]
Does your selection have any security implications? If so, use random_int() and array_keys(). (random_bytes() is PHP 7 only, but there is a polyfill for PHP 5).
function random_index(array $source)
{
$max = count($source) - 1;
$r = random_int(0, $max);
$k = array_keys($source);
return $k[$r];
}
Usage:
$array = [
'apple' => 1234,
'boy' => 2345,
'cat' => 3456,
'dog' => 4567,
'echo' => 5678,
'fortune' => 6789
];
$i = random_index($array);
var_dump([$i, $array[$i]]);
Demo: https://3v4l.org/1joB1
Use rand() to get random number to echo random key. In ex: 0 - 3
$ran = array(1,2,3,4);
echo $ran[rand(0,3)];
This will work nicely with in-line arrays. Plus, I think things are tidier and more reusable when wrapped up in a function.
function array_rand_value($a) {
return $a[array_rand($a)];
}
Usage:
array_rand_value(array("a", "b", "c", "d"));
On PHP < 7.1.0, array_rand() uses rand(), so you wouldn't want to this function for anything related to security or cryptography. On PHP 7.1.0+, use this function without concern since rand() has been aliased to mt_rand().
I'm basing my answer off of #ÓlafurWaage's function. I tried to use it but was running into reference issues when I had tried to modify the return object. I updated his function to pass and return by reference. The new function is:
function &random_value(&$array, $default=null)
{
$k = mt_rand(0, count($array) - 1);
if (isset($array[$k])) {
return $array[$k];
} else {
return $default;
}
}
For more context, see my question over at Passing/Returning references to object + changing object is not working
Get random values from an array.
function random($array)
{
/// Determine array is associative or not
$keys = array_keys($array);
$givenArrIsAssoc = array_keys($keys) !== $keys;
/// if array is not associative then return random element
if(!$givenArrIsAssoc){
return $array[array_rand($array)];
}
/// If array is associative then
$keys = array_rand($array, $number);
$results = [];
foreach ((array) $keys as $key) {
$results[] = $array[$key];
}
return $results;
}
mt_srand usage example
if one needs to pick a random row from a text but same all the time based on something
$rows = array_map('trim', explode("\n", $text));
mt_srand($item_id);
$row = $rows[rand(0, count($rows ) - 1)];
A simple way to getting Randdom value form Array.
$color_array =["red","green","blue","light_orange"];
$color_array[rand(0,3)
now every time you will get different colors from Array.
You get a random number out of an array as follows:
$randomValue = $rand[array_rand($rand,1)];

Pick random array of $mail values in PHPMailer [duplicate]

I have an array called $ran = array(1,2,3,4);
I need to get a random value out of this array and store it in a variable, how can I do this?
You can also do just:
$k = array_rand($array);
$v = $array[$k];
This is the way to do it when you have an associative array.
PHP provides a function just for that: array_rand()
http://php.net/manual/en/function.array-rand.php
$ran = array(1,2,3,4);
$randomElement = $ran[array_rand($ran, 1)];
$value = $array[array_rand($array)];
You can use mt_rand()
$random = $ran[mt_rand(0, count($ran) - 1)];
This comes in handy as a function as well if you need the value
function random_value($array, $default=null)
{
$k = mt_rand(0, count($array) - 1);
return isset($array[$k])? $array[$k]: $default;
}
You could use the array_rand function to select a random key from your array like below.
$array = array("one", "two", "three", "four", "five", "six");
echo $array[array_rand($array, 1)];
or you could use the rand and count functions to select a random index.
$array = array("one", "two", "three", "four", "five", "six");
echo $array[rand(0, count($array) - 1)];
Derived from Laravel Collection::random():
function array_random($array, $amount = 1)
{
$keys = array_rand($array, $amount);
if ($amount == 1) {
return $array[$keys];
}
$results = [];
foreach ($keys as $key) {
$results[] = $array[$key];
}
return $results;
}
Usage:
$items = ['foo', 'bar', 'baz', 'lorem'=>'ipsum'];
array_random($items); // 'bar'
array_random($items, 2); // ['foo', 'ipsum']
A few notes:
$amount has to be less than or equal to count($array).
array_rand() doesn't shuffle keys (since PHP 5.2.10, see 48224), so your picked items will always be in original order. Use shuffle() afterwards if needed.
Documentation: array_rand(), shuffle()
edit: The Laravel function has noticeably grown since then, see Laravel 5.4's Arr::random(). Here is something more elaborate, derived from the grown-up Laravel function:
function array_random($array, $number = null)
{
$requested = ($number === null) ? 1 : $number;
$count = count($array);
if ($requested > $count) {
throw new \RangeException(
"You requested {$requested} items, but there are only {$count} items available."
);
}
if ($number === null) {
return $array[array_rand($array)];
}
if ((int) $number === 0) {
return [];
}
$keys = (array) array_rand($array, $number);
$results = [];
foreach ($keys as $key) {
$results[] = $array[$key];
}
return $results;
}
A few highlights:
Throw exception if there are not enough items available
array_random($array, 1) returns an array of one item (#19826)
Support value "0" for the number of items (#20439)
The array_rand function seems to have an uneven distribution on large arrays, not every array item is equally likely to get picked. Using shuffle on the array and then taking the first element doesn't have this problem:
$myArray = array(1, 2, 3, 4, 5);
// Random shuffle
shuffle($myArray);
// First element is random now
$randomValue = $myArray[0];
Another approach through flipping array to get direct value.
Snippet
$array = [ 'Name1' => 'John', 'Name2' => 'Jane', 'Name3' => 'Jonny' ];
$val = array_rand(array_flip($array));
array_rand return key not value. So, we're flipping value as key.
Note:
PHP key alway be an unique key, so when array is flipped, duplicate value as a key will be overwritten.
$rand = rand(1,4);
or, for arrays specifically:
$array = array('a value', 'another value', 'just some value', 'not some value');
$rand = $array[ rand(0, count($array)-1) ];
On-liner:
echo $array[array_rand($array,1)]
In my case, I have to get 2 values what are objects. I share this simple solution.
$ran = array("a","b","c","d");
$ranval = array_map(function($i) use($ran){return $ran[$i];},array_rand($ran,2));
One line: $ran[rand(0, count($ran) - 1)]
I needed one line version for short array:
($array = [1, 2, 3, 4])[mt_rand(0, count($array) - 1)]
or if array is fixed:
[1, 2, 3, 4][mt_rand(0, 3]
Does your selection have any security implications? If so, use random_int() and array_keys(). (random_bytes() is PHP 7 only, but there is a polyfill for PHP 5).
function random_index(array $source)
{
$max = count($source) - 1;
$r = random_int(0, $max);
$k = array_keys($source);
return $k[$r];
}
Usage:
$array = [
'apple' => 1234,
'boy' => 2345,
'cat' => 3456,
'dog' => 4567,
'echo' => 5678,
'fortune' => 6789
];
$i = random_index($array);
var_dump([$i, $array[$i]]);
Demo: https://3v4l.org/1joB1
Use rand() to get random number to echo random key. In ex: 0 - 3
$ran = array(1,2,3,4);
echo $ran[rand(0,3)];
This will work nicely with in-line arrays. Plus, I think things are tidier and more reusable when wrapped up in a function.
function array_rand_value($a) {
return $a[array_rand($a)];
}
Usage:
array_rand_value(array("a", "b", "c", "d"));
On PHP < 7.1.0, array_rand() uses rand(), so you wouldn't want to this function for anything related to security or cryptography. On PHP 7.1.0+, use this function without concern since rand() has been aliased to mt_rand().
I'm basing my answer off of #ÓlafurWaage's function. I tried to use it but was running into reference issues when I had tried to modify the return object. I updated his function to pass and return by reference. The new function is:
function &random_value(&$array, $default=null)
{
$k = mt_rand(0, count($array) - 1);
if (isset($array[$k])) {
return $array[$k];
} else {
return $default;
}
}
For more context, see my question over at Passing/Returning references to object + changing object is not working
Get random values from an array.
function random($array)
{
/// Determine array is associative or not
$keys = array_keys($array);
$givenArrIsAssoc = array_keys($keys) !== $keys;
/// if array is not associative then return random element
if(!$givenArrIsAssoc){
return $array[array_rand($array)];
}
/// If array is associative then
$keys = array_rand($array, $number);
$results = [];
foreach ((array) $keys as $key) {
$results[] = $array[$key];
}
return $results;
}
mt_srand usage example
if one needs to pick a random row from a text but same all the time based on something
$rows = array_map('trim', explode("\n", $text));
mt_srand($item_id);
$row = $rows[rand(0, count($rows ) - 1)];
A simple way to getting Randdom value form Array.
$color_array =["red","green","blue","light_orange"];
$color_array[rand(0,3)
now every time you will get different colors from Array.
You get a random number out of an array as follows:
$randomValue = $rand[array_rand($rand,1)];

Calculate the intersection of arrays with a threshold in PHP

Let's say I have following arrays:
$a = [1,2,3,4,5];
$b = [1,3,4,5,6];
$c = [1,7,8,9,10];
$d = [1,2,3,4];
The intersection of those would be $result = [1], which is easy enough. But what if I wanted the intersection of those with a minimum threshold of let's say 3? The threshold means I can skip one or more arrays from the intersection, as long as my resulting intersection has at least 3 elements, which in this case might result in:
$result = [1,3,4];
1, 3 and 4 are present in $a, $b and $d, but not in $c which is skipped because of the threshold. Is there an existing PHP class, algorithm or function with which I might accomplish this?
To do that we have to use combinations of an array. I have used combinations algorithm from this great article. Adjusting this algorithm we can write the following class:
class Intersections
{
protected $arrays;
private $arraysSize;
public function __construct($arrays)
{
$this->arrays = $arrays;
$this->arraysSize = count($arrays);
}
public function getByThreshold($threshold)
{
$intersections = $this->getAll();
foreach ($intersections as $intersection) {
if (count($intersection) >= $threshold) {
return $intersection;
}
}
return null;
}
protected $intersections;
public function getAll()
{
if (is_null($this->intersections)) {
$this->generateIntersections();
}
return $this->intersections;
}
private function generateIntersections()
{
$this->generateCombinationsMasks();
$this->generateCombinations();
$combinationSize = $this->arraysSize;
$intersectionSize = 0;
foreach ($this->combinations as $combination) {
$intersection = call_user_func_array('array_intersect', $combination);
if ($combinationSize > count($combination)) {
$combinationSize = count($combination);
$intersectionSize = 0;
}
if (count($intersection) > $intersectionSize) {
$this->intersections[$combinationSize] = $intersection;
$intersectionSize = count($intersection);
}
}
}
private $combinationsMasks;
private function generateCombinationsMasks()
{
$combinationsMasks = [];
$totalNumberOfCombinations = pow(2, $this->arraysSize);
for ($i = $totalNumberOfCombinations - 1; $i > 0; $i--) {
$combinationsMasks[] = str_pad(
decbin($i), $this->arraysSize, '0', STR_PAD_LEFT
);
}
usort($combinationsMasks, function ($a, $b) {
return strcmp(strtr($b, ['']), strtr($a, ['']));
});
$this->combinationsMasks = array_slice(
$combinationsMasks, 0, -$this->arraysSize
);
}
private $combinations;
private function generateCombinations()
{
$this->combinations = array_map(function ($combinationMask) {
return $this->generateCombination($combinationMask);
}, $this->combinationsMasks);
}
private function generateCombination($combinationMask)
{
$combination = [];
foreach (str_split($combinationMask) as $key => $indicator) {
if ($indicator) {
$combination[] = $this->arrays[$key];
}
}
return $combination;
}
}
I have tried to give self-explanatory names to methods. Some chunks of code can be optimized more (for example, I call count function multiple times on same arrays; this was done in order to reduce variables fiddling) for production use.
So basically the logic is pretty simple. We generate all combinations of arrays and sort them decreasingly by the number of used arrays. Then we find the longest intersection for each length of combinations. Actually, this is the hardest part. To get one particular intersection we return first one that matches threshold.
$intersections = new Intersections([$a, $b, $c, $d]);
var_dump($intersections->getAll());
var_dump($intersections->getByThreshold(3));
Here is working demo.
There are other ways to find all combinations, for example, one from "PHP Cookbook". You can choose whatever one you like most.
No build in feature for that. You need to write something short like:
$values = [];
foreach ([$a, $b, $c, $d] as $arr)
foreach ($arr as $value)
$values[$value] = ($values[$value] ?? 0) + 1;
// For threshold of 3
$values = array_keys(array_filter($values, function($a) { return $a >= 3; }));
Note: This requires PHP7 for ?? operator. Otherwise use something like:
$values[$value] = empty($values[$value]) ? 1 : $values[$value] + 1;

PHP Better way to Replace/Match $1

I am trying to replace $1, $2, $3 variables in a URL with another URL.
You can copy paste my example below and see my solution.
But I feel like there is a more elegant way with an array mapping type function or a better preg_replace type of thing. I just need a kick in the right direction, can you help?
<?php
/**
* Key = The DESIRED string
* Value = The ORIGINAL value
*
* Desired Result: project/EXAMPLE/something/OTHER
*/
$data = array(
'project/$1/details/$2' => 'newby/EXAMPLE/something/OTHER'
);
foreach($data as $desiredString => $findMe)
{
/**
* Turn these URI's into arrays
*/
$desiredString = explode('/', $desiredString);
$findMe = explode('/', $findMe);
/**
* Store the array position of the match
*/
$positions = array();
foreach($desiredString as $key => $value) {
/**
* Look for $1, $2, $3, etc..
*/
if (preg_match('#(\$\d)#', $value)) {
$positions[$key] = $value;
}
}
/**
* Loop through the positions
*/
foreach($positions as $key => $value){
$desiredString[$key] = $findMe[$key];
}
/**
* The final result
*/
echo implode('/', $desiredString);
}
Sometimes you are out of luck and the functions you need to solve a problem directly just aren't there. This happens with every language regardless of how many libraries and builtins it has.
We're going to have to write some code. We also need to solve a particular problem. Ultimately, we want our solution to the problem to be just as clean as if we had the ideal functions given to us in the first place. Therefore, whatever code we write, we want most of it to be out of the way, which probably means we want most of the code in a separate function or class. But we don't just want to just throw around arbitrary code because all of our functions and classes should be reusable.
My approach then is to extract a useful general pattern out of the solution, write that as a function, and then rewrite the original solution using that function (which will simplify it). To find that general pattern I made the problem bigger so it might be applicable to more situations.
I ended up making the function array array_multi_walk(callback $callback [, array $array1 [, array $array2 ... ]]). This function walks over each array simultaneously and uses $callback to select which element to keep.
This is what the solution looks like using this function.
$chooser = function($a, $b) {
return strlen($a) >= 2 && $a[0] == '$' && ctype_digit($a[1])
? $b : $a;
};
$data = array(
'project/$1/details/$2' => 'newby/EXAMPLE/something/OTHER'
);
$explodeSlashes = function($a) { return explode('/', $a); };
$find = array_map($explodeSlashes, array_keys($data));
$replace = array_map($explodeSlashes, array_values($data));
$solution = array_multi_walk(
function($f, $r) use ($chooser) {
return array_multi_walk($chooser, $f, $r);
},
$find, $replace);
And, as desired, array_multi_walk can be used for other problems. For example, this sums all elements.
$sum = function() {
return array_sum(func_get_args());
};
var_dump(array_multi_walk($sum, array(1,2,3), array(1,2,3), array(10)));
// prints the array (12, 4, 6)
You might want to make some tweaks to array_multi_walk. For example, it might be better if the callback takes the elements by array, rather than separate arguments. Maybe there should be option flags to stop when any array runs out of elements, instead of filling nulls.
Here is the implementation of array_multi_walk that I came up with.
function array_multi_walk($callback)
{
$arrays = array_slice(func_get_args(), 1);
$numArrays = count($arrays);
if (count($arrays) == 0) return array();
$result = array();
for ($i = 0; ; ++$i) {
$elementsAti = array();
$allNull = true;
for ($j = 0; $j < $numArrays; ++$j) {
$element = array_key_exists($i, $arrays[$j]) ? $arrays[$j][$i] : null;
$elementsAti[] = $element;
$allNull = $allNull && $element === null;
}
if ($allNull) break;
$result[] = call_user_func_array($callback, $elementsAti);
}
return $result;
}
So at the end of the day, we had to write some code, but not only is the solution to the original problem slick, we also gained a generic, reusable piece of code to help us out later.
Why there should not be $2,$4 but $1,$2 ?if you can change your array then it can be solved in 3 or 4 lines codes.
$data = array(
'project/$2/details/$4' => 'newby/EXAMPLE/something/OTHER'
);
foreach($data as $desiredString => $findMe)
{
$regexp = "#(".implode(')/(',explode('/',$findMe)).")#i";
echo preg_replace($regexp,$desiredString,$findMe);
}
I've shortened your code by removing comments for better readability. I'm using array_map and the mapping function decides what value to return:
<?php
function replaceDollarSigns($desired, $replace)
{
return preg_match('#(\$\d)#', $desired) ? $replace : $desired;
}
$data = array(
'project/$1/details/$2' => 'newby/EXAMPLE/something/OTHER',
);
foreach($data as $desiredString => $findMe)
{
$desiredString = explode('/', $desiredString);
$findMe = explode('/', $findMe);
var_dump(implode('/', array_map('replaceDollarSigns', $desiredString, $findMe)));
}
?>
Working example: http://ideone.com/qVLmn
You can also omit the function by using create_function:
<?php
$data = array(
'project/$1/details/$2' => 'newby/EXAMPLE/something/OTHER',
);
foreach($data as $desiredString => $findMe)
{
$desiredString = explode('/', $desiredString);
$findMe = explode('/', $findMe);
$result = array_map(
create_function(
'$desired, $replace',
'return preg_match(\'#(\$\d)#\', $desired) ? $replace : $desired;'
),
$desiredString,
$findMe);
var_dump(implode('/', $result));
}
?>
Working example: http://ideone.com/OC0Ak
Just saying, why don't use an array pattern/replacement in preg_replace? Something like this:
<?php
/**
* Key = The DESIRED string
* Value = The ORIGINAL value
*
* Desired Result: project/EXAMPLE/something/OTHER
*/
$data = array(
'project/$1/details/$2' => 'newby/EXAMPLE/details/OTHER'
);
$string = 'project/$1/details/$2';
$pattern[0] = '/\$1/';
$pattern[1] = '/\$2/';
$replacement[0] = 'EXAMPLE';
$replacement[1] = 'OTHER';
$result = preg_replace($pattern, $replacement, $string);
echo $result;
I think that it's much easier than what you're looking for. You can see that it works here: http://codepad.org/rCslRmgs
Perhaps there's some reason to keep the array key => value to accomplish the replace?

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);
}

Categories