1

So I have names stored in a database, but they are stored as firstname lastname in one field. For example field name names shows john doe.

I want my name to be last name first, first name last (ex: doe, john), so I have that figured out (code below), but after that I need to sort the array by last name, how can I do this?

$names = array(
    1 => "Joe Johnson",
    2 => "Ann Marie blah",
    3 => "person three"
);  

foreach ($names as $id => $name) {

$parts = explode(" ", $name);

$lastname = array_pop($parts);

$firstname = implode(" ", $parts);  

$name = $lastname.", ".$firstname." ";  

echo    
    "<option value='$id'>$name</option>\n";
}
Lux
  • 17,835
  • 5
  • 43
  • 73
dchodev
  • 11
  • 3
  • 2
    There are persons out there having several first names, middle names etc. And there are persons that have more than one family name. This happens for entire nations where the custom is to get the family name of both parents as family names. How do you decide without offending anyone how many of the 3-4-5 or more words one person could have in their name are the given names and how many are the family names? Mission: impossible. – axiac Mar 10 '16 at 23:18
  • @axiac Excellent point. Also, some people only have one name (not just Prince.) – Don't Panic Mar 10 '16 at 23:21

3 Answers3

1

The problem is that you echoes names in the same foreach() loop that invert name and surname.

With a very little modification, you can obtain desired result.

First of all, use reference in foreach() loop (& before $name). By this way, you will change directly the array values, not a copy of them. The $id is not necessary in this foreach. Remove the echo from this loop:

foreach( $names as &$name )
{
    $parts     = explode(" ", $name);
    $lastname  = array_pop($parts);
    $firstname = implode(" ", $parts);  
    $name      = $lastname.", ".$firstname." ";  
}

At the end of foreach() loop, we have to unset() $name to obtain a correct result (thank to mr. Don't Panic for advice):

unset( $name );

Then, use natcasesort() to sort your array. With natcasesort, you sort case insensitive and maintain original keys, so the id in your <option> has the same of original array:

natcasesort( $names );

At the end, perform an additional foreach() loop to echo names:

foreach( $names as $id => $name )
{
    echo "<option value='$id'>$name</option>\n";
}

Edit:

If you prefer (I prefer), you can replace completely first foreach() loop with array_walk() and preg_replace():

array_walk
(
    $names, 
    function( &$val ) 
    {
        $val = '['.preg_replace('/^(.+) +(\S+)$/','\2, \1',$val).']';
    }
);

I have exploded syntax for clarity, but it is one line of code.

fusion3k
  • 11,568
  • 4
  • 25
  • 47
  • Don't forget to unset the reference after the foreach. I forgot about that at first and I got two `Johnson, Joe`s and no `three, person`s. – Don't Panic Mar 10 '16 at 23:25
  • @Don'tPanic Sure? I not. I will re-test. Thank you for the tip :) – fusion3k Mar 10 '16 at 23:35
  • Hi @fusion, I didn't necessarily mean the comment to be directed at you, just FYI for whomever since our answers are so similar (although yours is more thoroughly explained) and I encountered that problem. – Don't Panic Mar 10 '16 at 23:39
  • @Don'tPanic Yes, you are right! same problem in my code. Clearly before I had missed... Answer corrected. Thank again! But... you can explain the reason of that behavior? It is due to the use of `$name` in second `foreach` (I have changed it and the issue disappear) and - obviously - to the reference call. But I can't figure why... – fusion3k Mar 10 '16 at 23:49
  • 1
    [This answer](http://stackoverflow.com/a/5810200/2734189) has a link to an article with a pretty in-depth explanation. – Don't Panic Mar 10 '16 at 23:56
  • @Don'tPanic Thank you – fusion3k Mar 11 '16 at 00:02
  • @Don'tPanic how does this change when the reference object is using a object notation such as in the explode function $name -> name ? FOR INSTANCE: $parts = explode(" ", $name->name) that would break the reference correct? – dchodev Mar 14 '16 at 17:18
  • @dchodev it don't _break_ the reference, but yes, `$parts` has no reference with `$name`, because `explode` has not effect on passed arguments. Conversely, `$name = explode( '', $name )` will change even original `$names[n]` array item. – fusion3k Mar 14 '16 at 18:31
0

After getting only the last names into an Array, you should use asort() to preserve key associations, so you can create an new Array based on those keys.

$names = array(
    1 => 'Joe Johnson',
    2 => 'Ann Marie blah',
    3 => 'person three'
);
function sortLastNames(&$fromArray){
  $s = $r = array();
  foreach($fromArray as $k => $v){
     $s[$k] = substr($v, (strrpos($v, ' ')+1));
  }
  asort($s, SORT_NATURAL | SORT_FLAG_CASE);
  foreach($s as $k => $v){
    $r[$k] = $fromArray[$k];
  }
  $fromArray = $r;
}
sortLastNames($names); $opts = '';
foreach($names as $k => $v){
  $opts .= "<option value='$k'>$v</option>\n";
}
$test = '<select>'.$opts.'</select>';
// now `echo $test;` where you want it

Note that &$fromArray means the Array you pass as an argument is effected. This function does not return a new Array.

StackSlave
  • 10,613
  • 2
  • 18
  • 35
0

You can do what you're already doing, but use a reference so you update the array with the new last-name-first format, rather than echoing the name out in the first pass.

foreach ($names as $id => &$name) {  // use a reference here
    $parts = explode(" ", $name);
    $lastname = array_pop($parts);
    $firstname = implode(" ", $parts);
    $name = $lastname.", ".$firstname." ";
}

unset($name); // unset the reference

sort($names, SORT_NATURAL | SORT_FLAG_CASE); // sort the array

// then one more pass to echo the sorted names
foreach ($names as $id => $name) {
    echo "<option value='$id'>$name</option>\n";
}
Don't Panic
  • 41,125
  • 10
  • 61
  • 80