2
\$\begingroup\$

I am reading a text file that has some name inconsistencies. For example I have names like:

MILLERS, WALTER M IV
DUPONTE, THOMAS B. II
HARDIWAY, GRANT U. SR.
GUIDRY, PAUL JOHN

As you can see I can split the name by first name and last name however the suffixes are placed incorrectly. Ideally the names should be:

WALTER M MILLERS IV
THOMAS B. DUPONTE II
GRANT U. HARDIWAY SR.
PAUL JHN GUIDRY

That being said this is what I have but I feel Im over complicating something simple. Any tips? Also my logic does not account for the suffix of V since that could be a middle initial also.

private function extractFullName($name){

        $suffix = collect(['II', 'III', 'IV', 'JR.', 'SR.']);

        // lets first assemble a full name from the row
        $fullNameArray = explode(",", $name);
        $firstName=trim($fullNameArray[1]);
        $lastName=trim($fullNameArray[0]);

        //lets look at the FIRST NAME for II/III/IV/JR./SR.
        $suffix->each(function ($item, $key) use ($firstName, $lastName) {

            if (strpos($firstName, " ".$item)){
                $firstName = preg_replace('/ '.$item.'$/', '', $firstName);
                $lastName.=' '.$item;
                return false;
            }
        });

        //Basically arranging it in a 'firstname lastname II/III/IV/JR./SR.' format
        $name = $firstName." ".$lastName;
        $name = ucwords(strtolower($name));
        return $name;
    }
\$\endgroup\$
2
\$\begingroup\$

Laravel aside, your task can be done in just one line/call of preg_replace() in your user-defined function:

return preg_replace('/^([^,]+), (.+?)(?: (I?[VX]|[SJ]R\.|[VX]?I{1,3}))?$/','$2 $1 $3',$name);

Regex Demo Link

This will reconstruct the full name strings with optional suffixes including SR., JR., and Roman numerals from 1 to 13 (that should be sufficient).

Pattern Breakdown:

^                 # Match from the start of the string
([^,]+)           # Capture all characters until a comma is found
, (.+?)           # Match a comma and a space, then lazy capture one or more characters
(?: (             # Match a space then capture qualifying matches to follows...
     I?[VX]       # Match: IV, V, IX, X
     |            # or
     [SJ]R\.      # Match: SR. or JR.
     |            # or
     [VX]?I{1,3}  # Match: I, II, II, VI, VII, VIII, XI, XII, XIII
    )             #
)?                # make the capture group optional (so suffix is not required)
$                 #Match to end of the string

In my PHP demo, I'll add a consideration for flagging unsuccessful reconstruction attempts via a shorthand conditional in the return line.

PHP: (Demo)

function reconstructName($name){
    $name=preg_replace('/^([^,]+), (.+?)(?: (I?[VX]|[SJ]R\.|[VX]?I{1,3}))?$/','$2 $1 $3',$name, 1, $count);
    return $count ? $name : "*** Name Reconstruction Failed: $name";  // handle failures as/if you wish
}

$registry=["MILLERS, WALTER M IV",
           "DUPONTE, THOMAS B. II",
           "HARDIWAY, GRANT U. SR.",
           "GUIDRY, PAUL JOHN",
           "BLAH",
           "FIVERSON-O'FIFE, FIVEY V.",
           "FIVERSON, FIVEY V",
           "FIVERSON, FIVEY V V",
           "O'FIVERSON, FIVEY V. V",
           "THREE-TRIP, THERESA III",
           "JUNIPER, JUNE JR.",
           "VENDETTA, V. V. V",
           "CÔTÉ, ZOË",
           "SLEVIN, LUCKY N. VII"
];

foreach($registry as $name){
    echo reconstructName($name),"\n";
}

Output:

WALTER M MILLERS IV
THOMAS B. DUPONTE II
GRANT U. HARDIWAY SR.
PAUL JOHN GUIDRY 
*** Name Reconstruction Failed: BLAH
FIVEY V. FIVERSON-O'FIFE 
FIVEY FIVERSON V
FIVEY V FIVERSON V
FIVEY V. O'FIVERSON V
THERESA THREE-TRIP III
JUNE JUNIPER JR.
V. V. VENDETTA V
ZOË CÔTÉ 
LUCKY N. SLEVIN VII

