Reorder elements in array evenly - php

Say i have:
$array = (1,1,1,1,2,2,2,2,3,3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);
What I'm trying to achive is to reorder elements evenly in it.
PHP's function shuffle() don't fits here, because i want some distance between same digits. So 1's has to be somewhere in the beginning of array, in the middle and in the end too.
I google about Fisher-Yates_shuffle algorithm, but it seems to work exactly like shuffle().
Thanks in advance!

I think this is close to what you ask: A constant, reasonably even distribution of the items in an array.
// The input array. 0s are regarded as blanks.
$array = array(1,1,1,1,2,2,2,2,3,3,3,3,3,3,3,3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);
// Count the times each item occurs. PHP will probably have a function for that, but I don't know.
$counter = array();
foreach ($array as $item)
{
// Zeros are infill. Don't process them now, only process the other numbers and
// the zeros will occupy the remaining space.
if ($item === 0)
continue;
if (!array_key_exists($item, $counter))
$counter[$item] = 0;
$counter[$item]++;
}
// Reverse sort by quantity. This results in the best distribution.
arsort($counter);
// Pre-fill a new array with zeros.
$resultCount = count($array);
$result = array_fill(0, $resultCount, 0);
// Distribute the items in the array, depending on the number of times they occur.
foreach ($counter as $item => $count)
{
// Determine the division for this item, based on its count.
$step = $resultCount / $count;
// Add the item the right number of times.
for ($i = 0; $i < $count; $i++)
{
// Start with the index closest to the preferred one (based on the calculated step).
$index = 0;
$startIndex = (int)($step * $i);
// Count up until a right index is found.
for ($index = $startIndex; $index < $resultCount; $index++)
{
if ($result[$index] === 0)
{
$result[$index] = $item;
break;
}
}
// If no proper index was found, count fown from the starting index.
if ($index === $resultCount)
{
for ($index = $startIndex; $index >= 0; $index--)
{
if ($result[$index] === 0)
{
$result[$index] = $item;
break;
}
}
}
// Still no proper index found, that shouldn't be possible. There's always room.
if ($index === -1)
{
throw new Exception('This cannot not happen');
}
}
}
var_dump($result);
For array:
1,1,1,1,2,2,2,2,3,3,3,3,3,3,3,3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
It returns:
3,2,1,0,3,0,0,0,3,0,2,1,3,0,0,0,3,0,0,0,0,3,2,1,0,3,0,0,0,3,0,2,1,3,0,0,0,3,0,0,0,0
For array:
1,1,1,1,2,2,2,2,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,0,0
It returns:
4,4,3,4,3,4,2,4,3,4,2,4,3,4,1,4,3,4,1,4,0,4,4,3,4,3,4,2,4,3,4,2,4,3,4,1,4,3,4,1,4,0
Which I think is a neat distribution. Thanks to datdo for the idea of sorting the intermediate array.

Related

Consolidate array of numbers without exceeding a predefined maximum value per element

