views:

322

answers:

8

I often need to list items separated by comma, space or punctuation, addresses are a classic example (This is overkill for an address and is for the sake of an example!):

echo "L$level, $unit/$num $street, $suburb, $state $postcode, $country.";
//ouput: L2, 1/123 Cool St, Funky Town, ABC 2000, Australia.

As simple as it sounds, is there an easy way to "conditionally" add the custom separators between variables only if the variable exists? Is it necessary to check if each variable is set? So using the above, another address with less detail may output something like:

//L, / Cool St, , ABC , .

A slightly arduous way of checking would be to see if each variable is set and display the punctuation.

if($level){ echo "L$level, "; }
if($unit){ echo "$unit"; }
if($unit && $street){ echo "/"; }
if($street){ echo "$street, "; }
if($suburb){ echo "$suburb, "; }
//etc...

It would be good to have a function that could automatically do all the stripping/formatting etc:

somefunction("$unit/$num $street, $suburb, $state $postcode, $country.");


Another example is a simple csv list. I want to output x items separated by comma:

for($i=0; $i=<5; $i++;){ echo "$i,"; }
//output: 1,2,3,4,5,

In a loop for example, what's the best way of determining the last item of an array or the loop condition is met to not include a comma at the end of the list? One long way around this I've read of is to put a comma before an item, except the first entry something like:

$firstItem = true; //first item shouldn't have comma
for($i=0; $i=<5; $i++;){
  if(!$firstItem){ echo ","; }
  echo "$i";
  $firstItem = false;
}
A: 

I always find that its both faster and easier to use the language's array methods. For instance, in PHP:

<?php
echo join(',', array('L'.$level, $unit.'/'.$num, 
          $street, $suburb, $state, $postcode, $country));
digitala
Won't solve the problem unless you check for isset or empty before adding each element.
Tobias
like i did (see below, #3). unfortunatley that makes it a lot less elegant.
Schnalle
+1  A: 

While Phillip's answer addresses your question, I wanted to supplement it with the following blog post by Eric Lippert. Although his discussion is in c#, it applies to any programming language.

Jordan S. Jones
+1  A: 

There's a simple solution to your second problem:

for($i=0; $i<=5; $i++)
    $o .= "$i,";
echo chop($o, ',');
Tobias
+1  A: 

Philip's solution is probably best when working with arrays (if you don't have to filter out empty values), but if you can't use the array functions--for instance, when dealing with query results returned from mysqli_fetch_object()--then one solution is just a simple if statement:

$list = '';
$row=mysqli_fetch_object($result);
do {
    $list .= (empty($list) ? $row->col : ", {$row->col}");
} while ($row=mysqli_fetch_object($result));

Or, alternatively:

do {
    if (isset($list)) {
     $list .= ", {$row->col}";
    } else $list = $row->col;
} while ($row=mysqli_fetch_object($result));

To build a list and filter out empty values, I would write a custom function:

function makeList() {
    $args = array_filter(func_get_args()); // as per Jon Benedicto's answer
    foreach ($args as $item) {
        if (isset($list)) {
            $list .= ", $item";
        } else {
            $list = $item;
        }
    }
    if (isset($list)) {
        return $list;
    } else return '';
}

Then you can call it like so:

$unitnum = implode('/',array_filter(array($unit,$num)));
if ($unitnum || $street) {
    $streetaddress = trim("$unitnum $street");
} else $streetaddress = '';
if ($level) {
    $level = "L$level";
}
echo makeList($level, $streetaddress, $suburb, $state $postcode, $country).'.';
Calvin
I reckon this is getting close, the street address may also need to use the makeList function as it would still return the space and / if either of the three variables are blank... hence the main part of the problem...
Peter
You also make a good point about the arrays, it's more often the data is coming from MySQL which means there's an extra step to create an array or arrays.
Peter
Sometimes, with items like the street address and level, the most straightforward solution is just to use simple if statements for their custom formatting. It would be impractical to construct a custom function to handle one-off formatting problems.
Calvin
+3  A: 

For your first example, you can use arrays in conjunction with a few of the array methods to get the desired result. For example:

echo join(', ', array_filter(array("L$level", join(' ', array_filter(array(join('/', array_filter(array($unit, $num))), $street))), $suburb, join(' ', array_filter(array($state, $postcode))), $country))) . '.';

This one-liner is quite complicated to read, so one can always wrap the array, array_filter and join calls into a separate method, and use that:

function merge($delimiter)
{
    $args = func_get_args();
    array_shift($args);
    return join($delimiter, array_filter($args));
}

echo merge(', ', "L$level", merge(' ', merge('/', $unit, $num), $street), $suburb, merge(' ', $state, $postcode), $country) . '.';

You need the array_filter calls to remove the empty entries, otherwise the delimeters would still be printed out.

For your second example, add the items to an array, then use join to insert the delimeter:

