I got this challenge to find the highest product of 4 consecutive numbers on a 20x20 matrix of integers.
The numbers are read line by line from a file separated by a space.
The products can be in horizontal, vertical and diagonal in both directions
My "solution" gives the wrong answer.
EDIT: I've updated the code to work without file input and added sample data; also fixed one of my mistakes that were pointed out in the comments
$data = [
[89,32,92,64,81,2,20,33,44,1,70,75,39,62,76,35,16,77,22,27],
[53,11,6,95,41,51,31,59,8,23,19,13,61,91,48,69,84,52,66,24],
[93,72,85,97,21,79,56,5,45,3,65,30,83,87,43,7,34,0,4,14],
[29,17,49,9,82,90,55,67,15,63,54,94,12,28,96,37,58,98,86,78],
[74,40,50,60,26,99,80,18,10,46,36,68,25,57,47,71,42,73,88,38],
[50,22,6,26,18,53,52,5,46,2,89,77,83,48,4,58,45,28,84,81],
[49,82,31,14,69,17,91,54,34,40,0,33,30,95,60,44,29,24,85,16],
[27,11,76,39,15,86,92,74,99,59,94,12,55,57,38,96,47,32,78,75],
[51,20,87,42,62,41,7,35,23,21,71,25,67,97,80,90,88,64,13,70],
[19,9,56,43,68,93,65,98,36,3,61,63,10,72,8,73,1,66,79,37],
[22,58,52,12,3,41,28,72,42,74,76,64,59,35,85,78,14,27,53,88],
[46,80,5,96,7,68,61,69,67,34,36,40,82,26,75,50,29,91,10,2],
[30,39,19,48,33,93,1,45,66,98,0,23,62,25,51,71,56,77,24,21],
[79,87,94,60,8,32,13,65,4,92,73,9,31,37,17,84,15,90,86,20],
[95,6,81,70,47,16,44,83,49,43,55,54,18,63,38,11,97,89,99,57],
[95,78,64,58,7,17,53,28,74,86,6,12,54,85,21,94,16,69,25,68],
[13,20,41,97,1,2,80,30,0,84,67,45,93,96,82,92,62,33,18,44],
[60,77,31,70,76,36,59,38,15,3,91,46,65,73,49,11,8,35,5,52],
[61,66,79,40,26,72,89,71,75,99,22,9,43,32,14,81,98,88,87,83],
[10,4,23,19,56,57,51,47,50,27,90,63,42,29,24,55,48,37,39,34]
];
$matrix = [];
//maximums in possible directions
$maxes = [0, 0, 0, 0];
//while ($line = trim(fgets(STDIN))) {
while ($line = current($data)) {
//the horizontal maxes can be calculated while loading
//$array = explode(" ", $line);
$array = $line;
$hMax = array_product(array_slice($array, 0, 4));
for ($i = 1; $i < (count($array)-4); $i++) {
$max = array_product(array_slice($array, $i, 4));
if($max > $hMax) {
$hMax = $max;
}
}
if ( $hMax > $maxes[0] ) {
$maxes[0] = $hMax;
}
$matrix[] = $array;
next($data);
}
// the last 3 rows can be skipped
for($i = 0; $i < (count($matrix)-4); $i++) {
for ($j = 0; $j < (count($matrix[$i])-1); $j++) {
$vMax = 1; // vertical
$dlMax = 1; // diagonal left
$drMax = 1; // diagonal rigth
for ($k = 0; $k < 5; $k++) {
$vMax *= $matrix[$i + $k][$j];
if ( $j < (count($matrix[$i]) - 4) ) {
$drMax *= $matrix[$i + $k][$j + $k];
}
if ( $j > 3 ) {
$dlMax *= $matrix[$i + $k][$j - $k];
}
}
if ( $maxes[1] < $vMax ) $maxes[1] = $vMax; // the index used to be 1 - my first mistake
if ( $maxes[2] < $dlMax ) $maxes[2] = $dlMax; // the index used to be 1 - my first mistake
if ( $maxes[3] < $drMax ) $maxes[3] = $drMax; // the index used to be 1 - my first mistake
}
}
sort($maxes);
echo end($maxes).PHP_EOL;
Where did my approach go wrong, and how can it be sped up?
Are there any math tricks that can be applied here (besides checking for zeros)?
EDIT: the solution that the code gives for the current data is 4912231320 is it correct?
I've found 2 major errors, and now the result is a plausible 67352832
I'm considering it solved for that reason, but if anyone comes up with some math trick that simplifies or makes it faster I'll give up the accepted answer.
The first mistake was
for ($k = 0; $k < 5; $k++) {
It should've been
for ($k = 0; $k < 4; $k++) {
since we are only counting 4 numbers at once, thats why the result was so large compared to 10^8
The second was
if ( $j > 3 ) {
which should've been
if ( $j > 2 ) {
which will now include one more diagonal possibility
We can consider the four directions a bottom- or right-most cell can be the last of in a sequence. If m[i][j][k][d] is the highest total for a sequence of length k coming from direction d, then:
m[i][j][1][d] = data[i][j] for all d
m[i][j][k]['E'] = data[i][j] * m[i][j - 1][k - 1]['E']
m[i][j][k]['NE'] = data[i][j] * m[i - 1][j - 1][k - 1]['NE']
m[i][j][k]['N'] = data[i][j] * m[i - 1][j][k - 1]['N']
m[i][j][k]['NW'] = data[i][j] * m[i - 1][j + 1][k - 1]['NW']
If we traverse north to south, east to west, the needed cells should have already been calculated, and, clearly, we're looking for
max(m[i][j][4][d])
for all i, j, d
I want to print all combination of sub range in an given array. I have an array of y number of elements in it from which I want to print all combination of contiguous sub range.
Constraint is : each sub range should have at least 2 elements and each element in sub range should be contiguous. It should share same border of each element.
For example, We have an array of 7 elements [11,12,13,14,11,12,13]
So, the total number of sub range combination will [7 * (7-1) /2] = 21
So, the Output will be something like this:
11,12
12,13
13,14
14,11
11,12
12,13
11,12,13
12,13,14
13,14,11
...
11,12,13,14 and so on (total 21 combination as per above array)
we should not print any combination which is not contiguous. example: [11,12,14] is not valid combination as it skips the element "13" in between.
I am able to print the combination with 2 elements but i am having difficulty in printing more then 2 elements combination.
Below is what I have tried so far.
$data=array("11","12","13","14","11","12","13");
$totalCount=count($data);
for($i=0;$i<$totalCount;$i++){
if(($i+1) < ($totalCount)){
echo "[".$data[$i].",".$data[$i+1]."]<br>";
}
}
You can do that:
$arr = [11,12,13,14,11,12,13];
function genComb($arr, $from = 1, $to = -1) {
$arraySize = count($arr);
if ($to == -1) $to = $arraySize;
$sizeLimit = $to + 1;
for ($i = $from; $i < $sizeLimit; $i++) { // size loop
$indexLimit = $arraySize - $i + 1;
for ($j = 0; $j < $indexLimit; $j++) { // position loop
yield array_slice($arr, $j, $i);
}
}
}
$count = 0;
foreach (genComb($arr, 2) as $item) {
echo implode(',', $item), PHP_EOL;
$count++;
}
echo "total: $count\n";
Casimir et Hippolyte was faster, but you can gain huge performance by processing each contiguous section independently:
function getCombos(&$data) {
$combos = array();
$count = count($data);
$i = 0;
while ($i < $count) {
$start = $i++;
while ($i < $count && $data[$i - 1] + 1 == $data[$i]) // look for contiguous items
$i++;
if ($i - $start > 1) // only add if there are at least 2
addCombos($data, $start, $i, $combos); // see other answer
}
return $combos;
}
So, I want to distribute evenly lists across 3 columns. The lists cannot be broken up or reordered.
At the moment, I have 5 lists each containing respectively 4, 4, 6, 3 and 3 items.
My initial approach was:
$lists = [4,4,6,3,3];
$columns = 3;
$total_links = 20;
$items_per_column = ceil($total_links/$columns);
$current_column = 1;
$counter = 0;
$lists_by_column = [];
foreach ($lists as $total_items) {
$counter += $total_items;
$lists_by_column[$current_column][] = $total_items;
if ($counter > $current_column*$links_per_column) {
$current_column++;
}
}
Results in:
[
[4],
[4,6],
[3,3]
]
But, I want it to look like this:
[
[4,4],
[6],
[3,3]
]
I want to always have the least possible variation in length between the columns.
Other examples of expected results:
[6,4,4,6] => [[6], [4,4], [6]]
[4,4,4,4,6] => [[4,4], [4,4], [6]]
[10,4,4,3,5] => [[10], [4,4], [3,5]]
[2,2,4,6,4,3,3,3] => [[2,2,4], [6,4], [3,3,3]]
Roughly what you need to do is loop over the number of columns within your foreach(). That will distribute them for you.
$numrows = ceil(count($lists) / $columns);
$thisrow = 1;
foreach ($lists as $total_items) {
if($thisrow < $numrows){
for($i = 1; $i <= $columns; $i++){
$lists_by_column[$i][] = $total_items;
}
}else{
//this is the last row
//find out how many columns need to fit.
//1 column is easy, it goes in the first column
//2 columns is when you'll need to skip the middle one
//3 columns is easy because it's full
}
$thisrow++;
}
This will be an even distribution, from left to right. But you actually want a modified even distribution that will look symmetrical to the eye. So within the foreach loop, you'll need to keep track of 1.) if you're on the last row of three, and 2.) if there are 2 remainders, to have it skip col2 and push to col3 instead. You'll need to set that up to be able to play around with it,...but you're just a couple of logic gates away from the land of milk and honey.
So, I ended up using this code:
$lists = [4,4,6,3,3];
$columns = 3;
$total_links = 20;
$items_per_column = ceil($total_links/$columns);
$current_column = 1;
$lists_by_column = [];
for ($i = 0; $i < count($lists); $i++) {
$total = $lists[$i];
$lists_by_column[$current_column][] = $lists[$i];
//Loop until reaching the end of the column
while ($total < $items_per_column && $i+1 < count($lists)) {
if ($total + $lists[$i+1] > $items_per_column) {
break;
}
$i++;
$total += $lists[$i];
$lists_by_column[$current_column][] = $lists[$i];
}
//When exiting the loop the last time we need another break
if (!isset($lists[$i+1])) {break;}
//If the last item goes onto the next column
if (abs($total - $items_per_column) < abs($total + $lists[$i+1] - $items_per_column)) {
$current_column++;
//If the last item goes onto the current column
} else if ($total + $lists[$i+1] > $items_per_column) {
$i++;
$lists_by_column[$current_column][] = $lists[$i];
$current_column++;
}
}