eozzy
eozzy

Reputation: 68650

Separate first, middle and last names (Python)

I have a list of several hundred members that I want to separate by First Name, Middle Name and Last name, but some of the members have prefixes (denoted by 'P'). All possible combinations:

First Middle Last
P First Middle Last
First P Middle Last
P First p Middle Last

How do I separate First (with P, if available), Middle (with P, if available) and Last names in Python? This is what I came up with but it doesn't quite work.

import csv
inPath = "input.txt"
outPath = "output.txt"

newlist = []

file = open(inPath, 'rU')
if file:
    for line in file:
        member = line.split()
        newlist.append(member)
    file.close()
else:
    print "Error Opening File."

file = open(outPath, 'wb')
if file:
    for i in range(len(newlist)):
        print i, newlist[i][0] # Should get the First Name with Prefix
        print i, newlist[i][1] # Should get the Middle Name with Prefix
        print i, newlist[i][-1]
    file.close()
else:
    print "Error Opening File."

What I want is:

  1. Get first and middles names with their prefixes if available
  2. Output each (first, middle, last) to separate txt files, or a single CSV file (preferable).

Many thanks for your help.

Upvotes: 3

Views: 11615

Answers (8)

Janne Karila
Janne Karila

Reputation: 25197

Complete script:

import sys

def f(a,b):
    if b in ('M','Shk','BS'):
            return '%s %s' % (b,a)
    else:
            return '%s,%s' % (b,a)

for line in sys.stdin:
    sys.stdout.write(reduce(f, reversed(line.split(' '))))

Input:

First Middle Last
M First Middle Last
First Shk Middle Last
BS First M Middle Last

CSV output:

First,Middle,Last
M First,Middle,Last
First,Shk Middle,Last
BS First,M Middle,Last

Upvotes: 0

Hugh Bothwell
Hugh Bothwell

Reputation: 56624

import csv

class CsvWriter(object):
    """
    Wraps csv.writer in a partial file-API compatibility layer
    """
    def __init__(self, fname, mode='w', *args, **kwargs):
        super(CsvWriter, self).__init__()
        self.f = open(fname, mode)
        self.writer = csv.writer(self.f, *args, **kwargs)

    def write(self, *args):
        """
        Writes a row of data to the csv file

        Can be called as
          .write()         puts a blank row
          .write(2)        puts a single cell
          .write([1,2,3])  puts 3 cells
          .write(1,2,3)    puts 3 cells
        """
        if len(args)==1 and hasattr(args[0], ('__iter__')):
            # single argument, and it's a sequence - let it be the row data
            rowdata = args[0]
        else:
            rowdata = args

        self.writer.writerow(rowdata)

    def close(self):
        self.writer = None
        self.f.close()

    def __enter__(self):
        return self

    def __exit__(self, *exc):
        self.close()

class NameSplitter(object):
    def __init__(self, pre=None):
        super(NameSplitter, self).__init__()

        # list of accepted prefixes
        if pre is None:
            self.pre = set(['m','shk','bs'])
        else:
            self.pre = set([s.lower() for s in pre])

        # is-a-prefix word tester
        self.isPre = lambda x,p=self.pre: x.lower() in p

        jn = lambda *args: ' '.join(*args)

        # signature-based dispatch table
        self.match = {}
        self.match[(3,())]    = lambda w,j=jn: (w[0],         w[1],         w[2])
        self.match[(4,(0,))]  = lambda w,j=jn: (j(w[0],w[1]), w[2],         w[3])
        self.match[(4,(1,))]  = lambda w,j=jn: (w[0],         j(w[1],w[2]), w[3])
        self.match[(5,(0,2))] = lambda w,j=jn: (j(w[0],w[1]), j(w[2],w[3]), w[4])

    def __call__(self, nameStr):
        words = nameStr.split()

        # build hashable signature
        pres  = tuple(n for n,word in enumerate(words) if self.isPre(word))
        sig   = (len(words), pres)

        try:
            do = self.match[sig]
            return do(words)
        except KeyError:
            return None

def process(inf, outf, fn):
    for line in inf:
        res = fn(line)
        if res is not None:
            outf.write(res)

def main():
    infname = "input.txt"
    outfname = "output.csv"

    with open(infname,'rU') as inf:
        with CsvWriter(outfname) as outf:
            process(inf, outf, NameSplitter())

if __name__=="__main__":
    main()

Upvotes: 0

pav
pav

Reputation: 1

I would use regular expression, that particulaly designed for such purpose. This solution would be easily to maintain and understand.

It is worth to being tried out. http://docs.python.org/library/re.html

import re
from operator import truth

// patterns
                     //First    Middle   Last
first = re.compile ("^([\w]+) +([\w]+) ([\w]+)$")
                     //P  First    Middle    Last
second = re.compile ("^(M|Shk|BS) +([\w]+) +([\w]+) ([\w]+)$") 
                    //First     P    Middle   Last
third = re.compile ("^([\w]+) +(M|Shk|BS) +([\w]+) ([\w]+)$")     
                     //P    First    p   Middle    Last
forth = re.compile ("^(M|Shk|BS) +([\w]+) +(M|Shk|BS) +([\w]+) ([\w]+)$")     

if truth (first.search (you_string)):
     parsed = first.search (you_string)
     print parsed.group(1), parsed.group(2), parsed.group(3)
