I have an array of posts ids and an array of videos ids. I need to mix both to have something like "First is a video, then 3 normal posts then another video then 5 normal posts.. repeat... until the end. (ie : Video,normal,normal,normal,Video,normal*5 and repeat..)
My code works but how can i make it more clean and concise ?
$a1 = $video_posts_arr;
$a2 = $standard_posts_arr;
$res = [];
$tc1 = count($a1);
$tc2 = count($a2);
$tc = $tc1 + $tc2;
$i1 = 0;
$i2 = 0;
for ($i = 0; $i < $tc; $i++) {
// first video
if (isset( $a1[$i1] )) {
array_push($res,$a1[$i1]);
$i1++;
}
// next 3 normals
if (isset( $a2[$i2]) ) {
array_push($res,$a2[$i2]);
$i2++;
array_push($res,$a2[$i2]);
$i2++;
array_push($res,$a2[$i2]);
$i2++;
}
// next video
if (isset( $a1[$i1] )) {
array_push($res,$a1[$i1]);
$i1++;
}
// next 5 normals
if (isset( $a2[$i2]) ) {
array_push($res,$a2[$i2]);
$i2++;
array_push($res,$a2[$i2]);
$i2++;
array_push($res,$a2[$i2]);
$i2++;
array_push($res,$a2[$i2]);
$i2++;
array_push($res,$a2[$i2]);
$i2++;
}
}```
It's kind of hard to tell from your wording, but I'm assuming you're wanting to go back and forth between 3 "regular" posts, then 5, then 3, etc, between each "video" post. So as we loop through the video posts, we have to toggle back and forth between those options, and then use that plus a marker/counter to know where to insert each video post in the list of regular posts.
$a1 = array("v1", "v2", "v3", "v4", "v5");
$a2 = array("p1", "p2", "p3", "p4", "p5", "p6", "p7", "p8", "p9", "p10", "p11", "p12", "p13", "p14", "p15", "p16");
$skipNum = 3;
$currentPos = 1;
array_unshift($a2, array_shift($a1));
foreach($a1 as $v)
{
$currentPos+= $skipNum;
array_splice($a2, $currentPos, 0, $v);
$currentPos++;
if($skipNum == 3)
{
$skipNum = 5;
}
else
{
$skipNum = 3;
}
}
var_dump($a2);
DEMO
how can i make it more clean and concise ?
Here's what I think you should do...
Iterate on the video array and inject each video element into the standard post array at the variable positions without making any replacements.
Code: (Demo)
$video_posts_arr = range('a','e');
$standard_posts_arr = range(1, 16);
$pos = 0;
foreach ($video_posts_arr as $index => $video) {
array_splice($standard_posts_arr, $index + $pos, 0, $video);
$pos += $index & 1 ? 5 : 3;
}
var_export($standard_posts_arr);
Using the $index of the videos array spares having to use incrementation while iterating.
I am using a ternary expression with a bitwise condition to increase the $pos based on whether $index is odd. I should express these variables to better explain what is happening. As shown in this Demo, these are the values generated as $index increases:
$index = 0, $pos = 0; splicePoint = 0, isOdd = false
$index = 1, $pos = 3; splicePoint = 4, isOdd = true
$index = 2, $pos = 8; splicePoint = 10, isOdd = false
$index = 3, $pos = 11; splicePoint = 14, isOdd = true
$index = 4, $pos = 16; splicePoint = 20, isOdd = false
Output:
array (
0 => 'a',
1 => 1,
2 => 2,
3 => 3,
4 => 'b',
5 => 4,
6 => 5,
7 => 6,
8 => 7,
9 => 8,
10 => 'c',
11 => 9,
12 => 10,
13 => 11,
14 => 'd',
15 => 12,
16 => 13,
17 => 14,
18 => 15,
19 => 16,
20 => 'e',
)
The above script is demonstrated using a ratio of videos to standard posts which results in no consecutive videos.
If your video to standard post ratio is too great, then the above script will simply append all remaining/extra videos to the end of the standard posts array.
If you would like to ensure that the final element in your standard posts array is not a video, then you can add a conditional break like this:
$pos = 0;
foreach ($video_posts_arr as $index => $video) {
$injection_pos = $index + $pos;
if (!isset($standard_posts_arr[$injection_pos])) {
break;
}
array_splice($standard_posts_arr, $injection_pos, 0, $video);
$pos += $index & 1 ? 5 : 3;
}
Or to allow a maximum of one video after the last standard post, move the break after the array_splice() call and account for the injected video element like this:
$pos = 0;
foreach ($video_posts_arr as $index => $video) {
$injection_pos = $index + $pos;
array_splice($standard_posts_arr, $injection_pos, 0, $video);
if (!isset($standard_posts_arr[$injection_pos + 1])) {
break;
}
$pos += $index & 1 ? 5 : 3;
}
Maybe something like that?
This solution uses array_merge and array_slice to create result. This methods does not edit original arrays, just creates new one based on arguments.
$a1 = array("v1", "v2", "v3");
$a2 = array("a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9", "a10");
$output = array_merge(
array_slice($a1, 0, 1), //take one arg from a1. Not smartest to be honest but works. Maybe someone will give better idea here.
array_slice($a2, 0, 3), //take first 3 elements from a2
array_slice($a1, 1, 1),
array_slice($a2, 3, 5),
array_slice($a2, 300, 500), //it is save :)
);
print_r($output);
# [0] => v1
# [1] => a1
# [2] => a2
# [3] => a3
# [4] => v2
# [5] => a4
# [6] => a5
# [7] => a6
# [8] => a7
# [9] => a8
# [10] => a9
# [11] => a10
array_slice that takes only one argument array_slice($a1, 0, 1) looks bit cryptic but at least it is save to use.
This'll create new array instead of editing existing one. It comes with some additional overhead (You probably should not worry about it).
http://sandbox.onlinephpfunctions.com/code/9b23381b31ab7ed48f1941cbca981071eaf250af
It seems that a large, complicated codebase depends on the order arsort produces. Before I dive in to discern what's actually happening in like 50 classes -- is there a simple way to shuffle items with equal values?
In other words, if the input is
['foo' => 3, 'bar' => 3, 'baz' => 3, 'this' => 2, 'that' => 2]
I'd like to get
['baz' => 3, 'bar' => 3, 'foo' => 3, 'this' => 2, 'that' => 2]
one run maybe, then
['baz' => 3, 'bar' => 3, 'foo' => 3, 'that' => 2, 'this' => 2]
on another random run.
How about something like this? (Untested)
Worst Case complexity: O(k)
Note: Written for algorithmic clarity and not PHP details...
function shuffleInput( $data ) {
// Separate into sets.
$sets = [];
foreach ( $data as $k => $v ) {
$sets[$v][] = $k;
}
// Shuffle & Join.
$data = [];
foreach ( $sets as $v => &$set ) {
shuffle( $set );
foreach( $set as $k ) {
$data[$k] = $v;
}
}
return $data;
}
Depending on the size of your input, it might be a better idea to unset every element in $data in the first loop instead of just creating a new array. This applies if data is very large and memory is precious to you - as well as reducing any sudden spikes & dips in memory usage.
Also, if you're going to continously shuffle the same $data around you might want to separate out the making of $sets to some other place or at least allow the developer to pass/get it as a side effect.
If you do not want to deal with shuffle, but rather prefer to check all permutations of the array, then you can do something like this:
$arr = array('foo' => 3, 'bar' => 3, 'baz' => 3, 'this' => 2, 'that' => 2);
$keys = array_keys($arr);
$indexes = range(0, count($arr) - 1);
pc_permute($indexes, $perms);
var_dump($perms);
function pc_permute($items, &$ret = array(), $perms = array( )) {
if (empty($items)) {
$ret[] = $perms;
} else {
for ($i = count($items) - 1; $i >= 0; --$i) {
$newitems = $items;
$newperms = $perms;
list($foo) = array_splice($newitems, $i, 1);
array_unshift($newperms, $foo);
pc_permute($newitems, $ret, $newperms);
}
}
}
Array $perms will give all permutations of the indexes, key name by index you can get from $keys and value by key or index (use array_slice) from $arr :)
ps: but you should understand - more elements you have in the original array, more permutations you will find. if there are n elements then there will be n! permutations. for n = 5 there are 120 permutations.
I want to get generate histogram data for the following array. array_count_values() would be great to use, except it just counts values for an exact value match. How can I elegantly do the same, but bundle values by a given step or interval?
$dataArray = array(385,515,975,1136,2394,2436,4051,4399,4484,4768,4768,4849,4856,4954,5020,5020,5020,5020,5020,5020,5020,5020,5020,5052,5163,5200,5271,5421,5421,5442,5746,5765,5903,5992,5992,6046,6122,6205,6208,6239,6310,6360,6416,6512,6536,6543,6581,6609,6696,6699,6752,6796,6806,6855,6859,6886,6906,6911,6923,6953,7016,7072,7086,7089,7110,7232,7278,7293,7304,7309,7348,7367,7378,7380,7419,7453,7454,7492,7506,7549,7563,7721,7723,7731,7745,7750,7751,7783,7791,7813,7813,7814,7818,7833,7863,7875,7886,7887,7902,7907,7935,7942,7942,7948,7973,7995,8002,8013,8013,8015,8024,8025,8030,8038,8041,8050,8056,8060,8064,8071,8081,8082,8085,8093,8124,8139,8142,8167,8179,8204,8214,8223,8225,8247,8248,8253,8258,8264,8265,8265,8269,8277,8278,8289,8300,8312,8314,8323,8328,8334,8363,8369,8390,8397,8399,8399,8401,8436,8442,8456,8457,8471,8474,8483,8503,8511,8516,8533,8560,8571,8575,8583,8592,8593,8626,8635,8635,8644,8659,8685,8695,8695,8702,8714,8715,8717,8729,8732,8740,8743,8750,8756,8772,8772,8778,8797,8828,8840,8840,8843,8856,8865,8874,8876,8878,8885,8887,8893,8896,8905,8910,8955,8970,8971,8991,8995,9014,9016,9042,9043,9063,9069,9104,9106,9107,9116,9131,9157,9227,9359,9471);
// if array_count_values accepted a step value I could do this:
print_r(array_count_values($dataArray,1000));
// expected result:
// array(1000 => 3, 2000 => 1, ... 10000 => 15);
// ^0-1000 ^[385,515,975]
What do you recommend?
In the event I have to loop through all values manually, is there an elegant way to round all values to a given interval?
$step = 1000;
$result = array_count_values(
array_map(
function ($value) use ($step) {
return (int) ceil($value / $step) * $step;
},
$dataArray
)
);
var_dump($result);
The rounding solution seems pretty straight forward:
$step_size = 10;
$data = array(10, 20, 24, 30, 35, 50);
foreach ($data as $index => $value) {
$data[$index] = round($value / $step_size) * $step_size;
}
// array(10, 20, 20, 30, 40, 50);
You can build the output directly to avoid mapping the entire data array just to make use of array_count_values(); below is a more generic implementation that allows the mapping to be done outside of the function itself:
function array_count_values_callback(array $data, callable $fn)
{
$freq = [];
foreach ($data as $item) {
$key = $fn($item);
$freq[$key] = isset($freq[$key]) ? $freq[$key] + 1 : 1;
}
return $freq;
}
print_r(array_count_values_callback($dataArray, function($item) {
return ceil($item / 1000) * 1000;
}));
Here is a simple solution where you loop through your $dataArray,
$step_size = 1000;
$histogramArray = array();
foreach ($dataArray as $v) {
$k = (int)ceil($v / $step_size) * $step_size;
if (!array_key_exists($k, $histogramArray)) $histogramArray[$k] = 0;
$histogramArray[$k]++;
}
And the output would be,
Array
(
[1000] => 3
[2000] => 1
[3000] => 2
[5000] => 8
[6000] => 21
[7000] => 25
[8000] => 46
[9000] => 110
[10000] => 15
)
I got array of points:
array(
array('x' => ...,'y' => ...),
array('x' => ...,'y' => ...)
...
)
What is the best way to make those points one, that is in "avarge" position? Is pairing and then pairing and then pairing... a good algorithm?
I would give myself -1 for this question, as it seems to be really easy, but I am working on project for more than 20 hours now, and my brain went off.
Hmm... is it as simple as counting avarge x and avarge y?
The best solution would be to turn your computer off and go to sleep for a couple of good hours. Then wake up rested and ready for a new programming session. This solution in based on an assumption that those 20 hours you have assigned this project was without any proper breaks.
While this isn't a direct answer to your question, it will certainly help you get there by yourself. Don't underestimate the power of a nap.
Take a look at this function I wrote to loop through all the elements of your multidimensional array and return their average after adding all of the elements together.
print_r(getAveragePoints(array(array('x' => 1,'y' => 3),array('x' => 2,'y' => 4))));
function getAveragePoints($arrays = array()) {
if(!empty($arrays)) {
$i=0;
$x = 0;
$y = 0;
foreach($arrays as $array) {
// this would take avg
$x += $array['x']; // x
$y += $array['y']; // y
$i++;
}
$avgX = $x / $i;
$avgY = $y / $i;
return array($avgX,$avgY);
} else {
return array(0,0);
}
}
Simple average option:
$pointArray = array(
array('x' => 1,'y' => 2),
array('x' => 2,'y' => 5),
array('x' => 3,'y' => 3) ,
array('x' => 4,'y' => 6) ,
array('x' => 4,'y' => 5) ,
);
$valueCount = count($pointArray);
$midpoint = array_map(
function($value) use($valueCount) {
return $value / $valueCount;
},
array_reduce(
$pointArray,
function($summary, $value) {
return array(
'x' => $summary['x'] += $value['x'],
'y' => $summary['y'] += $value['y']
);
},
array('x' => 0, 'y' => 0)
)
);
var_dump($midpoint);