PHP - Split an array into chunks based on its content - php

I have an php array that that i would like split to up into smaller chunks using a loop.
Once all elements with the same values have been pushed into the chunk, i want to use that chunk for something, then continue the loop and setup the next chunk, until all content of the original $array has been used.
public function adjust($array){
// $array = [1, 1, 2, 2, 8, 8, 8, 9, 20]
$chunk = array()
for ($i = 0; $i < sizeof($array); $i++){
// code here
// result should be $chunk = [1, 1]
// use $chunk for something, reset its contents,
// continue loop until chunk is [2, 2],
// rinse and repeat
}
}
I dont necesarrily need a loop, i just require to be able to slice $array into 5 $chunks (for this specific example), to be able to process the data.
Is there an easier way to solve this without a bunch of if/else clauses ?

Initialize $chunk to an array containing the first element. Then loop through the rest of the array, comparing the current element to what's in $chunk. If it's different, process $chunk and reset it.
$chunk = array[$array[0]];
for ($i = 1; $i < count($array); $i++) {
if ($array[$i] != $chunk[0]) {
// use $chunk for something
$chunk = array($array[$i]);
} else {
$chunk[] = $array[$i];
}
}
// process the last $chunk here when loop is done

array_count_values is more or likely what you want.
$array = [1, 1, 2, 2, 8, 8, 8, 9, 20];
foreach (array_count_values($array) as $val => $count) {
$chunk = array_fill(0, $count, $val); // contains [1, 1], [2, 2], etc.
}

This can be done without any conditions and without pre-grouping with array_count_values(). No iterated function calls are necessary. Just use each value as a temporary grouping key as you push each value into their own dedicated subarray. This technique will group values even if recurring values are not consecutive. If you want to remove the temporary keys, just call array_values().
Language Construct Loop: (Demo)
$result = [];
foreach ($array as $value) {
$result[$value][] = $value;
}
var_export(array_values($result));
Functional Loop: (Demo)
var_export(
array_values(
array_reduce(
$array,
function($result, $value) {
$result[$value][] = $value;
return $result;
},
[]
)
)
);
Both output the following data structure:
[[1, 1], [2, 2], [8, 8, 8], [9], [20]]

Related

Need Three Highest Values (or more, if tied) of PHP Array and Their Location?

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,
)

Delete elements from array in foreach in PHP (advanced)

Here is my problem:
I have an array with id's ($arr) which I'm slicing in groups of three. Next, I have an array with other id's ($otherIds) which I want to compare with main array ($arr), and if some of id's are identical - they should be deleted from rest of the $arr's chunks.
F.e. I have $arr = array(1, 2, 3, 4, 5, 6, 7, 8), and $otherIds = array(5, 7). I'm cutting $arr into the chunks of three elements, and then loop $arr in foreach and compare them to $otherIds, so in first iteration - code should see that $otherIds '5' and '7' exists in next chunks of $arr, and delete them.
My output should be:
1st iteration - array(1, 2, 3)
2nd iteration - array(4, 6) - 5 should be removed
3rd iteration - array(8) - 7 should be removed
$otherIds can be different in each iteration (they are taken from database), but to simplify it, I will use constant values.
Here is my code:
$arr = array(15, 10, 12, 17, 21, 13, 15, 25, 7, 18, 4, 1, 5, 2);
$chunks = array_chunk($arr, 3);
$ids = array();
foreach ($chunks as $k => $v) {
$otherIds = array(6, 7, 22, 31, 44, 9, 17);
$ids = $v;
foreach ($chunks as $key => $val) {
if ($key <= $k) continue;
foreach ($chunks[$key] as $g => $ch) {
foreach ($otherIds as $o) {
if ($ch['id'] == $o) {
$ids[] = $o;
unset($chunks[$key][$g]);
}
}
}
}
}
As You can see I use a lot of foreaches, but I cant see better solution...
Also, every next iteration of main foreach should be (as mentioned above) shortened by deleted elements from $otherIds - which I this code is NOT doing.
How to achieve it? Is there easier/better/more efficient solution?
I repeat: main goal is to check $otherIds in each iteration of main foreach, and delete same elements from $arr in other chunks.
When you modify an array you are iterating in a foreach loop, you mess up internal pointer in the array and things get confused.
Make a copy while picking up the triplets, do not do unset() nor modify the original array. Thanks to the copy-on-write feature in PHP, the copy will be fast and cost effective, even if the elements are big structures, not only numbers.
Try array_diff():
$arr = array(15, 10, 12, 17, 21, 13, 15, 25, 7, 18, 4, 1, 5, 2);
$chunks = array_chunk($arr, 3);
// Build the filtered list into $output
$output = array();
foreach ($chunks as $k => $v) {
$otherIds = array(6, 7, 22, 31, 44, 9, 17);
// array_diff() returns the list of values from $v that are not in $otherIds
$output[$k] = array_diff($v, $otherIds);
}
// Investigate the result
print_r($output);
Update
I re-read the question and I think I eventually understood the logic (which is not described in the sample data). On each iteration it gets a new set of IDs to ignore and removes them from all chunks, starting from the current chunk.
The updated code is:
$arr = array(15, 10, 12, 17, 21, 13, 15, 25, 7, 18, 4, 1, 5, 2);
$chunks = array_chunk($arr, 3);
// $chunks is numerically indexed; we can use for() to iterate it
// (avoid assigning to $v a value that is never used)
$count = count($chunks);
for ($k = 0; $k < $count; $k ++) {
$otherIds = array(6, 7, 22, 31, 44, 9, 17);
// $chunks is numerically indexed; start with key `$k` to iterate it
for ($key = $k; $key < $count; $key ++) {
// remove the values from $otherId present in the chunk
$chunks[$key] = array_diff($chunks[$key], $otherIds);
}
}