elif truth (second.search (you_string)):
     parsed = first.search (you_string)
     print parsed.group(1), parsed.group(2), parsed.group(3)
elif truth (third.search (you_string)):
     parsed = first.search (you_string)
     print parsed.group(1), parsed.group(2), parsed.group(3)
elif truth (forth.search (you_string)):
     parsed = first.search (you_string)
     print parsed.group(1), parsed.group(2), parsed.group(3)
else:
     print "not match at all"

It would execute much faster due to precompiled patterns

Upvotes: -2

soulseekah
soulseekah

Reputation: 9162

names = [('A', 'John', 'Paul', 'Smith'),
('Matthew', 'M', 'Phil', 'Bond'),
('A', 'Morris', 'O', 'Reil', 'M', 'Big')]

def getItem():
    for name in names:
        for (pos,item) in enumerate(name):
            yield item

itembase = getItem()

for i in enumerate(names):
    element = itembase.next()
    if len(element) == 1: firstName = element+" "+itembase.next()
    else: firstName = element
    element = itembase.next()
    if len(element) == 1: mName = element+" "+itembase.next()
    else: mName = element
    element = itembase.next()
    if len(element) == 1: lastName = element+" "+itembase.next()
    else: lastName = element

    print "First Name: "+firstName
    print "Middle Name: "+mName
    print "Last Name: "+lastName
    print "--"

This seems to work. Replace the len(element) == 1 condition (I didn't know you needed checking for only 3, so I've done one with any single letter) with conditions looking for three prefixes you have.

**Output**
First Name: A John
Middle Name: Paul
Last Name: Smith

First Name: Matthew
Middle Name: M Phil
Last Name: Bond

First Name: A Morris
Middle Name: O Reil
Last Name: M Big

Upvotes: 1

Vinay Sajip
Vinay Sajip

Reputation: 99307

How about this complete test script:

import sys

def process(file):
    for line in file:
        arr = line.split()
        if not arr:
            continue
        last = arr.pop()
        n = len(arr)
        if n == 4:
            first, middle = ' '.join(arr[:2]), ' '.join(arr[2:])
        elif n == 3:
            if arr[0] in ('M', 'Shk', 'BS'):
                first, middle = ' '.join(arr[:2]), arr[-1]
            else:
                first, middle = arr[0], ' '.join(arr[1:])
        elif n == 2:
            first, middle = arr
        else:
            continue
        print 'First: %r' % first
        print 'Middle: %r' % middle
        print 'Last: %r' % last

if __name__ == '__main__':
    process(sys.stdin)

If you run this on Linux, type in example lines and then press Ctrl+D to signify end-of-input. On Windows, use Ctrl+Z instead of Ctrl+D. You can also pipe in a file, of course.

The following input file:

First Middle Last
M First Middle Last
First Shk Middle Last
BS First M Middle Last

gives this output:

First: 'First'
Middle: 'Middle'
Last: 'Last'
First: 'M First'
Middle: 'Middle'
Last: 'Last'
First: 'First'
Middle: 'Shk Middle'
Last: 'Last'
First: 'BS First'
Middle: 'M Middle'
Last: 'Last'

Upvotes: 2

redShadow
redShadow

Reputation: 6777

If 'M', 'Shk' and 'BS' are not valid names/surnames, i.e. you don't care about their exact position, you could filter them out with a one-liner:

first, middle, last = filter(lambda x: x not in ('M','Shk','BS'), yourNameHere.split())

where, of course, yourNameHere is the string containing the name you want to parse.

Warning: for this piece of code, I assume you always have a middle name, as you specified in the above examples. If not, you have to get the whole list and count elements to know whether you have a middle name or not.

EDIT: if you do care about the prefix position:

first, middle, last = map(
    lambda x: x[1],
    filter(
        lambda (i,x): i not in (0, 2) or x not in ('M','Shk','BS'),
        enumerate(yourNameHere.split())))

Upvotes: 1

kovshenin
kovshenin

Reputation: 32602

Here you go, in an object-oriented way:

class Name(object):
    def __init__(self, fullname):
        self.full = fullname
        s = self.full.split()

        try:
            self.first = " ".join(s[:2]) if len(s[0]) == 1 else s[0]
            s = s[len(self.first.split()):]

            self.middle = " ".join(s[:2]) if len(s[0]) == 1 else s[0]
            s = s[len(self.middle.split()):]

            self.last = " ".join(s[:2]) if len(s[0]) == 1 else s[0]
        finally:
            pass

names = [
    "First Middle Last",
    "P First Middle Last",
    "First P Middle Last",
    "P First p Middle Last",
]

for fullname in names:
    name = Name(fullname)
    print (name.first, name.middle, name.last)

Upvotes: 2

Trivikram
Trivikram

Reputation: 851

Here's another solution (obtained by making changes to source code given in question) :

import csv
inPath = "input.txt"
outPath = "output.txt"

newlist = []

file = open(inPath, 'rU')
if file:
    for line in file:
        member = line.split()
        newlist.append(member)
    file.close()
else:
    print "Error Opening File."

file = open(outPath, 'wb')
if file:
    for fullName in newlist:
        prefix = ""
        for name in fullName:
            if name == "P" or name == "p":
                prefix = name + " "
                continue
            print prefix+name
            prefix = ""
        print
    file.close()
else:
    print "Error Opening File."

Upvotes: -1

Related Questions