Some Closing Notes:

  • My method will require a substring before the comma, then a substring following the space after the comma.
  • A suffix is not required. A suffix must qualify as one of the valid substrings, or else it will considered part of the "given names" portion.
  • Notice the pattern will permit apostrophes, hyphens, and foreign characters.
  • The preg_replace() limit parameter is set to 1 as there will not possibly be more than one.
  • While not a Laravel solution, I think it is compelling to use this single, native php function. The pattern only really gets a little crazy when adding in the suffix considerations.
  • The pattern allows I to mean SR.. This seems appropriate since you were allowing II and JR..
  • A V. will never qualify as a Roman numeral; only a V at the end of the string will qualify.
\$\endgroup\$
2
\$\begingroup\$

If it can be assumed that a suffix will always have a space before it this is easier.

Loop through suffixes and see if one is in the first name. Strip out the suffix if is is there. Any suffix that is found is added to a suffix variable, s.

Treating the suffix separately is nice because it can be added, separated, lower cased, modified etc. For example, suffixes could be standardized; "SR." and "SR" could all be "SR." in the final name. This is easier to do if suffix is a separate variable. It could be an array as well.

function extractFullName($name) {

    // $suffixes = ['II', 'III', 'IV', 'JR.', 'SR.'];
    // Suffixes would always have a space before them
    $suffixes = [' II', ' III', ' IV', ' JR.', ' SR.'];


    // lets first assemble a full name from the row
    $fullNameArray = explode(",", $name);
    $firstName = trim($fullNameArray[1]);
    $lastName = trim($fullNameArray[0]);
    $s = ''; // default zero length string.
    // 
    //lets look at the FIRST NAME for II/III/IV/JR./SR.
    foreach ($suffixes as $suffix) {
        if (strpos($firstName, $suffix)) {
            // Suffix was found, remove it from first name
            $firstName = str_replace($suffix, "", $firstName);
            $s .= $suffix;  // keep ths suffixes stored separately.
        }
    }

    //Basically arranging it in a 'firstname lastname II/III/IV/JR./SR.' format
    $fullName = $firstName . " " . $lastName;
    $finalName = ucwords(strtolower($fullName)) . " " . $s;
    return $finalName;
}

$name1 = 'MILLERS, WALTER M IV';
$name2 = 'MIVERS, LIVEY M IV';  // test for IV in the middle of a name
$name3 = 'DUPONTE, THOMAS B. II';
$name4 = 'DUPONTE, THOMAS B. II SR.';  // test for multiple suffixes.
$name5 = 'HARDIWAY, GRANT U. SR.';
$name6 = 'GUIDRY, PAUL JOHN';
echo extractFullName($name1) . "<br>";
echo extractFullName($name2) . "<br>";
echo extractFullName($name3) . "<br>";
echo extractFullName($name4) . "<br>";
echo extractFullName($name5) . "<br>";
echo extractFullName($name6) . "<br>";
\$\endgroup\$
  • \$\begingroup\$ Thank you so much for your answer. It is just that my project is on laravel and it provides some helper functions that essentially do the same but in a lot less code. I hate picking an answer because you did spend the time but for my project the second answer works a little better. Thanks again for your help. I feel bad :( \$\endgroup\$ – kratos Apr 5 '17 at 16:18
  • \$\begingroup\$ That's very understandable. I took the Laravel out of because it was so sparsely used in this function. This PHP function can be used in any project. I liked the other answer for being shorter but the trade off is readability for lower level programmers :) \$\endgroup\$ – geeves Apr 5 '17 at 17:07
2
\$\begingroup\$

You could simplify you code quite a bit by utilising collections more:

private function extractFullName($name)
{
    $suffix = collect(['II', 'III', 'IV', 'JR.', 'SR.'])->first(function ($suffix) use ($name) {
        return ends_with($name, $suffix);
    });

    $name = $suffix ? rtrim($name, $suffix) : $name;

    return collect(explode(',', $name))->map(function ($name) {
        return trim(ucwords(strtolower($name)));
    })->reverse()->implode(' ') . ' ' . $suffix;
}

Hope this helps!

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.