Check if the array can be sorted with a single swap of 2 elements

I am trying to write a function that will check if the array can be sorted with a single swap of the values in the array.
For example: array(1,3,5,3,7) must return true, but array(1,3,5,3,4) must return false.
I tried the following code below, but I'm stuck with it:
$noOfIterations = 0;
for($x = 0; $x < count($a)-2; $x++) {
if($a[$x] > $a[$x+1]) {
$noOfIterations ++;
}
}
return $noOfIterations >1;
// The below solution helped as well.
//$arr = [1, 3, 5, 3, 7]; //[1, 3, 5, 3, 4]
$arr = [1, 3, 5, 3, 4];
$sortedArr = $arr;
sort($sortedArr);
print_r(array_intersect_assoc($arr,$sortedArr));
This should work for you:
(Here I first make a copy of the original array to then sort() it. After this I loop through both arrays with array_map() and look how many position has changed. With array_filter() I sort the elements out where no position has changed. Then you can simply check if 2 or more position has changed and print either FALSE or TRUE)
<?php
$arr = [1, 3, 5, 3, 7]; //[1, 3, 5, 3, 4]
$sortedArr = $arr;
sort($sortedArr);
$filtered = array_filter(
array_map(function($v1, $v2){
return ($v1 == $v2 ?FALSE:TRUE);
}, $arr, $sortedArr)
);
var_dump(count($filtered) > 2 ? FALSE : TRUE);
?>
output:
TRUE //FALSE
Execute the sort, then compare the original array with the sorted array using array_intersect_assoc().... if the difference is more than two elements, then the answer is 'no'
If you really wanna do it with a loop, you can do it with a double loop comparing each value to one another. You need to get a little creative with the comparing. From what I see, your code succeeds on the first array but fails on the second. It fails on the second one because you are only checking 2 adjacent entries and 3 is always less than 4. Or you can keep track of the largest number and count how many numbers are less than that value past it. Also make sure to add to the count if you encounter another bigger value. Hope all this makes sense.
What if array is sorted and no swap is needed?
It might help if I knew Why.
Try this, it works for your two example arrays.
function swap($array){
$prev = 0;
$count = 0;
foreach($array as $val){
if($val < $prev){
$count++;
}
else{
$prev = $val;
}
}
if($count < 2){return(true);}else{return(false);}
}
My answer in php.
function oneSwap($A){
$count=count($A);
$swaps=0;
$curr_max = 0;
$res = false;
for($i = 0; $i <= $count; $i++) {
if(isset($A[$i+1])){
if(($A[$i] >= $A[$i + 1]) && $curr_max >= $A[$i+1]){
$swaps++;
}
if($A[$i] >= $A[$i +1]){
$curr_max = $A[$i];
}
}
}
if(($swaps == 1) || ($swaps == 0 && $curr_max ==0)){
$res = true;
echo $res;
}
}
oneSwap([3,1,2,8]);
oneSwap([1,2,3]);
oneSwap([1,5,3,3,7]);
oneSwap([3,2,1,8]);
oneSwap([2,1,1,2]);

