Help pulling out data from a PHP array based on 5 rules - php

I'm working with arrays of image filepaths. A typical array might have 5 image filepaths stored in it.
For each array, I want to pull out just the "best" photo to display as a thumbnail for the collection.
I find looping and arrays very confusing and after 4 hours of trying to figure out how to structure this, I'm at a loss.
Here are the rules I'm working with:
The very best photos have "-large" in their filepaths. Not all arrays will have images like this in them, but if they do, that's always the photo I want to pluck out.
The next best photos are 260px wide. I can look this up with getimagesize. If I find one of these, I want to stop looking and use it.
The next best photos are 265 wide. If I find one I want to use it and stop looking.
The next best photos are 600px wide. Same deal.
Then 220px wide.
Do I need 5 separate for loops? 5 nested for-loops
Here's what I'm trying:
if $image_array{
loop through $image_array looking for "-large"
if you find it, print it and break;
if you didn't find it, loop through $image_array looking for 260px wide.
if you find it, print it and break;
}
and so on....
But this doesn't appear to be working.
I want to "search" my array for the best single image based on these criteria. If it can't find the first type, then it looks for the second on so on. How's that done?

// predefined list of image qualities (higher number = best quality)
// you can add more levels as you see fit
$quality_levels = array(
260 => 4,
265 => 3,
600 => 2,
220 => 1
);
if ($image_arry) {
$best_image = null;
// first search for "-large" in filename
// because looping through array of strings is faster then getimagesize
foreach ($image_arry as $filename) {
if (strpos('-large', $filename) !== false) {
$best_image = $filename;
break;
}
}
// only do this loop if -large image doesn't exist
if ($best_image == null) {
$best_quality_so_far = 0;
foreach ($image_arry as $filename) {
$size = getimagesize($filename);
$width = $size[0];
// translate width into quality level
$quality = $quality_levels[$width];
if ($quality > $best_quality_so_far) {
$best_quality_so_far = $quality;
$best_image = $filename;
}
}
}
// we should have best image now
if ($best == null) {
echo "no image found";
} else {
echo "best image is $best";
}
}

Another approach (trivial, less generic, slower). Just check rules one by one:
function getBestFile($files) {
foreach ($files as $arrayKey => $file) {
if (strstr($file, '-large') !== FALSE) {
return $file;
}
}
foreach ($files as $arrayKey => $file) {
if (is260wide($file)) {
return $file;
}
}
// ...
}

You need 3 loops and a default selection.
loop through $image_array looking for "-large"
if you find it, return it;
if you didn't find it, loop through $image_array
get image width
if prefered width (260px), return it.
if $sizes[$width] not set, add filename
loop a list of prefered sizes in order and see if it is set in $sizes
if you find it, return it;
return the first image or default image;