I'm trying to combine numbers in an array by adding them so that the max value can only by 30.
For example, this is my array:
array(10,30,10,10,15);
After combining the numbers in the array to items with a max value 30, the result should be:
array(30,30,15);
How to achieve this?
I'm trying to combine numbers in an array by adding them so that the
max value can only by 30
So, when you combine numbers, you can achieve the lowest possible set of values in your array and also make sure that max value remains 30 by:
First, sort them.
Second, keeping adding elements to sum till you are about to get a sum > 30.
Third, once an element can no longer be added to a sum, add the current sum in your array and make the current element as the new sum.
Code:
<?php
$arr = array(10,30,10,10,15);
sort($arr);
$res = [];
$curr_sum = 0;
foreach($arr as $each_value){
if($curr_sum + $each_value <= 30) $curr_sum += $each_value;
else{
$res[] = $curr_sum;
$curr_sum = $each_value;
}
}
$res[] = $curr_sum;
print_r($res);
Demo: https://3v4l.org/BYhuE
Update: If order of the numbers matters, seeing your current output, you could just use rsort() to show them in descending order.
rsort($res);
$total = array_sum(array(10,30,10,10,15)); //assign sum totals from orignal array
$maxValue = 30; //assign max value allowed in array
$numberOfWholeOccurancesOfMaxValue = floor($total/$maxValue);
$remainder = $total%$maxValue;
//build array
$i=0;
while ( $i < $numberOfWholeOccurancesOfMaxValue ){
$array[] = $maxValue;
$i++;
}
$array[] = $remainder;
print_r($array);
You can loop only once to get this,
$temp = array(10,30,10,10,15);
natsort($temp); // sorting to reduce hustle and complication
$result = [];
$i = 0;
$maxValue = 30;
foreach($temp as $v){
// checking sum is greater or value is greater or $v is greater than equal to
if(!empty($result[$i]) && (($result[$i]+$v) > $maxValue)){
$i++;
}
$result[$i] = (!empty($result[$i]) ? ($result[$i]+$v) : $v);
}
print_r($result);
Working demo.
I believe finding most space-optimized/compact result requires a nested loop. My advice resembles the firstFitDecreasing() function in this answer of mine except in this case the nested loops are accessing the same array. I've added a couple of simple conditions to prevent needless iterations.
rsort($array);
foreach ($array as $k1 => &$v1) {
if ($v1 >= $limit) {
continue;
}
foreach ($array as $k2 => $v2) {
if ($k1 !== $k2 && $v1 + $v2 <= $limit) {
$v1 += $v2;
unset($array[$k2]);
if ($v1 === $limit) {
continue 2;
}
}
}
}
rsort($array);
var_export($array);
By putting larger numbers before smaller numbers before processing AND by attempting to add multiple subsequent values to earlier values, having fewer total elements in the result is possible.
See my comparative demonstration.
I believe #Clint's answer is misinterpreting the task and is damaging the data by summing all values then distributing the max amounts in the result array.
With more challenging input data like $array = [10,30,5,10,5,13,14,15,10,5]; and $limit = 30;, my solution provides a more dense result versus #nice_dev's and #rahul's answers.

php implementation of finding cycle in minimum swaps