Remove array elements that are less than X

I have arrays:
$arr1 = array(5, 3, 9, 11, 6, 15);
$arr2 = array(11, 20, 1, 3, 8);
Now I need to loop through $arr1 and find the largest number that is less than X:
foreach($arr1 as $x) {
//need element that is MAX in $arr2 but is less than $x
}
so for example for the first run when $x = 5, maximum in $arr2 is 3 that is less than 5.
Is it possible to do this without having nested loop? I do not want to loop through $arr2. I tried using array_filter but didnt really work. Maybe I used it wrong.
This is what I tried with array_filter:
$results = array();
foreach($arr1 as $x) {
$max = max(array_filter($arr2, function ($x) { return $x < $y; }));
$results[$x] = $max;
}
The result should be this:
5 => 3,
3 => 1,
9 => 8,
11 => 8,
6 => 3,
15 => 11
The problem is with the use of the lambda - PHP does not automatically capture variables in the enclosing scope. (The official Anonymous Functions is a bit sparse on the topic, so see In PHP 5.3.0, what is the function "use" identifier? as well.)
Compare the original, which returns an empty array as it is effectively $x < undefined
$arr2 = array(11, 20, 1, 3, 8);
$y = 5;
var_dump(array_filter($arr2, function ($x) { return $x < $y; }));
with the following that employs the use syntax to bind the variable in the function
$arr2 = array(11, 20, 1, 3, 8);
$y = 5;
var_dump(array_filter($arr2, function ($x) use ($y) { return $x < $y; }));
(Also, in the original code there was no $y variable at all, whoops!)
Here's a method that uses array_map() to return the values that are lower than your maximum variable, then uses max() to return the highest (as in your example).
In this example I've used $var as a variable by reference (the &), so that it can be used by the get_highest() callback function which is accessing it as a global variable.
function get_highest($x) {
global $var;
return $x < $var ? $x : 0;
}
$results = array();
foreach($arr1 as &$var) {
$results[$var] = max(array_map('get_highest', $arr2));
}
Without using global variables
You can modify this if you don't want to use global variables by passing in an array of parameters to array_map(). It's a little strange the way it works, because the parameter array needs to be the same length as the original array, so I've used array_fill() to fill it up to the required length with the current value, so the callback function can use it to compare:
function get_highest($x, $y) {
return $x < $y ? $x : 0;
}
$results = array();
foreach($arr1 as $var) {
$max = array_fill(0, count($arr2), $var);
$results[$var] = max(array_map('get_highest', $arr2, $max));
}
Any solution that reuses/resets arr2 will be of quadratic complexity, meaning that if you have an arr1 of size N and an arr2 of size M, you'll be doing around N*M operations.
Plus function calls and parameter passing in case of lambda functions.
A better strategy if N and M are very large is to sort both arrays (complexity is N log N + M log M), then use the same strategy as merge sort to loop through the arrays, saving their state. This will execute in at most N+M operations instead of N*M, making the overall complexity linearithmic.
The lambda solution is easier to understand, more robust and simpler to maintain, so unless there's a pressing reason (N and M in the tens of thousands), it is to be preferred. You are trading speed for ease of maintenance; but it is normally a sweet deal.
First you sort both arrays with PHP's array sort functions, and get
$arr1 = array(3, 5, 6, 9, 11, 15);
$arr2 = array(1, 3, 8, 11, 20);
$map = array();
$n = count($arr1);
$m = count($arr2);
for ($i = 0, $j = 0, $y = false; $i < $n;) {
if ($j == $m) {
$map[$arr1[$i++]] = $y;
} else {
if ($arr1[$i] <= $arr2[$j]) {
$map[$arr1[$i++]] = $y;
} else {
$y = $arr2[$j++];
}
}
}

Counting Sort in PHP

