tags:

views:

441

answers:

5

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:

  1. 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.

  2. 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.

  3. The next best photos are 265 wide. If I find one I want to use it and stop looking.

  4. The next best photos are 600px wide. Same deal.

  5. 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?

A: 

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.
soulmerge
you've got 'ponits' in your foreach instead of 'points'
Dominic Rodger
thx. I really hope this was not the reason it got downvoted. And I would appreciate it if people left comments to explain *why* they considered an answer bad.
soulmerge
No downvotes from me - not sure why this answer was downvoted.
Dominic Rodger
not from me either. it's not optimal, but correct (didn't check the details tough). additionally, the combination of rules is not neccessary, as far as i understood the problem. also without combination, the whole second loop (and the searching for/sorting of the result-array) is not necessary anymore.
Schnalle
True, $highestKey could be calculated in the same loop. But I'll leave that as an excercise to the reader :)
soulmerge
A: 
<?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.

Schnalle
Could return from 5 as well since its not going to be higher.
Martijn Laarman
no, because a '-large'-entry could be further down the array.
Schnalle
array_reduce starts out with an initial value of 0, so the first time selectBestImage() is called, the first parameter will be 0. Other than that looks like a good solution.
soulmerge
@soulmerge: you're right - i didn't remember that. really strange behaviour imho ... and typical for php *sigh*.
Schnalle
A: 
// 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";
    }
}
bumperbox
A: 

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;
        }
    }
    // ...
}
soulmerge
A: 

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;
OIS