<?php
// decide if 1 or 2 is better
function selectBestImage($image1, $image2) {
// fix for strange array_filter behaviour
if ($image1 === 0)
return $image2;
list($path1, $info1) = $image1;
list($path2, $info2) = $image2;
$width1 = $info1[0];
$width2 = $info2[0];
// ugly if-block :(
if ($width1 == 260) {
return $image1;
} elseif ($width2 == 260) {
return $image2;
} elseif ($width1 == 265) {
return $image1;
} elseif ($width2 == 265) {
return $image2;
} elseif ($width1 == 600) {
return $image1;
} elseif ($width2 == 600) {
return $image2;
} elseif ($width1 == 220) {
return $image1;
} elseif ($width2 == 220) {
return $image2;
} else {
// nothing applied, so both are suboptimal
// just return one of them
return $image1;
}
}
function getBestImage($images) {
// step 1: is the absolutley best solution present?
foreach ($images as $key => $image) {
if (strpos($image, '-large') !== false) {
// yes! take it and ignore the rest.
return $image;
}
}
// step 2: no best solution
// prepare image widths so we don't have to get them more than once
foreach ($images as $key => $image) {
$images[$key] = array($image, getImageInfo($image));
}
// step 3: filter based on width
$bestImage = array_reduce($images, 'selectBestImage');
// the [0] index is because we have an array of 2-index arrays - ($path, $info)
return $bestImage[0];
}
$images = array('image1.png', 'image-large.png', 'image-foo.png', ...);
$bestImage = getBestImage($images);
?>
this should work (i didn't test it), but it is suboptimal.
how does it work? first, we look for the absolutely best result, in this case, -large, because looking for a substrings is inexpensive (in comparsion).
if we don't find a -large image we have to analyze the image widths (more expensive! - so we pre-calculate them).
array_reduce calls a filtering function that takes 2 array values and replaces those two by the one return by the function (the better one). this is repeated until there is only one value left in the array.
this solution is still suboptimal, because comparisons (even if they're cheap) are done more than once. my big-O() notation skills are a bit (ha!) rusty, but i think it's O(n*logn). soulmerges solution is the better one - O(n) :)
you could still improve soulmerges solution, because the second loop is not necessary:
first, pack it into a function so you have return as a break-replacement. if the first strstr matches, return the value and ignore the rest. afterwards, you don't have to store the score for every array key. just compare to the highestKey variable and if the new value is higher, store it.
<?php
function getBestImage($images) {
$highestScore = 0;
$highestPath = '';
foreach ($images as $image) {
if (strpos($image, '-large') !== false) {
return $image;
} else {
list($width) = getImageInfo($image);
if ($width == 260 && $highestScore < 5) {
$highestScore = 5;
$highestPath = $image;
} elseif ($width == 265 && $highestScore < 4) {
$highestScore = 4;
$highestPath = $image;
} elseif ($width == 600 && $highestScore < 3) {
$highestScore = 3;
$highestPath = $image;
} elseif ($width == 220 && $highestScore < 2) {
$highestScore = 2;
$highestPath = $image;
} elseif ($highestScore < 1) {
// the loser case
$highestScore = 1;
$highestPath = $image;
}
}
}
return $highestPath;
}
$bestImage = getBestImage($images);
?>
didn't test, should work in O(n). can't imagine a faster, more efficient way atm.

I would assign points to the files depending on how many rules apply. If you want certain rules to supercede others, you can give more points for that rule.
define('RULE_POINTS_LARGE', 10);
define('RULE_POINTS_260_WIDE', 5);
// ...
$points = array();
foreach ($files as $arrayKey => $file) {
$points[$arrayKey] = 0;
if (strstr($filename, '-large') !== FALSE) {
$points[$arrayKey] += RULE_POINTS_LARGE;
}
// if ...
}
// find the highest value in the array:
$highestKey = 0;
$highestPoints = 0;
foreach ($points as $arrayKey => $points) {
if ($files[$arrayKey] > $highestPoints) {
$highestPoints = $files[$arrayKey];
$highestKey = $arrayKey;
}
}
// The best picture is $files[$highestKey]
One more side note: Givign your rules multiples of a value will ensure that a rule can be 'stronger' than all others. Example: 5 rules -> rule values (1, 2, 4, 8, 16).
1 < 2
1 + 2 < 4
1 + 2 + 4 < 8
etc.

Related

Laravel foreaching random models

I am making seeders for a M:M relation where I would like to attach 1 Widget to WorkspaceItem in 90% of cases, other 5% 2, last 5% 3.
$widgets = Widget::all();
$workspaceItems = WorkspaceItem::all();
foreach ($workspaceItems as $workspaceItem) {
$numberBetween = $faker->numberBetween(0, 100);
if ($numberBetween > 95) {
$widgetsToSeed = $widgets->random(3);
} else if ($numberBetween > 90 && $numberBetween <= 95) {
$widgetsToSeed = $widgets->random(2);
} else {
$widgetsToSeed = $widgets->random();
}
foreach ($widgetsToSeed as $widget) {
$workspaceItem->widgets()->attach($widget->id, [...]);
}
}
Note: I can't use sync() because I have additional properties for pivot table.
If I dd($widgetsToSeed), I indeed get random widgets. But as soon as it enters the loop and I dd($widget) I don't get the model, but just true. What seems to be the problem?
I think you should be able to just do:
$workspaceItem->widgets()->saveMany($widgetsToSeed);
And by that you don't even need the last foreach loop. To always receive a collection (even with only one element), you can also add random(1) in your last else statement. With some minor simplifications it might look like this:
foreach ($workspaceItems as $workspaceItem) {
$numberBetween = $faker->numberBetween(0, 100);
if ($numberBetween > 95) {
$widgetsToSeed = $widgets->random(3);
} else if ($numberBetween > 90) {
$widgetsToSeed = $widgets->random(2);
} else {
$widgetsToSeed = $widgets->random(1);
}
$workspaceItem->widgets()->saveMany($widgetsToSeed);
}

PHP multiple conditions for if statement that changes font color

I've some problems with this PHP code:
// HIGHLIGHT NUMBERS *************************************************
// BUY
$valueEnlightOverBuy = $_POST["enlightOverBuy"];
$valueEnlightUnderBuy = $_POST["enlightUnderBuy"];
// GREEN PRICE BUY
$fontColorBuy = "#FFF";
if ((($valueEnlightOverBuy != '') or ($valueEnlightUnderBuy != '')) and (($valueEnlightOverBuy and $valueEnlightUnderSell) != "0")) {
if (($finalPriceBuyer >= $valueEnlightOverBuy) or ($finalPriceBuyer <= $valueEnlightUnderBuy)) {
$fontColorBuy = "#00FF00";
} else if (($finalPriceBuyer >= $valueEnlightOverBuy and $finalPriceBuyer <= $valueEnlightUnderBuy)) {
$fontColorBuy = "#FF00FF";
} else {
$fontColorBuy = "#D00000";
}};
// SELL LOCAL
$valueEnlightOverSloc = $_POST["enlightOverSloc"];
$valueEnlightUnderSloc = $_POST["enlightUnderSloc"];
// GREEN PRICE SELL LOCAL
$fontColorSellLoc = "#FFF";
if ((($valueEnlightOverSloc != '') or ($valueEnlightUnderSloc != '')) & (($valueEnlightOverSloc & $valueEnlightUnderSloc) != "0")) {
if (($finalPriceSellerLocal >= $valueEnlightOverSloc) or ($finalPriceSellerLocal <= $valueEnlightUnderSloc)) {
$fontColorSellLoc = "#00FF00";
} else if (($finalPriceSellerLocal >= $valueEnlightOverSloc) and ($finalPriceSellerLocal <= $valueEnlightUnderSloc)) {
$fontColorSellLoc = "#FF00FF";
} else {
$fontColorSellLoc = "#D00000";
}};
// SELL INTERNATIONAL
$valueEnlightOverSellInt = $_POST["enlightOverSellInt"];
$valueEnlightUnderSellInt = $_POST["enlightUnderSellInt"];
// GREEN PRICE SELL INTERNATIONAL
$fontColorSellInt = "#FFF";
if ((($valueEnlightOverSellInt != '') or ($valueEnlightUnderSellInt != '')) & (($valueEnlightOverSellInt & $valueEnlightUnderSellInt) != "0")) {
if (($finalPriceSellerInt >= $valueEnlightOverSellInt) or ($finalPriceSellerInt <= $valueEnlightUnderSellInt)) {
$fontColorSellInt = "#00FF00";
} else if (($finalPriceSellerInt >= $valueEnlightOverSellInt) and ($finalPriceSellerInt <= $valueEnlightUnderSellInt)) {
$fontColorSellInt = "#FF00FF";
} else {
$fontColorSellInt = "#D00000";
}};
As you see I have a post form (not showed in code) that transmit to this file the values for: VAR underBuy < BUY PRICE < VAR overBuy; VAR underLocalSeller < LOCAL SELL PRICE < VAR overLocalSeller; VAR underIntSeller < INT SELL PRICE < VAR underIntSeller.
Firstly the code makes a check
if the VAR received from form are EMPTY or = to 0; if it's not,
the code collect all this data and make a check on $FINALPRICEBUYER (extracted from a file with json) for the 1st two VARs, than make a check on $FINALPRICESELLERLOCAL for the 3rd and the 4th VARs and so make a check on $FINALPRICESELLERINT for the 5th and the 6th VARs.
If $ FINALPRICEBUYER is between >= than 1st VAR or <= than 2nd VAR make font GREEN, else if $FINALPRICEBUYER between 3rd VAR and 4th VAR go VIOLET,
else go RED.
The same for $FINALPRICESELLERLOCAL and $FINALPRICESELLERINT.
I did the first two conditions because user can insert one price limit or two.
Could you help me to understand what's I'm doing wrong? (Maybe it's only a matter of logic).
Here is the question:
This code doesn't work. Many time it return green font in spite of violet, so there should be something in the logic structure of math symbols (or PHP code of course) I can't get...
UPDATE 29.06.2015
Here is the code I'm using and adapting, starting from on of your examples.
// ENLIGHT NUMBERS ***************************************************
// get all input variables at once
$over_buy = $_POST['enlightOverBuy'];
$under_buy = $_POST['enlightUnderBuy'];
$over_loc = $_POST['enlightOverSloc'];
$under_loc = $_POST['enlightUnderSloc'];
$over_int = $_POST['enlightOverSellInt'];
$under_int = $_POST['enlightUnderSellInt'];
$final_buy = $finalPriceBuyer;
$final_loc = $finalPriceSellerLocal;
$final_int = $finalPriceSellerInt;
// now set the colors
$buy = getFontColor( $over_buy, $under_buy, $final_buy );
$loc = getFontColor( $over_loc, $under_loc, $final_loc );
$int = getFontColor( $over_int, $under_int, $final_int );
So nothing different here.
Now the function:
// function to return color based on input
function getFontColor( $over, $under, $final ) {
// colors
$white = '#fff';
$green = '#0f0';
$violet = '#f0f';
$red = '#d00000';
Fist step: if function get the "0" number font will be white so:
if ($final != '0') {
Second step: if both the "value" are sent by the form
if (!empty($over) and !empty($under)) {
Third step: if FINAL is a number between over and under
if ($final >= $over && $final <= $under) {
go GREEN
return $green;
if not
} else {
go RED
return $red;
}
Fourth step: else if one of the "value", or the other one, is empty
} else if (!empty($over) or !empty($under)) {
Fifth step: if FINAL is a number higher than "over" value, or lower than "under" value
if ($final >= $over or $final <= $under) {
go GREEN
return $green;
else go RED
} else {
return $red;
}
Sixth step: in any other case, go WHITE
} else {
return $white;
}
}};
So I would have GREEN if $final is in between two values received from form, but if you have only one value input (so under or over), if $final is higher than over or lower than under, go GREEN.
In any other caso, go RED, if $final is not "0".
Here is the full code without spaces:
// ENLIGHT NUMBERS ***************************************************
// get all input variables at once
$over_buy = $_POST['enlightOverBuy'];
$under_buy = $_POST['enlightUnderBuy'];
$over_loc = $_POST['enlightOverSloc'];
$under_loc = $_POST['enlightUnderSloc'];
$over_int = $_POST['enlightOverSellInt'];
$under_int = $_POST['enlightUnderSellInt'];
$final_buy = $finalPriceBuyer;
$final_loc = $finalPriceSellerLocal;
$final_int = $finalPriceSellerInt;
// now set the colors
$buy = getFontColor( $over_buy, $under_buy, $final_buy );
$loc = getFontColor( $over_loc, $under_loc, $final_loc );
$int = getFontColor( $over_int, $under_int, $final_int );
// function to return color based on input
function getFontColor( $over, $under, $final ) {
// colors
$white = '#fff';
$green = '#0f0';
$violet = '#f0f';
$red = '#d00000';
if ($final != '0') {
if (!empty($over) and !empty($under)) {
if ($final >= $over && $final <= $under) {
return $green;
} else {
return $red;
}
} else if (!empty($over) or !empty($under)) {
if ($final >= $over or $final <= $under) {
return $green;
} else {
return $red;
}
} else {
return $white;
}
}};
It seems correct to me, but I'm receiving a strange result, the echos you will see were created by me to make a check of function behavior.
Please do not consider my Italian comments, but the result is a non-sense, considering in:
1st example: GREEN (0.127839 is NOT under 0.125, so should be RED)
RED (correct) - GREEN (correct)
2nd example: GREEN (0.154761 is NOT under 0.125, so should be RED) - RED (correct) - GREEN (correct)
3rd example: GREEN (0.14442 is NOT under 0.125, so should be RED) - RED (correct) - GREEN (correct)
4th example: GREEN (0.129195 is NOT under 0.125, so should be RED) - RED (correct) - RED (correct).
How is it possible that using the same function you get always an error on 1st usage and correct answer on the next 2 uses?
FINAL UPDATE
Here is the code:
$over_buy = number_format($_POST['enlightOverBuy'], 6);
$under_buy = number_format($_POST['enlightUnderBuy'], 6);
$over_loc = number_format($_POST['enlightOverSloc'], 6);
$under_loc = number_format($_POST['enlightUnderSloc'], 6);
$over_int = number_format($_POST['enlightOverSellInt'], 6);
$under_int = number_format($_POST['enlightUnderSellInt'], 6);
$final_buy = $finalPriceBuyer;
$final_loc = $finalPriceSellerLocal;
$final_int = $finalPriceSellerInt;
// now set the colors
$buy = getFontColor( $over_buy, $under_buy, $final_buy );
$loc = getFontColor( $over_loc, $under_loc, $final_loc );
$int = getFontColor( $over_int, $under_int, $final_int );
// function to return color based on input
function getFontColor( $over, $under, $final ) {
// colors
$white = '#fff';
$green = '#0f0';
$red = '#FF3300';
if ($final != '0') {
if (!empty($over) and !empty($under)) {
if ($final >= $over && $final <= $under) {
return $green;
} else {
return $red;
}
} elseif (!empty($over)) {
return ($final >= $over) ? $green : $red;
} elseif (!empty($under)) {
return ($final <= $under) ? $green : $red;
} else {
return $white;
}
}};
Many things to address here...
It looks like you could use a good reading on:
truth tables and karnaugh maps to help you get the apropriate expresion and simplify it
is_numeric, to check for a numeric input
operators in general, and in particular operator precedence
The diference between & (bitwise operator) and && (logical operator)
How to properly use if...elseif expresions. The elseif condition only gets evaluated when the if condition is not met, so if you put something like this in your code where the elseif condition is a subset of the if condition, the elseif condition will only be evaluated when it is not met, rendering it useless because the code inside it will never be executed:
.
if (($finalPriceBuyer >= $valueEnlightOverBuy) or ($finalPriceBuyer <= $valueEnlightUnderBuy)) {
$fontColorBuy = "#00FF00";
} else if (($finalPriceBuyer >= $valueEnlightOverBuy and $finalPriceBuyer <= $valueEnlightUnderBuy)) { ... }
More importantly, be lazy and DRY. If you are doing the same thing twice, put it into a function:
function getColor($value, $limitOver, $limitUnder, $colors){
$underCheck = !is_numeric($limitUnder) || $value >= $limitUnder;
$overCheck = !is_numeric($limitOver) || $value <= $limitOver;
if ($underCheck && $overCheck) {
// all valid bounds are satisfied
return $colors['none'];
} else if ($overCheck){
// valid lower bound exists and is not satisfied
// and upper bound does not exist or is not valid or is satisfied
return $colors['under'];
} else if ($underCheck){
// valid upper bound exists and is not satisfied
// and lower bound does not exist or is not valid or is satisfied
return $colors['over'];
} else {
// both bounds exist and none of them are satisfied
return $colors['both'];
}
}
$colors = array (
'both' => "#FF00FF",
'over' => "#D00000",
'under' => "#FFF",
'none' => "#00FF00",
);
$colorBuy = getColor(
$finalPriceBuyer,
$_POST["enlightOverBuy"],
$_POST["enlightUnderBuy"],
$colors
);
$colorLocal = getColor(
$finalPriceSellerLocal,
$_POST["enlightOverSloc"],
$_POST["enlightUnderSloc"],
$colors
);
$colorInt = getColor(
$finalPriceSellerInt,
$_POST["enlightOverSellInt"];,
$_POST["enlightUnderSellInt"],
$colors
);
You have not been exactly clear on your definition of the problem, so it is up to you to check if I have correctly interpreted your intentions and make the required modifications to suit your exact needs, but you get the idea.
You must check the most restrictive condition first. See this code:
// case A
if($z>$x && $z<$y) {}
// case B
if($z>$x || $z<$y) {}
Outcome:
$x | $y | $z | case A | case B
10 20 5 false true
10 20 15 true true
10 20 25 false true
Whenever case A evaluates as true, case B also does. But the reverse situation might not. Therefore if you put case B first in an IF ELSE, it will always return true before reaching case A.
Fixed code sample:
$valueEnlightOverBuy = $_POST["enlightOverBuy"];
$valueEnlightUnderBuy = $_POST["enlightUnderBuy"];
// GREEN PRICE BUY
$fontColorBuy = "#FFF";
// user has provided price boundaries
if($valueEnlightOverBuy !== '' && $valueEnlightUnderSell !== '') {
// satisfies both boundaries (violet, should be green ?)
if($finalPriceBuyer >= $valueEnlightOverBuy && $finalPriceBuyer <= $valueEnlightUnderBuy) {
$fontColorBuy = "#FF00FF";
}
// satisfies at least one boundary (green, should be violet ?)
elseif($finalPriceBuyer >= $valueEnlightOverBuy || $finalPriceBuyer <= $valueEnlightUnderBuy) {
$fontColorBuy = "#00FF00";
}
// outside the boundaries (wrong boundaries order) (red)
else {
$fontColorBuy = "#D00000";
}
}
Instinctively, I get the feeling that you actually want the AND condition to be green and the OR condition to be violet, but I can be wrong about that. And I must point out the red condtion only fires if the boundaries are set in the wrong order, although that seems appropriate.
Regarding update 2015-06-29:
In this code:
elseif(!empty($over) or !empty($under)) {
if ($final >= $over or $final <= $under) {
return $green;
}
else {
return $red;
}
}
In your examples, you get $green because $over is evaluated as zero, therefore any positive value will return green. You must only evaluate the value that is not empty.
elseif(!empty($over)) {
return ($final >= $over) ? $green : $red;
}
elseif(!empty($under)) {
return ($final <= $under) ? $green : $red;
}
NOTE: This question should likely be moved to the code review site.
I think your main problem is coding style and organization. Your code is hard to read and therefore harder to debug. Particularly when you are trying to implement complex logic, making sure that you have clean code is helpful. Here are some suggestions.
Use shorter variable names
User proper indentation
Use built-in PHP functions to simplify, e.g. empty()
Use functions to get rid of repetition
Group similar tasks in one place, e.g. getting values of $_POST variables
So first, I'll rewrite your code, following these suggestions:
// function to return color based on input
function getFontColor( $over, $under, $final ) {
// colors
$white = '#fff';
$green = '#0f0';
$violet = '#f0f';
$red = '#d00000';
if ( !empty($over) and !empty($under) ) {
if ( $final >= $over || $final <= $under ) return $green;
if ( $final >= $over && $final <= $under ) return $violet;
return $red;
} else {
return $white;
}
}
// get all input variables at once
$over_buy = $_POST['enlightOverBuy'];
$under_buy = $_POST['enlightUnderBuy'];
$over_loc = $_POST['enlightOverSloc'];
$under_loc = $_POST['enlightUnderSloc'];
$over_int = $_POST['enlightOverSellInt'];
$under_int = $_POST['enlightUnderSellInt'];
// now set the colors
$buy = getFontColor( $over_buy, $under_buy, $final_buy );
$loc = getFontColor( $over_loc, $under_loc, $final_loc );
$int = getFontColor( $over_int, $under_int, $final_int );
Once, we've done that, we can see at least one problem. The getFontColor() function above will NEVER return $violet because you have the OR statement before the AND statement. To correct that, the function should be rewritten as:
// function to return color based on input
function getFontColor( $over, $under, $final ) {
// colors
$white = '#fff';
$green = '#0f0';
$violet = '#f0f';
$red = '#d00000';
if ( !empty($over) and !empty($under) ) {
if ( $final >= $over && $final <= $under ) return $violet;
if ( $final >= $over || $final <= $under ) return $green;
return $red;
} else {
return $white;
}
}
However, even with this revision, I'm still not sure the code does what you expect it to do. In any case it should be easier to debug, now that it is simpler and cleaner. Hope this helps!

PHPExcel - Finding First Column With blank cell

Trying to locate the first blank cell in a column. The idea is to pick a column that I know has to have a value (in this case, JOB_NUMBER) and scan through it until a blank cell is found. The code below, in my mind, should do that. However, it never stops. I imagine it is stuck in the while loop, but I don't understand why.
Code:
<?php
require('./Classes/PHPExcel/IOFactory.php');
ini_set('max_execution_time', 800);
ini_set('memory_limit', 2000000000);
$inputFileType = 'Excel2007';
$inputFileName = $_FILES['file']['tmp_name'];
class MyReadFilter implements PHPExcel_Reader_IReadFilter {
public function __construct($fromColumn, $toColumn) {
$this->columns = array();
$toColumn++;
while ($fromColumn !== $toColumn) {
$this->columns[] = $fromColumn++;
}
}
public function readCell($column, $row, $worksheetName = '') {
// Read columns from 'A' to 'AF'
if (in_array($column, $this->columns)) {
return true;
}
return false;
}
}
$filterSubset = new MyReadFilter('A', 'AF');
$objReader = PHPExcel_IOFactory::createReader($inputFileType);
$objReader->setReadFilter($filterSubset);
$objReader->setLoadSheetsOnly( array("NORTH") );
$objPHPExcelReader = $objReader->load($inputFileName);
$r = 3500;
while(isset($maxrow_north) != 1){
$cellvalue = $objPHPExcelReader->getSheetByName('NORTH')->getCellByColumnAndRow(2, $r);
if(isset($cellvalue) != 1){
$maxrow_north = $r;
} elseif($r > 4000) {
echo "It's over 4000!";
} else {
$r = $r++;
}
}
echo $maxrow_north;
?>
Some more background
I am having admins upload .xlsx .xls or .csv files into an html form. The code, above, is the handler. I have limited the number of columns seen because the original creator of the .xlsx file thought it would be a great idea to have the columns go all the way out to XCF.
The rows also go all the way out to somewhere around 10,000. So, I want to find the first blank row and stop there.
TIA!
Don't use
if(isset($cellvalue) != 1){
A cell value always exists even if it's an empty string or a null: and you're not testing the actual cell value, but the existence of a cell.... simply get() ting a cell will create a new empty cell object if one didn't already exist
You need to test the actual value stored in the cell
if($cellvalue->getValue() === NULL || $cellvalue->getValue() === '') {
$maxrow_north = $r;
And if you're trying to find the first blank cell in the column, then break once you've found it rather than carry on iterating till you reach your max
(Note, doesn't check for rich text in cells)
EDIT
Example, that also allows for merged cells
function testInMergeRangeNotParent($objWorksheet, $cell)
{
$inMergeRange = false;
foreach($objWorksheet->getMergeCells() as $mergeRange) {
if ($cell->isInRange($mergeRange)) {
$range = PHPExcel_Cell::splitRange($mergeRange);
list($startCell) = $range[0];
if ($cell->getCoordinate() !== $startCell) {
$inMergeRange = true;
}
break;
}
}
return $inMergeRange;
}
$column = 2; // Column to check
$max = 4000;
echo 'Get First blank row in column ', $column, PHP_EOL;
$r = 3500; // Starting row
while(true){
$cell = $objPHPExcelReader->getSheetByName('NORTH')->getCellByColumnAndRow($column, $r);
if ($cell->getValue() === NULL &&
!testInMergeRangeNotParent($objPHPExcelReader->getSheetByName('NORTH'), $cell)) {
break;
}elseif($r > $max) {
echo "It's over $max !";
break;
}
$r++;
}
echo 'First blank row in column ', $column, ' is ', $r, PHP_EOL;

Echo once per foreach

So I have the following code:
$colors=$ex->Get_Color("images/avatarimage3.png", $num_results, $reduce_brightness, $reduce_gradients, $delta);
foreach ( $colors as $hex => $count )
{
if ($hex == 'e6af23' && $count > 0.05)
{
echo "The image has the correct colour";
}
else
{
echo "The image doesn't have the correct colour";
}
}
Basically this code at the moment grabs the hex value and percentage of colours than image contains and adds them to an array. The code above looks to see if the hex is a certain value and the percent above 5% and if it is then it displays the success message. This part works exactly as it should do!
Now, what I also want is that if the colour isn't correct, so for all other hex values in the array other than $hex == 'e6af23' I want it to display a failure message but only display it once and not for every time the hex isn't that value.
Basically I need it so that the failure message is only displayed once and not 5 times (the number of hex colours in the image).
You can use a flag to indicate if the message has been outputted or not, and if so then don't output again:
$colors=$ex->Get_Color("images/avatarimage3.png", $num_results, $reduce_brightness, $reduce_gradients, $delta);
$error_displayed = false;
foreach ( $colors as $hex => $count ) {
if ($hex == 'e6af23' && $count > 0.05) {
echo "The image has the correct colour";
} else if (!$error_displayed) {
echo "The image doesn't have the correct colour";
$error_displayed = true;
}
}
Just keep a list of colors you already echoed.
$failed = array();
forech ($colors as $hex) {
if (!in_array($hex, $failed) && $error) {
echo 'Failed at hex ' . $hex;
$failed[] = $hex;
}
}
Using NewFurnitureRay's answer as a guideline I came up with this answer:
$colors=$ex->Get_Color("images/avatarimage.png", $num_results, $reduce_brightness, $reduce_gradients, $delta);
$success = true;
foreach ( $colors as $hex => $count ) {
if ($hex !== 'e6af23') {$success = false; }
if ($hex == 'e6af23' && $count > 0.05) {$success = true; break;}
}
if ($success) { echo "Success"; } else { echo "This is a failure"; }
Seems to work now as it should only displaying either a success or a failure regardless of the success's position in the array :)

PHP loops to check that a set of numbers are consecutive

I'm trying to loop through a set of records, all of which have a "number" property. I am trying to check if there are 3 consecutive records, e.g 6, 7 and 8.
I think i'm almost there with the code below, have hit the wall though at the last stage - any help would be great!
$nums = array();
while (count($nums <= 3))
{
//run through entries (already in descending order by 'number'
foreach ($entries as $e)
{
//ignore if the number is already in the array, as duplicate numbers may exist
if (in_array($e->number, $num))
continue;
else
{
//store this number in the array
$num[] = $e->number;
}
//here i need to somehow check that the numbers stored are consecutive
}
}
function isConsecutive($array) {
return ((int)max($array)-(int)min($array) == (count($array)-1));
}
You can achieve the same result without looping, too.
If they just have to be consecutive, store a $last, and check to make sure $current == $last + 1.
If you're looking for n numbers that are consecutive, use the same, except also keep a counter of how many ones fulfilled that requirement.
$arr = Array(1,2,3,4,5,6,7,343,6543,234,23432,100,101,102,103,200,201,202,203,204);
for($i=0;$i<sizeof($arr);$i++)
{
if(isset($arr[$i+1]))
if($arr[$i]+1==$arr[$i+1])
{
if(isset($arr[$i+2]))
if($arr[$i]+2==$arr[$i+2])
{
if(isset($arr[$i+3]))
if($arr[$i]+3==$arr[$i+3])
{
echo 'I found it:',$arr[$i],'|',$arr[$i+1],'|',$arr[$i+2],'|',$arr[$i+3],'<br>';
}//if3
}//if 2
}//if 1
}
I haven't investigated it thoroughly, maybe can be improved to work faster!
This will confirm if all items of an array are consecutive either up or down.
You could update to return an array of [$up, $down] or another value instead if you need direction.
function areAllConsecutive($sequence)
{
$up = true;
$down = true;
foreach($sequence as $key => $item)
{
if($key > 0){
if(($item-1) != $prev) $up = false;
if(($item+1) != $prev) $down = false;
}
$prev = $item;
}
return $up || $down;
}
// areAllConsecutive([3,4,5,6]); // true
// areAllConsecutive([3,5,6,7]); // false
// areAllConsecutive([12,11,10,9]); // true
Here's an example that can check this requirement for a list of any size:
class MockNumber
{
public $number;
public function __construct($number)
{
$this->number = $number;
}
static public function IsListConsecutive(array $list)
{
$result = true;
foreach($list as $n)
{
if (isset($n_minus_one) && $n->number !== $n_minus_one->number + 1)
{
$result = false;
break;
}
$n_minus_one = $n;
}
return $result;
}
}
$list_consecutive = array(
new MockNumber(0)
,new MockNumber(1)
,new MockNumber(2)
,new MockNumber(3)
);
$list_not_consecutive = array(
new MockNumber(5)
,new MockNumber(1)
,new MockNumber(3)
,new MockNumber(2)
);
printf("list_consecutive %s consecutive\n", MockNumber::IsListConsecutive($list_consecutive) ? 'is' : 'is not');
// output: list_consecutive is consecutive
printf("list_not_consecutive %s consecutive\n", MockNumber::IsListConsecutive($list_not_consecutive) ? 'is' : 'is not');
// output: list_not_consecutive is not consecutive
If u don't wanna mess with any sorting, picking any of three numbers that are consecutive should give you:
- it either is adjacent to both the other numbers (diff1 = 1, diff2 = -1)
- the only number that is adjacent (diff = +-1) should comply the previous statement.
Test for the first condition. If it fails, test for the second one and under success, you've got your secuence; else the set doesn't comply.
Seems right to me. Hope it helps.
I think you need something like the following function (no need of arrays to store data)
<?php
function seqOfthree($entries) {
// entries has to be sorted descending on $e->number
$sequence = 0;
$lastNumber = 0;
foreach($entries as $e) {
if ($sequence==0 or ($e->number==$lastNumber-1)) {
$sequence--;
} else {
$sequence=1;
}
$lastNumber = $e->number;
if ($sequence ==3) {
// if you need the array of sequence you can obtain it easy
// return $records = range($lastNumber,$lastNumber+2);
return true;
}
}
// there isn't a sequence
return false;
}
function isConsecutive($array, $total_consecutive = 3, $consecutive_count = 1, $offset = 0) {
// if you run out of space, e.g. not enough array values left to full fill the required # of consecutive count
if ( $offset + ($total_consecutive - $consecutive_count ) > count($array) ) {
return false;
}
if ( $array[$offset] + 1 == $array[$offset + 1]) {
$consecutive_count+=1;
if ( $consecutive_count == $total_consecutive ) {
return true;
}
return isConsecutive($array, $total_consecutive, $consecutive_count, $offset+=1 );
} else {
return isConsecutive($array, $total_consecutive, 1, $offset+=1 );
}
}
The following function will return the index of the first of the consecutive elements, and false if none exist:
function findConsecutive(array $numbers)
{
for ($i = 0, $max = count($numbers) - 2; $i < $max; ++$i)
if ($numbers[$i] == $numbers[$i + 1] - 1 && $numbers[$i] == $numbers[$i + 2] - 2)
return $i;
return false;
}
Edit: This seemed to cause some confusion. Like strpos(), this function returns the position of the elements if any such exists. The position may be 0, which can evaluate to false. If you just need to see if they exist, then you can replace return $i; with return true;. You can also easily make it return the actual elements if you need to.
Edit 2: Fixed to actually find consecutive numbers.

Categories