In a PHP project I have some data I want to sort using a linear time, simple counting sort:
$ar = array(7, 2, 0, 3, 8, 0, 12, 7, 6, 7);
$count = array();
foreach ($ar as $v)
$count[$v]++;
$sorted = array();
foreach ($count as $v => $c)
for ($i = 0; $i < $c; $i++)
$sorted[] = $v;
The problem is that the above obviously doesn't work. The php array works more like a hashmap than an array. The code can be made to work by inserting ksort($count) before the final loop, but ksort runs in O(nlogn) which destroys the entire point.
Is there any way to do a linear time sort in php? Perhaps using some paramter to array(), or some entirely different structure?
You didn't follow the algorithm correctly. This is O(n).
$ar = array(7, 2, 0, 3, 8, 0, 12, 7, 6, 7);
$count = array();
foreach ($ar as $v) {
$count[$v] = isset($count[$v]) ? $count[$v] + 1 : 1;
}
$sorted = array();
$min = min($ar);
$max = max($ar);
for ($i=$min; $i<=$max; $i++) {
if (isset($count[$i])) {
for ($j=0; $j<$count[$i]; $j++) {
$sorted[] = $i;
}
}
}
also, see array_count_values(), or alternatively compute the min and max inside the counting loop.
Approved answer is wrong. Correct:
$ar = array(7, 2, 0, 3, 8, 0, 12, 7, 6, 7);
$count = array();
foreach ($ar as $v) {
$count[$v] = isset($count[$v]) ? $count[$v] + 1 : 1;
}
$sorted = array();
$min = min($ar);
$max = max($ar);
for ($i=$min; $i <= $max; $i++) {
if (isset($count[$i])) {
for ($j=0; $j<$count[$i]; $j++) {
$sorted[] = $i;
}
}
}
If i understand your question AND comment correctly, just using sort($count) would work no?
$ar = array(7, 2, 0, 3, 8, 0, 12, 7, 6, 7);
$sorted = $ar;
sort($sorted);
var_dump($ar);
var_dump($sorted);
Result:
array(7,2,0,3,8,0,12,7,6,7);
array(0,0,2,3,6,7,7,7,8,12);
But i'm wondering what the foreach($ar as $v)$count[$v]++; does... doesn't really make sense...
Adding some comments to the code to show you why this doesn't do what you think it should do.
$ar = array(7, 2, 0, 3, 8, 0, 12, 7, 6, 7);
$count = array();
foreach ($ar as $v) {
// add each number in $ar to $count.
// the first number in $ar is 7, so 7 will be the first number in $count.
// because 7 is in $ar 3 times, $count[7] == 3.
$count[$v]++;
}
// the output of print_r will be very revealing:
print_r($count);
/*Array
(
[7] => 3
[2] => 1
[0] => 2
[3] => 1
[8] => 1
[12] => 1
[6] => 1
)*/
$sorted = array();
foreach ($count as $v => $c) {
// the first entry: $count[7] == 3
// so add 7 to $sorted 3 times.
// the second entry: $count[2] == 1
// so add 2 to $sorted 1 time.
// etc.
for ($i = 0; $i < $c; $i++) {
$sorted[] = $v;
}
}
This simply groups numbers together based on their location in the first array.
To get the sorted $ar in a variable of it's own ($sorted), it's pretty trivial:
$sorted = $ar;
sort($sorted);
Which makes me think that your question and comment is not giving the whole picture.
Edit: Now after you have clarified that you wanted to implement a specific algorithm (and you actually got an answer already that shows some points that were wrong implementing it first), I think it's worth to focus on another aspect of your question:
You're comparing the complexity of two (theoretical) algorithms, but you're leaving aside how the algorithms are implemented.
PHP's sort() - even based on "bad" quicksort, will outrun your own PHP usercode implementation of some other algorithm by numbers.
You just have compared the wrong parameters here. The complexity of a function does not says much when you compare a build-in PHP function with some function in your user-code.
$A = [1, 2, 2, 2, 1, 3, 3, 1, 2, 4, 5, 0, 0]; // example array
$m = max($A);
$count = array_fill(0, $m + 1, '0');
foreach ($A as $value) $count[$value] += 1;
// next step is print the numbers
$a = [];
foreach ($count as $key => $value) {
for ($i = 0; $i < $value;) {
array_push($a, $key);;
$i++;
}
}
var_dump($count); // print the sorted array
var_dump($a); // print the numbers (low to high)

Categories