$arr = array();
for($i=0; $i=<5; $i++)
{
    $arr[] = $i;
}
echo(join(',', $arr));
Jon Benedicto
If I ever had to maintain your code, I would MUCH rather see something like the "arduous" method in the question. That one-line solution is uglyyyyyyyy.
Chad Birch
+1. I'd never heard of array_filter() until now.
Calvin
I agree the one-liner might be hard to read, so I've added another example that refactors the array, array_filter and join calls into a helper method.
Jon Benedicto
A: 
<?php
    $level  = 'foo';
    $street = 'bar';
    $num    = 'num';
    $unit   = '';

    // #1: unreadable and unelegant, with arrays
    $values   = array();
    $values[] = $level ? 'L' . $level : null;
    // not very readable ...
    $values[] = $unit && $num ? $unit . '/' . $num : ($unit ? $unit : ($num ? $num : null));
    $values[] = $street ? $street : null;

    echo join(',',  $values);


    // #2: or, even more unreadable and unelegant, with string concenation
    echo trim( 
        ($level ? 'L' . $level . ', ' : '') . 
        ($unit && $num ? $unit . '/' . $num . ', ' : ($unit ? $unit . ', ' : ($num ? $num . ', ': '')) .
        ($street ? $street . ', ': '')), ' ,');

    // #3: hey, i didn't even know that worked (roughly the same as #1):
    echo join(', ', array(
        $level ? 'L' . $level : null,
        $unit && $num ? $unit . '/' . $num : ($unit ? $unit : ($num ? $num : null)),
        $street ? $street : null
    ));
?>
Schnalle
although these solutions work, it seems like some long-winded code for a seemingly simple output... for a more complex arrangement it would be even hairier to get your head around these options... thanks for the contribution!
Peter
imho #3 is not so bad. in my example it looks worse than it is, because i covered every option for the unit/num combination. and it displays correctly if there are empty values (no "foo,,bar"). some people don't like the (sometimes poor) readability of the ? construct, tough.
Schnalle
A: 

Just take out the last comma, i.e replace it with nothing.

$string1 = "L$level, $unit/$num $street, $suburb, $state $postcode, $country.";
$string1 = eregi_replace(", \.$", "\.", $string1);
echo $string1;

This will do the work.

uuɐɯǝʃǝs
i don't think so, because if $suburb is empty, there'll be double commas in the middle (not nice).
Schnalle
+1  A: 

ok, take that! (but not too serious ^^)

<?php

function bothOrSingle($left, $infix, $right) {
    return $left && $right ? $left . $infix . $right : ($left ? $left : ($right ? $right : null));
}

function leftOrNull($left, $postfix) {
    return $left ? $left . $postfix : null;
}

function rightOrNull($prefix, $right) {
    return $right ? $prefix . $right : null; 
}

function joinargs() {
    $args = func_get_args();
    foreach ($args as $key => $arg) 
        if (!trim($arg)) 
            unset($args[$key]);

    $sep = array_shift($args);
    return join($sep, $args);
}

$level    = 2;
$unit     = 1;
$num      = 123;
$street   = 'Cool St';
$suburb   = 'Funky Town';
$state    = 'ABC';
$postcode = 2000;
$country  = 'Australia';

echo "\n" . '"' . joinargs(', ', rightOrNull('L', $level), bothOrSingle(bothOrSingle($unit, '/', $num), ' ', $street), bothOrSingle($state, ' ', $postcode), bothOrSingle($country, '', '.')) . '"';

// -> "L2, 1/123 Cool St, ABC 2000, Australia."

$level    = '';
$unit     = '';
$num      = '';
$street   = 'Cool St';
$suburb   = '';
$state    = 'ABC';
$postcode = '';
$country  = '';

echo "\n" . '"' . joinargs(
    ', ', 
    leftOrNull(
        joinargs(', ', 
            rightOrNull('L', $level), 
            bothOrSingle(bothOrSingle($unit, '/', $num), ' ', $street), 
            bothOrSingle($state, ' ', $postcode), 
            $country
        ),
        '.'
    )
) . '"';

// -> "Cool St, ABC."


$level    = '';
$unit     = '';
$num      = '';
$street   = '';
$suburb   = '';
$state    = '';
$postcode = '';
$country  = '';

echo "\n" . '"' . joinargs(
    ', ', 
    leftOrNull(
        joinargs(', ', 
            rightOrNull('L', $level), 
            bothOrSingle(bothOrSingle($unit, '/', $num), ' ', $street), 
            bothOrSingle($state, ' ', $postcode), 
            $country
        ),
        '.'
    )
) . '"';

// -> "" (even without the dot!)

?>

yes, i know - looks a bit like brainfuck.

Schnalle
geez, and I was trying to simplify the "arduous" way of doing it :) It looks like a dogs breakfast but elements are usable. Once output is decided, joinargs could have functions for each output so for addresses, simply call: doaddress($level,$unit,$num,$street,$state,$postcode,$country);
Peter