How can I subtract the value of an array if that value is greater than specific data?
For example:
I have an array of menu's position [0,3,10,5,6,9,2,7,1,4,8,11] I want to subtract all values that are greater than selectedDeletedNumbers and stay in their position.
Example: If I delete 3 and 5. I am using checkbox to delete
And the output should be like this.
[0,8,4,7,2,5,1,3,6,9]
Greater than 3 is subtract by 1, and greater than 5 is subtract by 2 since I selected 2 numbers.
HTML
#foreach ($columns as $key => $column)
<div class="checkbox checkbox-danger col-xs-6 el{{ $column->Field }}">
<input type="checkbox" name="CheckboxDelete[]" id="check{{ $column->Field }}" data-column="{{ $key }}" value="{{ $column->Field }}">
<label for="check{{ $column->Field }}"><b style="color: grey">{{ strtoupper($column->Field) }}</b></label>
</div>
#endforeach
Here is my code, but it's only when deleted 1, and not in a position as well.
$searchArr = array_search( $selectedDeletedNumbers, $items);
unset($items[$searchArr]);
$numbers = [];
foreach ($items as $item) {
if ($item > $col[1])
$numbers[] = $item - 1;
}
Thanks in advance.
For clarity and brevity, I'll refer to your input data as:
$array = [0, 3, 10, 5, 6, 9, 2, 7, 1, 4, 8, 11];
$deletes = [3, 5];
There is no sorting or preparation required for either of my snippets.
Output for both of the following are:
array (
0 => 0,
1 => 8,
2 => 4,
3 => 7,
4 => 2,
5 => 5,
6 => 1,
7 => 3,
8 => 6,
9 => 9,
)
Mostly function based approach: (Demo)
foreach (array_diff($array, $deletes) as $value) {
$result[] = array_reduce(
$deletes,
function ($carry, $item) use ($value) {
return $carry - ($value > $item);
},
$value
);
}
var_export($result);
*note: ($value > $item) will evaluated as true or false, when used in arithmetic, true = 1 and false = 0.
Language construct based approach: (Demo)
foreach ($array as $value) {
foreach ($deletes as $delete) {
if ($value == $delete) {
continue 2; // goto next value in outer loop without pushing
}
if ($value > $delete) {
--$value; // decrement value
}
}
$result[] = $value; // push adjusted value into result array
}
var_export($result);
The advantage in this snippet is that there are zero function calls and minimal variables declared.
To delete numbers from array and to subtract a count from the current number in the original array with the number of numbers smaller than it in selectedDeletedNumbers, you can:
Sort the selectedDeletedNumbers array.
Iterate over the original array and use binary search to get the count of numbers smaller than the current number in the original array and subtract it later.
If current number is present in selectedDeletedNumbers, then unset them.
Snippet:
<?php
$arr = [0,3,10,5,6,9,2,7,1,4,8,11];
$selectedDeletedNumbers = [3,5];
sort($selectedDeletedNumbers);
foreach($arr as $index => $val){
$low = 0;$high = count($selectedDeletedNumbers) - 1;
$equal_found = false;
while($low <= $high){
$mid = intval(($low + $high) / 2);
if($selectedDeletedNumbers[$mid] > $val){
$high = $mid - 1;
}else if($selectedDeletedNumbers[$mid] < $val){
$low = $mid + 1;
}else{
$equal_found = true;
unset($arr[$index]);
break;
}
}
if(!$equal_found){
$arr[$index] -= $low;
}
}
print_r($arr);
Demo: http://sandbox.onlinephpfunctions.com/code/ca881fce6a27c216f24a1701190940a87132ab1c
This way, the time complexity will be max(O(m log(m)),O(n log(m))) where n is size of original array and m is the size of selectedDeletedNumbers and space complexity is O(1).
If you want to re-index the numbers sequentially, do an $arr = array_values($arr) in the end.
One more solution is to optimize the checking of what to delete. This code makes sure the delete list is a 0 based array, so in the example it ensures it's
[ 0 => 3, 1 => 5 ]
Then it sorts it descending, but keeps the key, so
[ 1 => 5, 0 => 3 ]
Then when checking the numbers, if the number is greater than 5, it subtracts the key+1, so 1+1 = 2 (sorry for the basic maths). It can then stop and doesn't have to check any more.
$menu = [0,3,10,5,6,9,2,7,1,4,8,11];
$selectedDeletedNumbers = [3,5];
// In case array is not just 0 based index, can remove if it will be
$selectedDeletedNumbers = array_values($selectedDeletedNumbers);
// Sort so highest first, keep key (this is the offset)
arsort($selectedDeletedNumbers);
foreach ( $menu as $key => $menuItem ) {
foreach ( $selectedDeletedNumbers as $deleteIndex => $delete ) {
// Matching, just remove and carry on
if ( $menuItem == $delete ) {
unset($menu[$key]);
break;
}
// Greater than item, so deduct index and move on
if ( $menuItem > $delete ) {
$menu[$key] -= ($deleteIndex+1);
break;
}
}
}
print_r($menu);
If your selected array might come in any order, you can replace the
$selectedDeletedNumbers = array_values($selectedDeletedNumbers);
line with...
sort($selectedDeletedNumbers);
for ($i = 0; $i < count($menuPositions); $i++) {
for ($j = 0; $j < count($selectedDeletedNumbers); $j++) {
if ($menuPositions[$i] == $selectedDeletedNumbers[$j]) {
unset($menuPositions[$i]); // remove the match value from array
$menuPositions = array_values($menuPositions); // reorganize array keeping only the values are not null
$i--; //include the new value of $menuPositions[$i] on interation
break;
}
if ($menuPositions[$i] > $selectedDeletedNumbers[$j] ) {
$menuPositions[$i]--;
}
}
}
Related
I have an array that I'd like to reorder by every 3rd item. So it's 1st, 4th, 7th and then 2nd, 5th, 8th and 3rd, 6th, 9th.
Input:
$items = ['1','2','3','4','5','6','7','8','9'];
Desired result:
['1','4','7','2','5','8','3','6','9']
Or it can be 3 separate arrays as well. Like this:
['1', '4', '7']
['2', '5', '8']
['3', '6', '9']
I tried array_chunk($items, count($items) / 3) but it just returns the same array divided into 3 equal arrays.
['1', '2', '3']
['4', '5', '6']
['7', '8', '9']
I don't know if I should use array_chunk and then something else to achieve what I want.
UPDATE: It does not have to be exactly 9 items in the input array. it can be shorter or longer, the key point is that it has to take every 3rd item as long as it finds any and so on...
So if there are 10 items:
$items = ['1','2','3','4','5','6','7','8','9','10'];
Desired result:
['1','4','7','10','2','5','8','3','6','9']
The simplest way I can think of is to loop over the list three times:
Start at element 0, skipping by 3, stop when past end of list
Start at element 1, skipping by 3, stop when past end of list
Start at element 2, skipping by 3, stop when past end of list
That can be easily achieved using C-style for loop syntax:
$maxKey = count($items) - 1;
$rearrangedItems = [];
for ( $key=0; $key<=$maxKey; $key+=3 ) {
$rearrangedItems[] = $items[$key];
}
for ( $key=1; $key<=$maxKey; $key+=3 ) {
$rearrangedItems[] = $items[$key];
}
for ( $key=2; $key<=$maxKey; $key+=3 ) {
$rearrangedItems[] = $items[$key];
}
If you needed to generalize to different offsets, you could put the whole thing into an outer loop rather than copying and pasting:
$offset = 3;
$maxKey = count($items) - 1;
$rearrangedItems = [];
for ( $start=0; $start<$offset; $start++ ) {
for ( $key=$start; $key<=$maxKey; $key+=$offset ) {
$rearrangedItems[] = $items[$key];
}
}
Try this
$items = array('1','2','3','4','5','6','7','8','9');
$tmp = [];
for ($i = 0; $i < count($items); $i++) {
$tmp[$i%3][] = $items[$i];
}
$result = array_merge($tmp[0], $tmp[1], $tmp[2]);
print_r($result);
Try this,
$items = array('1','2','3','4','5','6','7','8','9');
$tmp = [];
for ($i = 0; $i < count($items); $i++) {
$tmp[$i%3][] = $items[$i];
}
$result = call_user_func_array('array_merge', $tmp);
dd($result);
Output :
array('1','4','7','2','5','8','3','6','9')
I see this task as nothing more than a sorting task.
Sort each value by it modulus value when divided by 3. If the result is 0, then fallback to 3. Nothing extravagant, just simple, direct, logical, D.R.Y. programming.
Code: (Demo)
array_multisort(
array_map(fn($v) => $v % 3 ?: 3, $items),
$items
);
Pretty straightforward:
$items = array('1','2','3','4','5','6','7','8','9');
$items = array_chunk($items, 3); # Split into arrays of size 3
$items = array_map(null, ...$items); # Transpose the arrays
$items = array_merge(...$items); # Merge the arrays again
print_r($items);
I have a long multi-dimensional array, with groups of numbers counting up to 100 ... For example:
$list = (
array('1-4','first'),
array('5-7','next'),
array('8-10','third group'),
array('11-12','another group'),
/* ... keep going up to 100 */
array('94-97','almost done'),
array('98-100','first')
);
I have a randomly generated number, to figure out with line to output:
$num = rand(1,100);
I'd like to know how I would go about getting the # associated inside the array. So, if I rolled a 3, I could get the first array key (1-4), and be able to print out "first" .... or if I rolled a 97, I would get the 2nd-to-last key for "almost done" (94-97). Is there a quick way to do this?
Thanks
You could do something like this if the structure of $list is guaranteed:
$randomRoll = rand(1,100);
array_walk($list, function($ar) use ($randomRoll){
$nums = explode('-', $ar[0]);
(int) $lower = $nums[0];
(int) $upper = $nums[1];
if (($randomRoll >= $lower) && ($randomRoll <= $upper))
{
print($ar[1]);
}
});
You can simplify your lookup array into an associative one since there are no gaps from 1 to 100. In other words, every random number will fall into one of the known groups by determining the greatest number that it is less than or equal to.
Don't use any functional iterators (e.g. array_filter() or array_walk()) because they will unconditionally iterate the entire lookup array. Instead, use foreach() and break as soon as possible -- this is most efficient.
Code: (Demo)
$list = [
4 => 'first', // 1 - 4
7 => 'next', // 5 = 7
10 => 'third group', // 8 - 10
12 => 'another group', // 11, 12
95 => 'huge chunk', // 13 - 95
97 => 'almost done', // 96, 97
100 => 'first' // 98 - 100
];
$num = rand(1, 100);
foreach ($list as $key => $group) {
// echo "$num vs $key\n";
if ($num <= $key) {
break; // don't iterate anymore after finding group
}
}
echo "Num = $num, Group = $group";
I did the following... and it seems to work. If anyone has advice on cleaning up the code, let me know... Thanks...
$dieroll = rand(1,100); // Final Roll
$numarr = array(); $j=0;
foreach ($list as $k => $v) {
$strTr = $v[0];
$nums2arr = explode("-", $strTr);
for($i=$nums2arr[0]; $i<=$nums2arr[1]; $i++) {
$numarr[] = $j;
}
$j++;
}
$roll = $list[$numarr[$dieroll-1]][1];
This solution uses array_filter. The result is again a 2-dimensional array with the elements that fulfill the condition of the filter function.
$filter = function($a) use($find){
list($low,$up) = explode('-',$a[0]);
return $find >= $low AND $find <= $up;
};
$arr = array_filter($list, $filter);
full example:
$list = [
['1-4','first'],
['5-7','next'],
['8-10','third group'],
['11-12','another group'],
/* ... keep going up to 100 */
['94-97','almost done'],
['98-100','first']
];
//for a test is better to use a fix number
$find = 9;
$filter = function($a) use($find){
list($low,$up) = explode('-',$a[0]);
return $find >= $low AND $find <= $up;
};
$arr = array_filter($list, $filter);
echo 'Label: '.reset($arr)[1]."<br>\n";
echo 'Area: '.reset($arr)[0]."<br>\n";
echo 'Array-Key: '.key($arr)."<br>\n";
Output:
Label: third group
Area: 8-10
Array-Key: 2
As of PHP 8.0, solutions with match are also possible for this:
$num = rand(1, 100);
$group = match(true) {
$num <= 4 => 'first',
$num <= 7 => 'next',
$num <= 10 => 'third group',
$num <= 12 => 'another group',
$num <= 95 => 'huge chunk',
$num <= 97 => 'amost done',
$num <= 100 => 'first',
};
echo "Num = $num, Group = $group";
I want to find minimum value from an array and subtract that value from all the elements until all the elements becomes 0. The problem is that if the array contains one of the element as 0 then it doesn't reflect anything. Thanks in advance.
<?php
$handle = fopen ("php://stdin","r");
fscanf($handle,"%d",$n);
$arr_temp = fgets($handle);
$arr = explode(" ",$arr_temp);
array_walk($arr,'intval');
for($j=0;$j<10;$j++)
{
$min = min($arr);
for($i=0;$i<count($arr);$i++)
{
if($arr[$i]>=$min)
$arr[$i]-=$min;
echo $arr[$i]." ";
}
echo "\n";
}
?>
Using array_filter(), plus a function you can get the minimum positive non-zero integer even if an array contains elements whose value is zero, as follows:
<?php
$arr = array(0, 1, 2, 3, 4, 0, 5, 6, 4, 3, 2, 1,0);
function getMin( $a ) {
$arr_filtered = array_values(array_filter( $a ));
return min($arr_filtered);
}
$min = getMin( $arr );
$count = count($arr);
while( count( array_unique($arr) ) > 1 ) {
for( $i = 0; $i < $count; $i++)
{
if( $arr[$i] >= $min ){
$arr[$i] -= $min;
echo $arr[$i]." ";
}
}
echo "\n";
}
See demo
Note, the code re-indexes the filtered array in getMin() by using array_values().
As long as the minimum value is one the above script works well. However, if the minimum value were another number, then the code needs to be adjusted. The following script handles a multi-dimensional array composed of four arrays, each with a different minimum value, including one with negative values:
<?php
error_reporting(E_ALL);
$a = [ [0,1, 2, 3, 4, 0, 5, 6, 4, 3, 2, 1,0],
[0, 2, 3, 4, 0, 5, 6, 4, 3, 2, 0],
[0, 3, 4, 0, 5, 6, 7,8,4, 3, 0],
[-1,0,1,2,3,4,2,1,0,-1]
];
function getMin( $a ) {
return min($a);
}
foreach ($a as $arr) {
while( ( $arr != null ) ) {
$arr = array_values(array_filter($arr));
if ($arr == null) {
break;
}
$min = getMin( $arr );
foreach ($arr as &$e) {
$e -= $min;
}
unset($e);
echo join(" ",$arr),"\n";
}
echo "\n";
}
see demo
Note, the second script explicitly filters out the zero values in each array whereas the first script effectively does so. The primary difference between the two scripts is that getMin() needs to be in the while loop so that the subtraction correctly occurs when the minimum number is greater than one or is a negative number.
The script also simplifies the code in several ways. Much more simple condition for the while loop -- works as long as array is not null. The code no longer needs getCount() since I changed the subtraction loop to use a foreach with a reference variable which is subsequently unset -- important to do to avoid problems. This foreach loop also does not require the if conditional of the first script.
It is because you taking each and every time $min = min($arr); value and it became 0 thats why your code is not working.
try below code
$min = min($arr);
for($j=0;$j<=100;$j++)
{
if (count(array_unique($arr)) === 1 && end($arr) === 0) {
break;
}
for($i=0;$i<count($arr);$i++)
{
if($arr[$i] >= $min){
$arr[$i]-=$min;
echo $arr[$i]." ";
}
}
echo "<br>";
}
I do not know much about php but in the first for loop you should check the elements of the array if they are zero or not using if statements e.g if(arr[j]!=0)
for($j=0;$j<10;$j++)
{
if($arr[$j]!=$0)
$min = min($arr);
for($i=0;$i<count($arr);$i++)
{
if($arr[$i]>=$min)
$arr[$i]-=$min;
echo $arr[$i]." ";
}
echo "\n";
}
I have an array. I'd like to get the three highest values of the array, but also remember which part of the array it was in.
For example, if my array is [12,3,7,19,24], my result should be values 24,19,12, at locations 4, 0, 3.
How do I do that? The first part is easy. Getting the locations is difficult.
Secondly, I'd like to also use the top three OR top number after three, if some are tied. So, for example, if I have [18,18,17,17,4], I'd like to display 18, 18, 17, and 17, at location 0,1,2,3.
Does that make sense? Is there an easy way to do that?
Wouldn't you be there using asort()?
For example:
<?php
$list = [4,18,18,17,17];
// Sort maintaining indexes.
asort($list);
// Slice the first 3 elements from the array.
$top3 = array_slice($list, -3, null, true);
// Results in: [ 1 => 18, 2 => 18, 3 => 17 ]
Or you can use arsort
function getMyTop($list, $offset, $top) {
arsort($list);
return array_slice($list, $offset, $top, true);
}
$myTop = getMyTop($list, 0, 3);
$myNextTop = getMyTop($list, 3, 4);
This is what you need!
<?php
$array = array(12,3,7,19,24);
$array_processed = array();
$highest_index = 0;
while($highest_index < 3)
{
$max = max($array);
$index = array_search($max,$array);
$array_processed[$index] = $max;
unset($array[$index]);
$highest_index++;
}
print_r($array_processed);
?>
You will get Index as well as the value! You just have to define how many top values you want! Let me know if it's what you want!
function top_three_positions($array){
// Sort the array from max to min
arsort($array);
// Unset everything in sorted array after the first three elements
$count = 0;
foreach($array as $key => $ar){
if($count > 2){
unset($array[$key]);
}
$count++;
}
// Return array with top 3 values with their indexes preserved.
return $array;
}
You can use a loop to determine how many elements your top-three-with-ties will have, after applying arsort:
function getTop($arr, $num = 3) {
arsort($arr);
foreach(array_values($arr) as $i => $v) {
if ($i >= $num && $v !== $prev) return array_slice($arr, 0, $i, true);
$prev = $v;
}
return $arr;
}
// Sample input
$arr = [4,18,17,6,17,18,9];
$top = getTop($arr, 3);
print_r($top); // [5 => 18, 1 => 18, 4 => 17, 2 => 17]
try this:
public function getTopSortedThree(array $data, $n = 3, $asc = true)
{
if ($asc) {
uasort($data, function ($a, $b) { return $a>$b;});
} else {
uasort($data, function ($a, $b) { return $a<$b;});
}
$count = 0;
$result = [];
foreach ($data as $key => $value) {
$result[] = $data[$key];
$count++;
if ($count >= $n){
break;
}
}
return $result;
}
Send false for desc order and nothing for asc order
Send $n with number of top values you want.
This functionality doesn't losing keys.
This task merely calls for a descending sort, retention of the top three values, and in the case of values after the third-positioned value being equal to the third value, retain these as well.
After calling rsort(), call a for() loop starting from the fourth element ([3]). If the current value is not equal to the value in the third position, stop iterating, and isolate the elements from the front of the array to the previous iteration's index. Done.
p.s. If the input array has 3 or fewer elements, the for() loop is never entered and the whole (short) array avoids truncation after being sorted.
Code: (Demo)
$array = [18, 17, 4, 18, 17, 16, 17];
rsort($array);
for ($i = 3, $count = count($array); $i < $count; ++$i) {
if ($array[2] != $array[$i]) {
$array = array_slice($array, 0, $i);
break;
}
}
var_export($array);
Because the loop purely finds the appropriate finishing point of the array ($i), this could also be compacted to: (Demo)
rsort($array);
for ($i = 3, $count = count($array); $i < $count && $array[2] === $array[$i]; ++$i);
var_export(array_slice($array, 0, $i));
Or slightly reduced further to: (Demo)
rsort($array);
for ($i = 3; isset($array[2], $array[$i]) && $array[2] === $array[$i]; ++$i);
var_export(array_slice($array, 0, $i));
Output:
array (
0 => 18,
1 => 18,
2 => 17,
3 => 17,
4 => 17,
)
I have this array.
$val = array(1, 0, 2, 1, 1);
I want to subtract 3.5 from $val array in the way that from every element that element not become negative like this:
$val = array(0, 0, 0, 0.5, 1)
Iterate array items and in loop check if target value is great that loop item, subtract item value from target value. If loop item is great than target value, subtract target value from loop item.
$val = array(1, 0, 2, 1, 1);
$subtract = 3.5;
foreach ($val as $key => $item){
if ($subtract >= $item){
$subtract -= $item;
$val[$key] = 0;
} else {
$val[$key] -= $subtract;
$subtract = 0;
}
}
See result in demo
One other possible approach: reduce the subtract value by the value of the current iteration, then set the current value to either zero or -$subtract. Break when $subtract drops below zero.
foreach ($val as &$number) {
$subtract -= $number;
$number = ($subtract > 0) ? 0 : -$subtract;
if ($subtract <= 0) break;
}