I have an array with numerical incrementing keys starting at 0, like 0 1 2 3 4 5 ....etc. I need to assign new keys to the array in the following manner
The first three keys keep their index number
Every 4th key gets incremented by 4
The two keys after the fourth gets incremented by 1again
I know I need to use a foreach loop ( seems the simplest way anyway ) to build a new array with newly assigned keys. My problem is the calculation of the keys
HERE IS WHAT I HAVE SEEN IN RELATION
Here is my current array keys
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
and this is what I need them to be
0 1 2 6 7 8 12 13 14 18 19 20 24 25 26 30
Lets break this down into 3 key groups with the old array key on the top and the new array key at the bottom
1st group
0 1 2
0 1 2
2nd group
3 4 5
6 7 8
3rd group
6 7 8
12 13 14
4th group
9 10 11
18 19 20
etc etc......
When you look at the relationship between the two array keys, you'll see the following:
The old array keys 0, 1 and 2 keep their key value
The old array keys 3, 6, 9 etc, if multiplied by 2, gives you the new array key value
The old array keys 4, 7, 10 etc, if multiplied by 2 and you subtract 1 from that total, you get the new array key for that specific key
The old array keys 5, 8, 11 etc, if multiplied by 2 and you subtract 2 from the total, you get the new array key for that specific key
Alternatively, if you subtract the old key from the new key, you get the following answer
0 for the 1st group
3 for the 2nd group
6 for the 3rd group
9 for the 4th group
etc etc.....
POSSIBLE SOLUTION
The only solution I can think of is to use the modulus operator (inside my foreach loop), check the current key against the modulus result and then calculate my new key
Example:
if ( $key%3 == 0 && $key != 0 ) {
$new_key = $key * 2;
} elseif ( ( $key - 1 ) %3 == 0 && $key != 1 ) {
$new_key = ( $key * 2 ) - 1;
} elseif ( ( $key - 2 ) %3 == 0 && $key != 2 ) {
$new_key = ( $key * 2 ) - 2;
} else {
$new_key = $key;
}
$new_array[$new_key] = $value;
MY QUESTION
Isn't there a smarter more mathematical way of doing this?
Try the following:
$new_key = floor($old_key / 3) * 3 + $old_key
If you use modulous you can generate a sequence with items missing. The below code demonstrates how to do that.
for ($x = 0; $x < 40; $x++)
{
echo ("$x, " . ($x % 6) . ", " . ($x % 6 < 3? "true": "false") . "\n");
}
The output is:
0, 0, true
1, 1, true
2, 2, true
3, 3, false
4, 4, false
5, 5, false
6, 0, true
7, 1, true
8, 2, true
9, 3, false
10, 4, false
11, 5, false
12, 0, true
13, 1, true
14, 2, true
15, 3, false
16, 4, false
17, 5, false
18, 0, true
19, 1, true
20, 2, true
21, 3, false
22, 4, false
23, 5, false
24, 0, true
25, 1, true
26, 2, true
27, 3, false
28, 4, false
29, 5, false
30, 0, true
31, 1, true
32, 2, true
33, 3, false
34, 4, false
35, 5, false
36, 0, true
37, 1, true
38, 2, true
39, 3, false
If you use $x as a key only when $x % 6 < 3 you can get the sequence you want.
$array = range(1,15);
For ($i=0; $i<count($array); $i++) echo $keys[] = $i + floor($i/3)*3;
$new_array = array_combine($keys, $array);
var_dump($new_array);
Related
I have a problem with aligning numbers from a multidimensional array. I want to print the following result:
1 2 3 4
12 13 14 5
11 16 15 6
10 9 8 7
And I want all of the numbers to be aligned with the second digit of the next rows. However my result is that:
1 2 3 4
12 13 14 5
11 16 15 6
10 9 8 7
I did this in C# by using:
for (int col = 0; col < matrix.GetLength(1); col++)
{
Console.Write("{0,4}", matrix[row, col]);
}
But how can I receive this result in PHP?
You can use str_pad
$arr = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
$test = '';
foreach ($arr as $key => $value) {
if ($key % 4 == 0) {
$test .= "\n";
}
$test .= str_pad($value, 4, ' ', STR_PAD_LEFT);
}
echo "<pre>$test</pre>";
The result would be:
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
I wish to create an array with random sequences that correspond to my conditions.
The sequence should be composed of 8 different numbers from 1-8.
I should not have more than 2 successive numbers in the first 4 or last 4 numbers. Ex: 1,2,3 is not good nor 5,3,4 because they are successive if sorted (3,4,5).
This is a good example: 1,4,5,7, | 8,6,3,2
This is NOT a good example: 1,3,2,6, | 5,7,8,4 because 1,3,2 are successive numbers if sorted (1,2,3) in the first 4 digits
I made this:
$sequences = array();
while(count($sequences) < 100){
//Random 8 numbers sequence from 1-8
$sequence = array();
while(count($sequence) < 8){
$rand = rand(1,8);
if(!in_array($rand, $sequence)){
array_push($sequence, $rand);
}
}
//Insert if numbers are not successive.
//Struggling here
if(?????){
array_push($sequences, $sequence);
}
}
print_r($sequences);
It's working for the generation part of it but I can't figure how to insert sequences that do not contain successive numbers. Any thoughts?
You can represent the two groups of digits as a 1 or 0 and pick which elements go in each set by picking one of the 8 bit binary strings with four 1s and 0s and no sequences of 3 consecutive 1s or 0s. Each of these binary strings representes by one of the following integers:
43 45 51 53 54 75 77 83 85 86 89 90 101 102 105 106 108
147 149 150 153 154 165 166 169 170 172 178 180 201 202 204 210 212
The second half are all symmetric with the first half, so we can just pick a number from the first half and then do some processing to pick the whole set:
$combinations = array(43,45,51,53,54,75,77,83,85,86,89,90,101,102,105,106,108);
// Pick a random combination
$combination = $combinations[array_rand($combinations)];
if (mt_rand() & 1)
$combination = 255 - $combination;
$combination = str_split(str_pad(decbin($combination), 8, '0', STR_PAD_LEFT));
// Get the first four values
$first = array_keys(array_filter($combination, function($x){ return $x == '0'; }));
shuffle($first); // Permute them
// Get the last four values
$last = array_keys(array_filter($combination, function($x){ return $x == '1'; }));
shuffle($last); // Permute them
$result = array_map(function($x){ return $x + 1; }, array_merge($first, $last));
This will a random sequence with your constraints uniformly at random and should be quite efficient. Some sample outputs:
[6, 2, 3, 8, 1, 4, 5, 7]
[1, 6, 2, 4, 3, 5, 8, 7]
[6, 3, 1, 8, 2, 4, 5, 7]
[5, 8, 4, 2, 7, 1, 6, 3]
[5, 2, 8, 1, 3, 6, 7, 4]
[8, 6, 2, 4, 7, 1, 3, 5]
[7, 1, 5, 4, 8, 3, 2, 6]
[5, 1, 3, 6, 2, 7, 8, 4]
[1, 7, 2, 5, 3, 8, 4, 6]
[5, 6, 3, 8, 2, 7, 4, 1]
This is probably an ineffective way (in terms of resources) and could be more considered as a hack, but;
$sequence = array(1,2,4,5,8,9,5,7);
usort($sequence, function($a, $b) {
//if $a + 1 = $b, then $b + 1;
return ($a + 1) == $b ? $b = $b + 1 : 1;
});
print_r($sequence);
This would return;
Array ( [0] => 5 [1] => 8 [2] => 2 [3] => 1 [4] => 4 [5] => 9 [6] => 5 [7] => 7 )
It's not really a beautiful code, I could shorten it but still, it's working.
$sequence = array();
$sequences = array();
$loops = 0;
while($loops < 10000){
$loops++;
//Get a sequence
while(count($sequence) < 8){
$rand = rand(1,8);
if(!in_array($rand, $sequence)){
array_push($sequence, $rand);
}
}
//Checks if sequence meet requirements
//This part could be summarized in a function
$first = array($sequence[0], $sequence[1], $sequence[2],$sequence[3]);
$last = array($sequence[4], $sequence[5], $sequence[6],$sequence[7]);
sort($first);
sort($last);
if($first[1] != $first[0]+1 || $first[1] !=$first[2]-1){
if($first[2] != $first[1]+1 || $first[2] !=$first[3]-1){
if($last[1] != $last[0]+1 || $last[1] !=$last[2]-1){
if($last[2] != $last[1]+1 || $last[2] !=$last[3]-1){
$sequence = array_merge($first, $last);
if(!in_array($sequence, $sequences)){
array_push($sequences, $sequence);
}
}
}
}
}
$sequence = array();
}
print_r($sequences);
The code in question comes from MathGuard, a PHP anti-spam CAPTCHA script that requires the user to answer a simple math problem. It displays the digits and operator symbols as 3x5 matrices of random characters. I understand how the code works in the sense that I can follow the code and understand what it's doing; I just don't understand how one would come to this solution.
This function takes an integer that describes one line of the 3x5 matrix and converts it into a line of random characters:
function decToBin($dec) {
$pattern = "123456789ABCDEFGHIJKLMNOPQRTSTUWXYZ";
$output = " ";
$i = 0;
do {
if ($dec % 2) {
$rand = rand() % 34;
$output { 2 - $i } = $pattern { $rand };
} else {
$output { 2 - $i } = " ";
}
$dec = (int) ($dec / 2);
$i++;
} while ($dec > 0);
$output = str_replace(" ", " ", $output);
return $output;
}
Here are the digit descriptors:
$number = array (
array ( 7, 5, 5, 5, 7 ), // 0
array ( 2, 6, 2, 2, 7 ), // 1
array ( 7, 1, 7, 4, 7 ), // 2
array ( 7, 1, 7, 1, 7 ), // 3
array ( 4, 5, 7, 1, 1 ), // 4
array ( 7, 4, 7, 1, 7 ), // 5
array ( 7, 4, 7, 5, 7 ), // 6
array ( 7, 1, 1, 1, 1 ), // 7
array ( 7, 5, 7, 5, 7 ), // 8
array ( 7, 5, 7, 1, 7 ) // 9
);
My question is: how does one come to this conclusion and method of generation and know that, for example, 7 will generate a full line of random characters, and 5 only the outermost characters?
Is this just a form of code obfuscation? What makes this method better than, say, storing the digits as a string (111101101101111 as 0, for example) and replacing each 1 with a random character?
Looks like a simple bitmap to me.
7 = 1 1 1
5 = 1 0 1
5 = 1 0 1
5 = 1 0 1
7 = 1 1 1
2 = 0 1 0
6 = 1 1 0
2 = 0 1 0
2 = 0 1 0
7 = 1 1 1
I am working with database data that manipulates college students exam results. Basically, I am pulling the records from a MySQL database and pulling one class at any given time. I want to rank the students with the highest performer given the rank of 1.
Here is an illustration;
Marks: 37, 92, 84, 83, 84, 65, 41, 38, 38, 84.
I want to capture MySQL data as a single array. Once I have the data in an array, I should then assign each student a position in the class such as 1/10 (number 1, the 92 score), 4/10 etc. Now the problem is that if there is a tie, then the next score skips a position and if there are 3 scores at one position then the next score skips 2 positions. So the scores above would be ranked as follows;
92 - 1
84 - 2,
84 - 2,
84 - 2,
83 - 5,
65 - 6,
41 - 7,
38 - 8,
38 - 8 ,
37 - 10
The grading system requires that the number of positions (ranks, if you will) will be maintained, so we ended up with 10 positions in this class since positions 3, 4, 5 and 9 did not have any occupants. (The alternative of filling every number will have given us only 8 positions!)
Is it possible (humanly/programmatically possible) to use PHP to rank the scores above in such a way that it can handle possible ties such as 4 scores at one position? Sadly, I could not come up with a function to do this. I need a PHP function (or something in PHP) that will take an array and produce a ranking as above.
If it's possible to do this with MySQL query data without having it in an array, then that will also be helpful!
I assume the grades are already sorted by the database, otherwise use sort($grades);.
Code:
$grades = array(92, 84, 84, 84, 83, 65, 41, 38, 38, 37);
$occurrences = array_count_values($grades);
$grades = array_unique($grades);
foreach($grades as $grade) {
echo str_repeat($grade .' - '.($i+1).'<br>',$occurrences[$grade]);
$i += $occurrences[$grade];
}
Result:
92 - 1
84 - 2
84 - 2
84 - 2
83 - 5
65 - 6
41 - 7
38 - 8
38 - 8
37 - 10
EDIT (Response to discussion below)
Apparently, in case the tie occurs at the lowest score,
the rank of all lowest scores should be equal to the total count of scores.
Code:
$grades = array(92, 84, 84, 84, 83, 65, 41, 38, 37, 37);
$occurrences = array_count_values($grades);
$grades = array_unique($grades);
foreach($grades as $grade) {
if($grade == end($grades))$i += $occurrences[$grade]-1;
echo str_repeat($grade .' - '.($i+1).'<br>',$occurrences[$grade]);
$i += $occurrences[$grade];
}
Result:
92 - 1
84 - 2
84 - 2
84 - 2
83 - 5
65 - 6
41 - 7
38 - 8
37 - 10
37 - 10
$scores = array(92, 84, 84, 84, 83, 65, 41, 38, 38, 37);
$ranks = array(1);
for ($i = 1; $i < count($scores); $i++)
{
if ($scores[$i] != $scores[$i-1])
$ranks[$i] = $i + 1;
else
$ranks[$i] = $ranks[$i-1];
}
print_r($ranks);
I needed to end up with a map of values to rank. This method may be more efficient for the original question too.
public static function getGrades($grades)
{
$occurrences = array_count_values($grades);
krsort($occurrences);
$position = 1;
foreach ($occurrences as $score => $count) {
$occurrences[$score] = $position;
$position += $count;
}
return $occurrences;
}
If you print_r on $occurrences you get
Array
(
[92] => 1
[84] => 2
[83] => 5
[65] => 6
[41] => 7
[38] => 8
[37] => 10
)
Based on the original answer, so thanks!
Using array_count_values() followed by a foreach() is doing 2 loops over the input array, but this task can be done will one loop (minimizing/optimizing the time complexity).
Code: (Demo)
// assumed already rsort()ed.
$scores = [92, 84, 84, 84, 83, 65, 41, 38, 38, 37];
$gappedRank = 0;
$result = [];
foreach ($scores as $score) {
++$gappedRank;
$gappedRanks[$score] ??= $gappedRank;
$result[] = [$score => $gappedRanks[$score]];
}
var_export($result);
For a flat, associative lookup array of scores and their rank, unconditionally increment the counter and only push a new element into the lookup array if the key will be new. (Demo)
$gappedRank = 0;
$lookup = [];
foreach ($scores as $score) {
++$gappedRank;
$lookup[$score] ??= $gappedRank;
}
var_export($lookup);
The first snippet provides "gapped ranking". I have another answer which implements a similar approach but with a different input data structure and with the intent of modifying row data while looping.
Get dense rank and gapped rank for all items in array
In the realm of ranking, there is also "dense ranking". See my time complexity optimized answers at:
Populate multidimensional array's rank column with dense rank number
Add order column to array to indicate rank from oldest to youngest
Normally, I'd be asking how to turn a 4-rowed, 3-columned array like this:
1 2 3
4 5 6
7 8 9
10 11 12
Into a 3-rowed, 4-columned array like: (I DON'T WANT THIS)
1 4 7 10
2 5 8 11
3 6 9 12
But actually, I want to turn it into this: (I WANT THIS)
1 5 9
2 6 10
3 7 11
4 8 12
In other words, I want to flip the rows and columns, but keep the same "width" and "height" of the new array. I've been stuck on this for over an hour.
This is the function I'm using to do a normal "flip" (the first example):
function flip($arr)
{
$out = array();
foreach ($arr as $key => $subarr)
{
foreach ($subarr as $subkey => $subvalue)
{
$out[$subkey][$key] = $subvalue;
}
}
return $out;
}
Just walk the array in the correct order. Assuming you have relatively small arrays, the easiest solution is just to create a brand new array during that walk.
A solution will be of the form:
$rows = count($arr);
$ridx = 0;
$cidx = 0;
$out = array();
foreach($arr as $rowidx => $row){
foreach($row as $colidx => $val){
$out[$ridx][$cidx] = $val;
$ridx++;
if($ridx >= $rows){
$cidx++;
$ridx = 0;
}
}
}
function flip_row_col_array($array) {
$out = array();
foreach ($array as $rowkey => $row) {
foreach($row as $colkey => $col){
$out[$colkey][$rowkey]=$col;
}
}
return $out;
}
First, what you don't want (which is half of the solution to what you do want)...
The term for "flipping rows and columns" is "transposing".
PHP has had a sleek native technique for this very action since the splat operator was added to the language.
The caveats to bear in mind are:
All keys must be numeric. The splat/spread operator will choke on non-numeric keys.
The matrix must be complete. If there are any gaps, you may not get the result that you desire.
Code: (Demo)
$matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
[10, 11, 12],
];
var_export(
array_map(null, ...$matrix)
);
Output:
[
[1, 4, 7, 10],
[2, 5, 8, 11],
[3, 6, 9, 12],
];
Now, for what you do want!
Here is a functional-style snippet that incorporates php's transposing technique while ensuring that the output has the same number of columns as the input.
Code: (Demo)
var_export(
array_map(
null,
...array_chunk(
array_merge(...$matrix),
count($matrix)
)
)
);
Output:
[
[1, 5, 9],
[2, 6, 10],
[3, 7, 11],
[4, 8, 12],
];
This approach flattens the input, then breaks it into rows with lengths equivalent to the original number of rows, then that result is transposed.
Late Edit: As a purely academic pursuit, I wanted to see what a pure mathematical technique would look like which didn't use any conditions and didn't maintain multiple "counters".
As it turns out, because php arrays will truncate float keys to integers, this can be done in a single loop.
// assuming the earlier mentioned 3x4 matrix:
i old pos new pos i%rows i/rows i/col i%col
0 : [0][0] => [0][0] 0 0 0 0
1 : [1][0] => [0][1] 1 0.25 0.3 1
2 : [2][0] => [0][2] 2 0.5 0.6 2
3 : [3][0] => [1][0] 3 0.75 1 0
4 : [0][1] => [1][1] 0 1 1.3 1
5 : [1][1] => [1][2] 1 1.25 1.6 2
6 : [2][1] => [2][0] 2 1.5 2 0
7 : [3][1] => [2][1] 3 1.75 2.3 1
8 : [0][2] => [2][2] 0 2 2.6 2
9 : [1][2] => [3][0] 1 2.25 3 0
10 : [2][2] => [3][1] 2 2.5 3.3 1
11 : [3][2] => [3][2] 3 2.75 3.6 2
Code: (Demo)
$rows = count($matrix);
$cols = count(current($matrix));
$cells = $rows * $cols;
$result = $matrix; // used to preserve original key orders
for ($i = 0; $i < $cells; ++$i) {
$result[$i % $rows][$i / $rows] = $matrix[$i / $cols][$i % $cols];
}
var_export($result);
here you go. It works. :)
Demonstration
$input = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
[10, 11, 12],
];
// flipping matrices
$output = array();
$intern = array();
for($row=0; $row < 4; $row++)
for($col=0;$col < 3;$col++)
$intern[] = $input[$row][$col];
// nesting the array
$count = 0;
$subcount = 0;
foreach($intern as $value)
{
$output[$count][$subcount] = $value;
$count++;
if($subcount == 3)
{
break;
}
if($count == 4)
{
$count = 0;
$subcount++;
}
}
echo "\n final output ";print_r($output);