Guys I have an array with objects,
I want the last item in the foreach loop do something else then the rest.
How do I archive that?
if(sizeof($testDup) > 3){
} else {
foreach ($testDup as $d) {
}
}
$test array(3)
432 => test_id -> 21
431 => test_id -> 21
435 => test_id -> 21
This will process the array of objects and do something else with the last element:
$data = '';
$arrayWithObjects = array(
(object)array('test1', 'test2'),
(object)array('test1', 'test2'),
(object)array('test1', 'test2'),
);
foreach ($arrayWithObjects as $object) {
// Can't get next in the array, so is last element
if (!next($arrayWithObjects)) {
// Process last element
$data .= $object->{1};
} else {
// Process all other elements
$data .= $object->{0};
}
}
var_dump($data); // "test1test1test2"
you can compare the current one with the end():
class Test {
public function __construct(private string $name) {}
public function read(): string {
return sprintf('%s: hurray', $this->name);
}
public function readLast():string {
return sprintf('%s: am I last?', $this->name);
}
}
$array = [
new Test('first'),
new Test('second'),
new Test('third'),
new Test('fourth'),
];
foreach( $array as $object ){
if($object === end($array)) {
echo $object->readLast().PHP_EOL;
}else{
echo $object->read().PHP_EOL;
}
}
As an alternative to checking if the current item is the last one (which the other answers show), you could use array_slice() to get the start of the array to loop over and then end() to get the last element of the array.
$data = [/*...*/]
foreach ($item as array_splice($data, 0, -1, true) {
$item->foo();
}
if (($item = end($data) !== false) {
$item->bar();
}
In my opinion, this code is easier to read (and metrics like cyclomatic complexity agree) than the nested if $item === end($data) check. If the same is true on your specific case will depend on what, exactly is in the loop and how much of it is different.
In addition, if your array is large, this approach may offer (slightly) better performance (but if your array is large and a small performance difference is important, don't take my word for this - benchmark both solutions with read data).
It's so easy: When the loop is finished, you still got the last element!!
if (!empty($arr)) {
foreach ($arr as $item) {
; // Do something with $item
}
// Here you still got last $item
echo var_export($item, true);
}
Related
I'm having an array_reduce function which I am willing to exit when specific criteria is met.
$result = array_reduce($input, function($carrier, $item) {
// do the $carrier stuff
if (/* god was one of us */) {
break; //some break analogue
}
return $carrier;
});
How do I achieve this? Or should I use foreach instead?
array_reduce is used to write functional-style code which always iterates over the full array. You can either rewrite to use a regular foreach loop to implement short circuiting logic, or you can simply return the current $carrier unmodified. This will still iterate over your full array, but it will not alter the result (as you said, this is more alike to continue)
Firstly, let me say that array_reduce is probably one of my favorite functions - I am famous (well, in a very small circle) for taking 40 lines of clearly written code and replacing them with four harder-to-follow 10 line array_reduce calls to do the same thing!
Sadly, PHP array functions seem bound to want to complete their task. This, combined with the inability to make a recursive unnamed function, makes this common situation difficult to deal with. Not wanting to put a lot of ugly for loops in my code, I tend to bury them in another function (see reduce below) as did an earlier poster.
It's worth pointing out that this is in no way as efficient as using array functions, and, in most circumstances, it's better just to let the array reduce function use a "done" flag to spin quickly through the unneeded values. At any rate, this is something reasonably array_reduce like (the evaluation function using a null return to indicate its finished). The goal is to add up the numbers in the array until you get to a 4.
<?php
$init = 0;
$arr = [1,2,3,4,5,6,7,8,9,0];
$func = function($c, $it) {
if ($it == 4) return null;
return $c + $it;
};
function reduce($arr, $f, $init) {
for ($c = $init; count($arr); ) {
$newc = $f($c, array_shift($arr));
if (!isset($newc)) break;
$c = $newc;
}
return $c;
}
echo reduce($arr, $func, $init) . "\n"; // 6
According to a similar answer.
Break array_walk from anonymous function
The best and the worst way to complete this is Exception.
Not recommend this way, but this way is the solution to your question:
try {
$result = array_reduce( $input, function ( $carrier, $item ) {
// do the $carrier stuff
$condition = true;
if ( $condition ) {
throw new Exception;
}
return $carrier;
} );
} catch ( Exception $exception ) {
echo 'Break';
}
The way I would solve the problem
I would create a global function or write PHP extension and add a function
There is a good answer about writing PHP extension:
How to make a PHP extension
array_reduce_2();
But there is a problem with breaking implementation.
Need to detect which condition to out of function.
Below implementation, array_reduce_2 checks if a callback returned.
If so - breaking out of execution.
This way allows checking if execution has broken by checking return type of value.
array_reduce_2 implementation
According to #wordragon notice, implemented the ability to pass an associative array as param too.
function array_reduce_2( $array, $callback, $initial = null ) {
$len = count( $array );
$index = 0;
if ( $len == 0 && count( func_get_args() ) == 1 ) {
return null;
}
$values = array_values( $array );
$result = $initial ?? $values[ $index ++ ];
foreach ( $values as $value ) {
$result = $callback( $result, $value );
if ( ! is_callable( $result ) ) {
continue;
}
// break;
return $result;
}
return $result;
}
I've used the idea from JS implementation and rewrite for PHP accordingly
https://gist.github.com/keeto/229931
Detecting if the break occured
$input = [ 'test', 'array', 'god was one of us' ];
$result = array_reduce_2( $input, function ( $carrier, $item ) {
// do the $carrier stuff
if ( $item === 'god was one of us' ) {
return function () {
return 'god was one of us';
};
}
return $carrier;
} );
$is_break = is_callable( $result );
if ( $is_break ) {
echo $result();
exit;
}
Important to note!
This array_reduce_2 implementation works properly only if you don't need to return the normal value as a callback.
I suggest using foreach loops instead. The reasons to not use array_reduce are:
Sound reasons:
It is not statically type-checked. So code inspections do not show type errors if there are any in the input or callback arguments.
It returns mixed, so inspections do not show errors if you misuse the result, or they may show false positive if you use it properly.
You cannot break.
Opinionated reasons:
It is harder on the eye. Having a $result and adding to it in a loop (or whatever you do) is way easier to read than grasping that something is returned and then passed as a $carry accumulator in the next call.
It makes me lazy to extract functions properly. If I extract one operation to a callback, I then may find the code short enough to not extract the whole array operation to a function which should really be done in the first place.
If you use a condition to break, there is a good chance you may one day need other arguments to that callback function. With the callback signature being fixed, you would have to pass arguments with use keyword which really much harder to read than a non-callback.
breakable_array_reduce()
function breakable_array_reduce(array $array, callable $callback, $initial = null) {
$result = $initial;
foreach ($array as $value) {
$ret = $callback($result, $value);
if (false === $ret) {
return $result;
} else {
$result = $ret;
}
}
return $result;
}
Usage
// array of 10 values
$arr = [
1,
1,
1,
1,
1,
1,
1,
1,
1,
1
];
// callback function which stops once value >= 5
$sum_until_five = function($initial, $value) {
if ($initial >= 5) {
return false;
} else {
return $initial + $value;
}
};
// calculate sum of $arr using $sum_until_five()
$sum = breakable_array_reduce($arr, $sum_until_five);
// output: 5
echo $sum;
Explanation
breakable_array_reduce() will work just like array_reduce() unless/until callback $callback returns bool(false)
Alternate implementation using array keys:
breakable_array_reduce_keyed()
function breakable_array_reduce_keyed(array $array, callable $callback, $initial = null) {
$result = $initial;
foreach ($array as $key => $value) {
$ret = $callback($result, $value, $key);
if (false === $ret) {
return $result;
} else {
$result = $ret;
}
}
return $result;
}
Usage
// array of values
$arr = [
'foo' => 1,
'bar' => 1,
'baz' => 1
];
// callback function which stops when $key === 'baz'
$sum_until_baz = function($initial, $value, $key) {
if ('baz' === $key) {
return false;
} else {
return $initial + $value;
}
};
// calculate sum of $arr using $sum_until_baz()
$sum = breakable_array_reduce($arr, $sum_until_baz);
// output: 2
echo $sum;
P.S. I just wrote and fully tested this locally.
In Microsoft Dynamics Nav 2013, there is a feature for specifying Item Substitutions for an item (product); However, you can specify more than one substitution for a single product and technically a substitution can itself have one or more substitutions.
I am trying to build a recursive solution in PHP that allows me to take a known product code, and recursively search for item substitutions to generate a one dimensional array of items. If this were a one to one relationship (parent,child) this would be a trivial task for me, but the fact that there can be multiple childs on any given iteration is kind of blowing my mind.
My question is if anyone knows how to write a recursive method for the situation I've described above? Below I will layout the way the data is structured to give a better understanding of the problem:
$lineItems = array(
'XXX-0',
'XXX-1',
'XXX-3'
);
$substitutionsLookup = array(
0 => array('No_' => 'XXX-1', 'Substitute No_' => 'XXX-2'),
1 => array('No_' => 'XXX-3', 'Substitute No_' => 'XXX-4'),
2 => array('No_' => 'XXX-3', 'Substitute No_' => 'XXX-5'),
3 => array('No_' => 'XXX-5', 'Substitute No_' => 'XXX-6')
);
// Resulting product code substitutions for XXX-0
$result1 = array();
// Resulting product code substitutions for XXX-1
$result2 = array('XXX-2');
// Resulting product code substitutions for XXX-3
$result3 = array('XXX-4', 'XXX-6');
Edit (added my attempt to solve using recursive method):
protected function getSubstitutions($haystack, $needle, &$results = array())
{
if (count($haystack) == 0)
{
return false;
}
$matches = array();
foreach ($haystack as $index => $check)
{
if ($check['No_'] === $needle)
{
$newHaystack = $haystack;
unset($newHaystack[$index]);
$moreMatches = $this->getSubstitutions($newHaystack, $check['Substitute No_'], $results);
if ($moreMatches === false)
{
$matches[] = $check['Substitute No_'];
}
}
}
if (count($matches))
{
foreach($matches as $match)
{
$results[] = $match;
}
}
return $results;
}
Edit (Final code used, derived from accepted answer):
class ItemSubstitutionService implements ServiceLocatorAwareInterface
{
public function getSubstitutions($itemNo, $noInventoryFilter = true, $recursive = true)
{
$substitutions = array();
$directSubs = $this->itemSubstitutionTable->getSubstitutionsByNo($itemNo);
if ($recursive)
{
foreach($directSubs as $sub)
{
$this->getSubstitutionsRecursive($sub, $substitutions);
}
} else {
$substitutions = $directSubs;
}
foreach($substitutions as $index => $sub)
{
$inventory = $this->itemLedgerEntryTable->getQuantityOnHand($sub->getSubstituteNo());
$sub->setInventory($inventory);
if ($noInventoryFilter)
{
if ($inventory == 0)
{
unset($substitutions[$index]);
}
}
}
return $substitutions;
}
private function getSubstitutionsRecursive(ItemSubstitution $sub, &$subs)
{
$directSubs = $this->itemSubstitutionTable->getSubstitutionsByNo($sub->getSubstituteNo());
if (empty($directSubs))
{
$subs[$sub->getSubstituteNo()] = $sub;
}
foreach($directSubs as $curSub)
{
$this->getSubstitutionsRecursive($curSub, $subs);
}
}
}
This code could serve as a solution for your example.
I just presume that you fetch the list of 'direct' items substitutes from your database, so you could replace GetDirectSubstitutes with the code that fetches the substitutes list for the given item (I used you example array as a data source).
Just be careful - this simple implementation doesn't check for cyclical references. If your initial data contains loops, this code will stuck.
function GetDirectSubstitutes($itemNo)
{
global $substitutionsLookup;
$items = array();
foreach ($substitutionsLookup as $itemPair) {
if ($itemPair['No'] == $itemNo) {
array_push($items, $itemPair['SubstNo']);
}
}
return $items;
}
function GetSubstitutesTree($itemNo, &$substitutes)
{
$directSubst = GetDirectSubstitutes($itemNo);
if (!empty($directSubst)) {
$substitutes = array_merge($substitutes, $directSubst);
foreach ($directSubst as $item) {
GetSubstitutesTree($item, $substitutes);
}
}
}
Using PHP, I would like to write a function that accomplishes what is shown by this pseudo code:
function return_value($input_string='array:subArray:arrayKey')
{
$segments = explode(':',$input_string);
$array_depth = count(segments) - 1;
//Now the bit I'm not sure about
//I need to dynamically generate X number of square brackets to get the value
//So that I'm left with the below:
return $array[$subArray][$arrayKey];
}
Is the above possible? I'd really appreciate some pointer on how to acheive it.
You can use a recursive function (or its iterative equivalent since it's tail recursion):
function return_value($array, $input_string) {
$segments = explode(':',$input_string);
// Can we go next step?
if (!array_key_exists($segments[0], $array)) {
return false; // cannot exist
}
// Yes, do so.
$nextlevel = $array[$segments[0]];
if (!is_array($nextlevel)) {
if (1 == count($segments)) {
// Found!
return $nextlevel;
}
// We can return $nextlevel, which is an array. Or an error.
return false;
}
array_shift($segments);
$nextsegments = implode(':', $segments);
// We can also use tail recursion here, enclosing the whole kit and kaboodle
// into a loop until $segments is empty.
return return_value($nextlevel, $nextsegments);
}
Passing one object
Let's say we want this to be an API and pass only a single string (please remember that HTTP has some method limitation in this, and you may need to POST the string instead of GET).
The string would need to contain both the array data and the "key" location. It's best if we send first the key and then the array:
function decodeJSONblob($input) {
// Step 1: extract the key address. We do this is a dirty way,
// exploiting the fact that a serialized array starts with
// a:<NUMBEROFITEMS>:{ and there will be no "{" in the key address.
$n = strpos($input, ':{');
$items = explode(':', substr($input, 0, $n));
// The last two items of $items will be "a" and "NUMBEROFITEMS"
$ni = array_pop($items);
if ("a" != ($a = array_pop($items))) {
die("Something strange at offset $n, expecting 'a', found {$a}");
}
$array = unserialize("a:{$ni}:".substr($input, $n+1));
while (!empty($items)) {
$key = array_shift($items);
if (!array_key_exists($key, $array)) {
// there is not this item in the array.
}
if (!is_array($array[$key])) {
// Error.
}
$array = $array[$key];
}
return $array;
}
$arr = array(
0 => array(
'hello' => array(
'joe','jack',
array('jill')
)));
print decodeJSONblob("0:hello:1:" . serialize($arr));
print decodeJSONblob("0:hello:2:0" . serialize($arr));
returns
jack
jill
while asking for 0:hello:2: would get you an array { 0: 'jill' }.
you could use recursion and array_key_exists to walk down to the level of said key.
function get_array_element($key, $array)
{
if(stripos(($key,':') !== FALSE) {
$currentKey = substr($key,0,stripos($key,':'));
$remainingKeys = substr($key,stripos($key,':')+1);
if(array_key_exists($currentKey,$array)) {
return ($remainingKeys,$array[$currentKey]);
}
else {
// handle error
return null;
}
}
elseif(array_key_exists($key,$array)) {
return $array[$key];
}
else {
//handle error
return null;
}
}
Use a recursive function like the following or a loop using references to array keys
<?php
function lookup($array,$lookup){
if(!is_array($lookup)){
$lookup=explode(":",$lookup);
}
$key = array_shift($lookup);
if(!isset($array[$key])){
//throw exception if key is not found so false values can also be looked up
throw new Exception("Key does not exist");
}else{
$val = $array[$key];
if(count($lookup)){
return lookup($val,$lookup);
}
return $val;
}
}
$config = array(
'db'=>array(
'host'=>'localhost',
'user'=>'user',
'pass'=>'pass'
),
'data'=>array(
'test1'=>'test1',
'test2'=>array(
'nested'=>'foo'
)
)
);
echo "Host: ".lookup($config,'db:host')."\n";
echo "User: ".lookup($config,'db:user')."\n";
echo "More levels: ".lookup($config,'data:test2:nested')."\n";
Output:
Host: localhost
User: user
More levels: foo
protected static function arrayShuffling($itemsArray)
{
$itemSwitching = array();Switching
$shItem= array();
foreach ($itemArray as $i => $myItem) {
if (!in_array($myItem->_id, $itemSwitching)) {
$itemSwitching[$i]['_id'] = $myItem->_id;
$itemSwitching[$i]['poistion'] = $myItem['details']['move_to_poistion'];
}
foreach ($itemSwitching as $t => $pinPrep) {
if ($event->_id == $pinPrep['_id']) {
$shuffeledItem[$itemSwitching[$t]['poistion']] = $myItem;
unset($itemArray[$i]);
}
}
}
foreach($shItem as $key=>$shffuledItem){
array_splice( $itemArray, $key, 0, array($shffuledItem));
}
}
I have this method that takes an array and then look at $myItem['details']['move_to_poistion'] index to see what position that element should be moved to. After that it takes that specific element out of the array and then uses splice to insert it back to the $myItem['details']['move_to_poistion'] position.
I am worried about too many foreach lops and want to know if some how we can shorten this.
thanks
I want to generate all possible combination of array elements to fill a placeholder, the placeholder size could vary.
Let say I have array $a = array(3, 2, 9, 7) and placeholder size is 6. I want to generate something like the following:
3,3,3,3,3,3
2,3,3,3,3,3
2,2,3,3,3,3
...........
...........
7,7,7,7,7,9
7,7,7,7,7,7
However (2,3,3,3,3,3) would be considered the same as (3,2,3,3,3,3) so the later one doesn't count.
Could anyone point me to the right direction? I know there is Math_Combinatorics pear package, but that one is only applicable to placeholder size <= count($a).
Edit
I am thinking that this one is similar to bits string combination though with different number base
I have no PHP source code for you but some sources that might help.
Some C code. Look at 2.1:
http://www.aconnect.de/friends/editions/computer/combinatoricode_g.html
Delphi code: combination without repetition of N elements without use for..to..do
Wiki article here
Well it took quit some time to figure this one out.
So i split the question into multiple parts
1.
I firsrt made an array with all the possible value options.
function create_all_array($placeholder, array $values)
{
if ($placeholder <= 0) {
return [];
}
$stack = [];
$values = array_unique($values);
foreach ($values as $value) {
$stack[] = [
'first' => $value,
'childs' => create_all_array($placeholder - 1, $values)
];
}
return $stack;
}
2.
Then I made a function to stransform this massive amount of data into string (no check for uniques).
function string($values, $prefix = '')
{
$stack = [];
foreach($values as $value) {
$sub_prefix = $prefix . $value['first'];
if (empty($value['childs'])) {
$stack[$sub_prefix] = (int)$sub_prefix;
} else {
$stack = array_merge($stack, string($value['childs'], $sub_prefix));
}
}
return $stack;
}
3.
Then the hard part came. Check for duplicates. This was harder than expected, but found some good anser to it and refactored it for my use.
function has_duplicate($string, $items)
{
$explode = str_split ($string);
foreach($items as $item) {
$item_explode = str_split($item);
sort($explode);
$string = implode('',$explode);
sort($item_explode);
$item = implode($item_explode);
if ($string == $item) {
return true;
}
}
return false;
}
4.
The last step was to combine the intel into a new funciton :P
function unique_string($placeholder, array $values)
{
$stack = string(create_all_array($placeholder, $values));
$check_stack = [];
foreach($stack as $key => $item) {
if (has_duplicate($item, $check_stack)) {
unset($stack[$key]);
}
$check_stack[] = $item;
}
return $stack;
}
Now you can use it simple as followed
unique_string(3 /* amount of dept */, [1,2,3] /* keys */);
Ps the code is based for PHP5.4+, to convert to lower you need to change the [] to array() but I love the new syntax so sorry :P