find out minimum number of adjacent swaps require to make the array sorted.
the approach I use is finding a repeated cycle within my array for example 3->1->4->2 is cycle of 4 so minimum swap of 3 with input of [3,1,4,2,5] to make it sorted [1,2,3,4,5]
I have three arrays, $old contains unsorted array, $arr is sorted, and $bool counts whether the position is visited or not
$old = $arr;
quicksort($arr, 0, count($arr) - 1);
$bool = array_fill(0, count($arr), false);
$count = 0;
$circle = 0;
for ($i = 0; $i < count($bool); $i++){
if ($arr[$i] !== $old[$i]) {
$circle ++;
$where = array_search($old[$i], $arr);
$bool[$i] = true;
if ($bool[$where]) {
$count += $circle;
$circle = 0;
}
}
}
return $count;
the idea is skipping element that is already in right index,
and mark element that is not, keeping count of cycle (i used $circle), once a right position is already marked, i conclude that a cycle is reached,
but this logic is not behaving what I expected, need some help.
I think what you are after is something like this, however I might be wrong.
I tried to keep it simple with a lot of loops to make it work, and explained the different steps in the code
<?php
$minSize = 3;
$arr = [3,1,2,6,9,3,5,2,6,9,3,5,1,8,4,6,6,9,3,0];
$repeats = [];
//outer loop
for($i = 0; $i < count($arr); $i++) {
$currValue = $arr[$i];
//get all the keys for where the value is the same as $currValue
//this gives a starting position to test our matching sets
$matches = array_keys($arr, $currValue);
//match loop
for($j = 0; $j < count($matches); $j++) {
$leftIndex = $currValue; //this needs to be reset for every set
$matchIndex = $matches[$j];
//now we do want matches only when the index is different, always it will always match
if($leftIndex === $matchIndex) {
continue;
}
$subsequentMatch = 0;
//isset so we don't go out of bounds of our array
while(isset($arr[$leftIndex], $arr[$matchIndex]) && $arr[$leftIndex] === $arr[$matchIndex]) {
$subsequentMatch++;
$leftIndex++;
$matchIndex++;
}
if($subsequentMatch >= $minSize) {
$repeats[] = array_slice($arr, $currValue, $subsequentMatch) ;
}
}
}
print_r($repeats);
See an example here : https://3v4l.org/cBWQ5
you could improve on this by doing
for($i = 0; $i < (count($arr) - $minSize ); $i++) {
because if the set is under $minSize it will never be added to the repeats
EDIT: based on comments, OP might be looking for this ?
<?php
$arr = [3,1,2,6,9,3,5,2,6,9,3,5,1,8,4,6,6,9,3,0];
$swapCount = 0;
usort($arr, function ($a, $b) use (&$swapCount) {
if($a === $b) {
return 0;
}
$swapCount++;
return $a <=> $b;
});
echo 'Arr Count: ' . count($arr) . PHP_EOL;
echo 'Swaps performed: ' . $swapCount;
This is a simple sort, I suppose you could write a usort and count with each different sort mechanism (the list is endless), and then find out the min value of that
list of sorting mechanism off the top of my head
Insert Sort
Bubble Sort
Quick Sort
Counting Sort
Comb Sort
Heap Sort
Merge Sort
Shell Sort
Odd Even Sort
You can google on how to implement these in PHP, there are probably more ways too.

remove duplicate arrays in loop

I have an associative array that might contain duplicates. I am trying to loop through the array and compare the current element with the next element in the array. If there is a duplicate, it should be removed.
The code below removes one instance of the element. In the test array I'm using, I have 3 duplicate part numbers, but my code only removes one. I'm left with two. I only want one to remain.
$length = count($items);
for($i = 0; $i < $length -1; $i++){
if($items[$i]['part_number'] == $items[$i+1]['part_number']){
unset($items[$i+1]);
$items = array_values($items);
}
}
What am I doing wrong here?
You need to loop backwards through the array, and delete the current item.
$length = count($items);
for($i = $length - 1; $i > 0; $i--){
if($items[$i]['part_number'] == $items[$i-1]['part_number']){
unset($items[$i]);
}
}
becuase your code is
The $ items value is in the for statement.
if you want unique array, you have to array_unique function
http://php.net/manual/en/function.array-unique.php
In your case after you unset element, $i++ in for loop, you reindexed your array and you skip one element. Add $i-- if you unset item. Or you can reindex your array after for loop.
This is also a very simple example you can start improving with.
<?php
$test = ['sample', 'sample', 'sample', 'not', 'not', 'no', 'no'];
$test2 = [];
$k = 0;
foreach ($test as $key => $value) {
if ($key == 0) {
$test2[$k] = $value;
$k++;
} else {
if ($test2[$k - 1] != $value) {
$test2[$k] = $value;
$k++;
}
}
}
$test = $test2;
var_dump($test);
One dirty hack is to check again if you have a duplicate by decreasing $i.
for($i = 0; $i < $length -1; $i++){
if($items[$i]['part_number'] == $items[$i+1]['part_number']){
unset($items[$i+1]);
$items = array_values($items);
$i--;
}
}
This way it will again test your previous value against next item in array.
So if 0==1, then next time if 0==2.
Your code did 0==1 then (2)==(3).

How to sort it better?

I have 2 arrays, both are multidimensional with same number of elements and same values, which are on different positions (those values are actually ID-s from my database, so one ID appears only once). How can I sort second array with values which are in first array?
For example - if first array looks like:
$array1[0][0] = 1;
$array1[0][x] = it doesn't matter what's here
$array1[1][0] = 4;
$array1[1][x] = it doesn't matter what's here
$array1[2][0] = 3;
$array1[2][x] = it doesn't matter what's here
...
how to sort second array so it would have same values as array1 on indexes [0][0], [1][0], [2][0], etc.
How I could solve problem is:
$i=0
while ($i < (count($array1)-2)){ // * check down
$find_id = $array1[$i][0];
// here I need to search for index of that ID in other array
$position = give_index($find_id, $array2);
// swapping positions
$temp = array2[$i][0];
$array2[$i][0] = $array2[$position][0];
$array2[$position][0] = $temp;
// increasing counter
i++;
}
function give_index($needle, $haystack){
for ($j = 0, $l = count($haystack); $j < $l; ++$j) {
if (in_array($needle, $haystack[$j][0])) return $j;
}
return false;
}
*There is only -2 because indexes start from 0 and also for the last element you don't need to check since it would be automatically sorted by last iteration of while-loop.
I don't find this solution good as I think that this is quite simple issue (maybe it's not even correct). Is there easier way in PHP that I'm missing?
This is the most efficient way I can think of:
function swap(&$a, &$b) {
$t = $a;
$a = $b;
$b = $t;
}
function find_index($id, $array, $from = 0) {
$index = false;
for ($i = $from, $c = count($array); $i < $c; $i++) {
if ($array[$i][0] == $id) {
$index = $i;
break;
}
}
return $index;
}
for ($i = 0, $c = count($array1); $i < ($c - 2); $i++) {
if ($array1[$i][0] != $array2[$i][0]) {
$fi = find_index($array1[$i][0], $array2, $i);
swap($array2[$i][0], $array2[$fi][0]);
}
}
What changes from yours?
I've defined a swap() function in order to swap any variable. That doesn't cost anything and makes everything look nicer. Also you can reuse that function later if you need to.
In the find_index (give_index in your code) we stop the loop once we find the correct index. Also we avoid the cost of an in_array function call.
We modified the find_index function to start only from the part of the array we haven't checked yet. Leading to a way more efficient way of scan the array.
In the for loop (a while loop was just wrong there) we stored the count of the array once, avoiding multiple calls.
Also we swap the $array2 values only if they are in the wrong place.
Other improvements
If you know anything else of the $array2 array you can make this even more performant. For example if you know that indexes are alternated like in $array1 you can change the main for loop from:
for ($i = 0, $c = count($array1); $i < ($c - 2); $i++) {
to
for ($i = 0, $c = count($array1); $i < ($c - 2); $i+2) {
(notice the $i+2 at the end) And you could do that in the find_index function as well.
Look into usort (http://php.net/manual/en/function.usort.php).
It provides a simple way to sort arrays using a user provided comparison function.

PHP Array shuffle, keeping unique

this is my first php script and problem, I've searched hours with no conclusion other than looping a function" too many laterations". but it doesn't solve my problem I've never studied programming or what ever so I'm hoping that there is an educated person to fill me in on this:
I have an array that contains 120 elements; consists of duplicates eg:
myArray = [0]= item_1, [1] = item _1, [2] = item_2, [3] = item_3 ect..
Briefly I'm trying to make a flash php pokermachine but I need these items in the array to be shuffled BUT I do not want the duplicates to be next to each other after the shuffle but I need the duplicates to be still in the array
I can't do a loop function to check this because it will change the shuffle too many times which will effect the odds of the game: below is what I currently have:
/ * Removed the link here that is no longer available */
you may notice at times it will double up with 2 items in the same reel
Basically I created the virtual reel dynamically with php.ini file
these values are repeatedly pushed into an array($virtualreel) so the value may appear 10 times in the reel and another value will appear 5 times variating the odds. Then after I take a random slice() from the $virtualreel to display 3 vars from this reel and repeat the loop 4 more times for the other reels, also I only can shuffle once as I want the slice() to be from the same reels array order
I only shuffle every new spin not running loop functions to shuffle if I double up on a slice(array,3 items).
hope I've explained what I'm after well enough to give you guys an idea.
You can use this function:
<?php
function shuffleArray($myArray) {
$value_count = array_count_values($myArray);
foreach($value_count as $key=>$value) {
if ($value > count($myArray)/2) {
return false;
}
}
$last_value = $myArray[count($myArray) - 1];
unset($myArray[count($myArray) - 1]);
$shuffle = array();
$last = false;
while (count($myArray) > 0) {
$keys = array_keys($myArray);
$i = round(rand(0, count($keys) - 1));
while ($last === $myArray[$keys[$i]]) {
$i = round(rand(0, count($keys) - 1));
}
$shuffle[] = $myArray[$keys[$i]];
$last = $myArray[$keys[$i]];
unset($myArray[$keys[$i]]);
}
if ($last_value === $last) {
$i = 0;
foreach($shuffle as $key=>$value) {
if ($value !== $last_value) {
$i = $key;
break;
}
}
array_splice($shuffle, $i + 1, 0, $last_value);
} else {
$shuffle[] = $last_value;
}
return $shuffle;
}
print_r(shuffleArray(array(1,5,5,3,7,7)));
Why not just:
Edit :
$shuffled = array();
while(count($to_shuffle) > 0):
$i = rand(0, count($to_shuffle)-1);
$shuffled[] = $to_shuffle[$i];
array_splice($to_shuffle, $i, 1,null);
endwhile;
I think this is what you were expecting, if you don't mind not preserving the association between keys and values.

Categories