2
\$\begingroup\$

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]
asked Mar 30, 2017 at 10:31
\$\endgroup\$
0

3 Answers 3

2
\$\begingroup\$
  • 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.

answered Mar 30, 2017 at 13:30
\$\endgroup\$
2
  • \$\begingroup\$ Great catch on the loose comparison. Will move to built in functions and yes a more descriptive name would make sense. \$\endgroup\$ Commented Mar 30, 2017 at 14:06
  • 1
    \$\begingroup\$ About performances: eval.in/764866 \$\endgroup\$ Commented Mar 30, 2017 at 22:06
2
\$\begingroup\$
 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.

answered Mar 30, 2017 at 14:18
\$\endgroup\$
2
  • \$\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() returns false (i.e. there are no non-zero items in array) \$\endgroup\$ Commented 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 use array_search with this kind of array [1, 0, 4, 0, 0, 0] \$\endgroup\$ Commented Mar 30, 2017 at 21:35
1
\$\begingroup\$

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;
answered Sep 15, 2017 at 2:05
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.