I need to fill an array with the last non-zero value.
Points of note I guess are that the array will either have all 0
values or end with 0
values.
Please review my code below.
<?php
$array = [1, 1, 5, 4, 0, 0];
$array2 = [0, 0, 0, 0, 0, 0];
function padArray($array) {
$length = count($array) - 1;
for ($i = $length; $i >= 0; $i--) {
if($array[$i] != 0) {
for ($j = $i; $j < count($array); $j++) {
$array[$j] = $array[$i];
}
break;
}
}
return $array;
}
var_dump(padArray($array));
var_dump(padArray($array2));
//should output [1,1,5,4,4,4]
//should output [0,0,0,0,0,0]
3 Answers 3
- Do you really want this function to use loose comparison
$array[$i] != 0
? What should intended behavior be for other potentially falsey values at the end of the array (false
,""
,[]
, etc.)? If you truly only want to replace only trailing zeroes, then you must use exact comparison here!==
. - Your function name seems unclear with regards to what the function does. Perhaps
arrayPadOverTrailingZeroes
,arrayPadOverTrailingEmptyValues
(depending on desired behavior). - PHP provides built-in array functions that may clean up this code a little bit (and perhaps perform slightly better since they are compiled).
For example:
function arrayPadOverTrailingZeroes($array) {
$length = count($array);
while (($value = end($array)) === 0) {
array_pop($array);
}
// This conditional to support case where you have all zeroes,
// in which case, the while loop above would have discarded all array members
// and $value would be set to false
if (count($array) === 0) {
$value = 0;
}
return array_pad($array, $length, $value);
}
Answered this before I saw your all-zeroes use case. Added some additional logic above to handle this case.
-
\$\begingroup\$ Great catch on the loose comparison. Will move to built in functions and yes a more descriptive name would make sense. \$\endgroup\$Luke– Luke2017年03月30日 14:06:36 +00:00Commented Mar 30, 2017 at 14:06
-
1\$\begingroup\$ About performances: eval.in/764866 \$\endgroup\$Casimir et Hippolyte– Casimir et Hippolyte2017年03月30日 22:06:18 +00:00Commented Mar 30, 2017 at 22:06
for ($j = $i; $j < count($array); $j++) { $array[$j] = $array[$i]; }
PHP has built-in functions for this:
array_pad(array_slice($array, 0, $i + 1), $length + 1, $array[i]);
or
$firstZero = $i + 1;
$zeroCount = count($array) - $i;
$suffix = array_fill($firstZero, $zeroCount, $array[$i]);
$array = array_splice($array, $firstZero, $zeroCount, $suffix);
This also doesn't copy $array[$i]
over $array[$i]
which may be harmless in terms of effect but is a waste.
Consider renaming $length
to something like $last
, as it isn't the length but the zero-indexed location of the last value.
We can actually simplify the whole function to
$firstZero = array_search(0, $array, true);
$lastNonZero = $firstZero - 1;
if ($firstZero > 0) {
array_pad(array_slice($array, 0, $firstZero), count($array), $lastNonZero);
}
return $array;
I find this more readable as to what it is doing. We're finding the first index of a zero element and thus the last index of a non-zero element. If the first element isn't already zero, we fill all the elements starting with the zero element with the last non-zero value.
The described inputs always include a zero in the array. But if someone passes an array without a zero, false
is not greater than zero, so it just returns the original array. If you prefer, you could write this explicitly as
if ($firstZero !== false && $firstZero > 0) {
or
if ($firstZero === false) {
return $array;
}
But the behavior will be correct without that.
It also may be faster, as the built-in functions are often faster than the manual versions.
-
\$\begingroup\$ Probably best to search from back of array so you iterate over fewer values. Also you have edge case not addressed here for when
array_search()
returnsfalse
(i.e. there are no non-zero items in array) \$\endgroup\$Mike Brant– Mike Brant2017年03月30日 16:19:59 +00:00Commented Mar 30, 2017 at 16:19 -
1\$\begingroup\$ Actually your function doesn't work at all since the return of
array_fill
is blowing in the wind. You can't also usearray_search
with this kind of array[1, 0, 4, 0, 0, 0]
\$\endgroup\$Casimir et Hippolyte– Casimir et Hippolyte2017年03月30日 21:35:10 +00:00Commented Mar 30, 2017 at 21:35
Assuming your actual project arrays will be a relatively small size (like your posted input arrays), there is going to be an unnoticeable margin of speed between the many ways that this can be done.
Here is a method that doesn't use a loop or array_search()
. The else portion of my replaceTrailingZeros()
function could have been condensed into a one-liner, but I've declared $replace
to make it easier to read.
Code: (Demo)
function replaceTrailingZeros($array) {
$trimmed = array_diff($array, [0]); // remove all zeros
// seek last non-zero value (store value or false) and move array pointer to last element
if (($last = end($trimmed)) === false) {
return $array; // nothing to change
} else {
// generate minimal array from zero element keys and last non-zero value
$replace = array_fill($index = key($trimmed) + 1, sizeof($array) - $index, $last);
return array_replace($array, $replace); // only replace the trailing zero elements
}
}
echo implode(',', replaceTrailingZeros([1,0,1,0,5,4,0,0,0,0])); // 1,0,1,0,5,4,4,4,4,4 (5 native func calls)
echo "\n";
echo implode(',', replaceTrailingZeros([0,0,0])); // 0,0,0 (just 2 native func calls)
echo "\n";
echo implode(',', replaceTrailingZeros([1,1,5,4,0,0])); // 1,1,5,4,4,4 (5 native func calls)
Older and wiser... using minimal function calls and relying on language constructs are great ways to maximize script efficiency. You actually could have gotten away with using $length
in both of your loops.
$length = count($array);
for ($i = $length - 1; $i >= 0; --$i) {
if ($array[$i] !== 0) {
for ($j = $i + 1; $j < $length; ++$j) {
$array[$j] = $array[$i];
}
break;
}
}
return $array;
If you wanted to remove the nested loop, you could use array_replace()
and array_fill()
, but they won't outperform a simple for()
loop.
$length = sizeof($array);
for ($i = $length - 1; $i >= 0; --$i) {
if ($array[$i] !== 0) {
return array_replace($array, array_fill($i, $length - $i, $array[$i]));
}
}
return $array;