Looking for some constructive feedback on Parsing an Address without using Regular Expression in C# for performance purposes.
My aim is to clean the data and to separate it into its respective fields: HouseNumber, Directional, StreetName, StreetType, Zip, City, State, Apartment
Example Input: 123 Main Street
Example Output: HouseNumber: 123, Street Name: Main, Street Type: Street
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace AddressParse
{
public class ParseAddress
{
private List<string> DigitList = new List<string>() { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" };
// Populated via: https://usaddress.codeplex.com/SourceControl/latest#trunk/AddressParser/AddressParser.cs
private List<string> StreetTypesList = new List<string>() { "ALEE", "ALLEY", "ANNEX", "ANEX", "ANX", "ANX.", "ANNX", "ARCADE", "ARC", "AV", "AVE", "AVEN", "AVENU", "AVENUE", "AVN", "AVNUE", "AVN.", "ALY", "ALY.","AVE", "ROAD", "AVE.", "ALLY", "STREET", "ST.", "ST", "WAY", "COURT", "CT", "CT.", "BAYOO", "BYU", "BYU.", "BAYOU", "BEACH", "BCH", "BEND", "BND.", "BND", "BLUF", "BLF", "BLF.", "BLUFF", "BLUFFS", "BLFS", "BOT", "BOT.", "BTM", "BTM.", "BOTTM", "BTTM.", "BOTTOM", "BOUL", "BOUL.", "BLVD.", "BLVD", "BOULEVARD", "BOULV.", "BOULV", "BRANCH", "BR", "BR.", "BRDGE", "BRG.", "BRIDGE", "BRG", "BRNCH", "BROOK", "BROOKS", "BURG", "BG.", "BURGS", "BGS", "BGS.", "BYPA", "BYPA.", "BYP", "BYP.", "CAMP", "CP", "CP.", "CANYN", "CYN", "CYN.", "CANYON", "CAPE", "CPE", "CPE.", "CAUSEWAY", "CSWY", "CSWY.", "CAUSWAY", "CEN.", "CEN", "CTR", "CTR.", "CENT", "CENT.", "CENTER", "CENTERS", "CTRS", "CTRS.", "CENTR", "CENTR.", "CENTRE", "CIRC", "CIRC.", "CIR", "CIR.", "CIRCL", "CIRCL.", "CIRCLE", "CIRCLES", "CIRS", "CIRS", "CK", "CK.", "CRK", "CRK.", "CLIFF", "CLF", "CLF.", "CLIFFS", "CLFS", "CLFS.", "CLUB", "CLB", "CLB.", "CMP", "CMP.", "CP", "CP.", "CNTER", "CTR", "CTR.", "CNTR", "CNTR.", "CNYN", "CYN", "CYN.", "COMMON", "CMN", "CMN.", "CORNER", "COR", "COR.", "CORNERS", "CORS", "CORS.", "COURSE", "CRSE", "CRSE.", "COURT", "CT", "CT.", "COURTS", "CTS", "CTS", "COVE", "CV", "CV.", "COVES", "CVS", "CVS.", "CR", "CRK", "CR.", "CRK.", "CRCL", "CIR", "CRCL.", "CIR.", "CRCLE", "CRECENT", "CRES.", "CREEK", "CRK", "CRK.", "CRESCENT", "CRESENT", "CRES.", "CREST", "CRST", "CRST.", "CROSSING", "XING", "XNG", "XNG.", "CROSSROAD", "XRD", "XRD.", "CRSCNT", "CRES", "CRES.", "CRSENT", "CRSNT", "CRSNT.", "CRSSING", "CRSSNG", "XING.", "CRT", "CRT.", "CT", "CT.", "CURVE", "CURV", "CURV.", "DALE", "DL", "DL.", "DAM", "DM.", "DIV", "DIV.", "DV", "DV.", "DIVIDE", "DRIV", "DRIV.", "DR", "DR.", "DRIVE", "DR", "DR.", "DRIVES", "DRS", "DRS.", "DRV", "DRV.", "DR", "DVD", "DV", "DV.", "ESTATE", "EST", "EST.", "ESTATES", "ESTS", "ESTS.", "EXP", "EXP.", "EXPY", "EXPR", "EXPY", "EXPRESS", "EXPY", "EXPRESSWAY", "EXPY.", "EXPW", "EXTENSION", "EXT", "EXTENSIONS", "EXTS", "EXTN", "EXT", "EXT.", "EXTNSN", "FALLS", "FLS", "FLS.", "FERRY", "FRY", "FRY.", "FIELD", "FLD", "FLD.", "FIELDS", "FLDS", "FLDS.", "FLAT", "FLT", "FLT.", "FLATS", "FLTS", "FORD", "FRD", "FRD." , "FORDS", "FRDS", "FOREST", "FRST", "FORESTS", "FRST", "FORG", "FRG", "FRG.", "FORGE", "FORGES", "FRGS", "FRGS.", "FORK", "FRK", "FRK.", "FORKS", "FRKS", "FORT", "FT", "FT.", "FREEWAY", "FWY", "FREEWY", "FWY.", "FRRY", "FRY", "FRY.", "FRT", "FRWAY", "FWY", "FRWY", "FWY", "FWY.", "GARDEN", "GDN", "GDN.", "GARDENS", "GDNS", "GDNS.", "GATEWAY", "GTWY", "GTWY.", "GATEWY", "GATWAY", "GLEN", "GLN", "GLN.","GLENS", "GLNS", "GLNS", "GRDEN", "GRDN", "GRDNS", "GDNS", "GREEN", "GRN", "GRN.", "GREENS", "GRNS", "GRNS.", "GROV", "GROV.", "GRV", "GRV.", "GROVE", "GROVES", "GRVS", "GRVS.", "GTWAY", "GTWY", "GTWY.", "HARB", "HBR", "HBR.", "HARBOR", "HARBORS", "HBRS", "HARBR", "HAVEN", "HVN", "HVN.", "HAVN", "HEIGHT", "HTS", "HTS.", "HEIGHTS", "HGTS", "HIGHWAY", "HWY", "HWY.", "HIGHWY", "HILL", "HL", "HL.", "HILLS", "HLS", "HLS.", "HIWAY", "HIWY", "HLLW", "HOLW", "HOLLOW", "HOLW.", "HOLLOWS", "HOLWS", "HRBOR", "HBR", "HT", "HTS", "HT.", "HTS.", "HWAY", "INLET", "INLT", "ISLAND", "IS", "IS.", "ISLANDS", "ISS", "ISS.", "ISLES", "ISLE", "ISLND", "ISLNDS", "JCTION", "JCT", "JCT.", "JCTN", "JCTNS", "JCTS", "JUNCTION", "JUNCTIONS", "JUNCTN", "KNOL", "KNL", "KNL.", "KNOLL", "KNOLLS", "KNLS", "LN", "LAKE", "LK", "LK.", "LAKES", "LKS", "LANDING", "LNDG", "LANE", "LANES", "LN", "LDGE", "LDG", "LDG.", "LIGHT", "LGT.", "LIGHTS", "LGTS.", "LNDNG", "LNDG", "LNDG.", "LOAF", "LF", "LA", "LA.", "LOCK", "LCK.", "LOCKS", "LCKS", "LODG", "LDG.", "LODGE", "LDG", "LDG.", "KEY", "KY.", "KY", "LOOPS", "LOOP", "MANOR", "MNR", "MNR.", "MANORS", "MNRS", "MNRS.", "MEADOW", "MDW", "MDW.", "MEADOWS", "MDWS", "MDWS.", "MEDOWS", "MILL", "ML", "ML.", "MILLS", "MLS", "MLS.", "MISSION", "MSN", "MSN.", "MISSN", "MSN", "MSN.", "MNT", "MNT.", "MT", "MT.", "MNTAIN", "MNTN", "MNTN.", "MNTNS", "MTNS", "MOTORWAY", "MTWY", "MOUNT", "MOUNTAIN", "MTN", "MOUNTAINS", "MTNS", "MOUNTIN", "MTN.", "MSSN", "MSN", "MSN.", "MTIN", "MTN", "NECK", "NCK", "NCK.", "ORCHARD", "ORCH", "ORCHRD", "ORCH", "OVERPASS", "OPAS", "OVL", "OVL.", "OVAL", "PARKS", "PARK", "PARKWAY", "PKWY", "PARKWAYS", "PKWY.", "PARKWY", "PASSAGE", "PSGE", "PATHS", "PATH", "PIKES", "PIKE", "PINE", "PNE", "PNE.", "PINES", "PNES", "PK", "PARK", "PKWAY", "PKWY", "PKWYS", "PKWY", "PKY", "PKWY", "PLACE", "PL", "PLAIN", "PLN", "PLAINES", "PLNS", "PLAINS", "PLNS", "PLAZA", "PLZ", "PLZA", "PLZ", "POINT", "PT", "POINTS", "PTS", "PORT", "PRT", "PORTS", "PRTS", "PRAIRIE", "PR.", "PRARIE", "PR", "PRK", "PARK", "PRR", "PR", "RAD", "RADL", "RADIAL", "RADL", "RADIEL", "RADL", "RANCH", "RNCH", "RANCHES", "RNCH", "RAPID", "RPD", "RPD.", "RAPIDS", "RPDS", "RDGE", "RDG", "RDG.", "REST", "RST", "RST.", "RIDGE", "RIDGES", "RDGS", "RIVER", "RIV", "RIV.", "RIVR", "RNCHS", "RNCH", "ROAD", "RD", "RD.", "ROADS", "RDS", "RDS.", "ROUTE", "RTE", "RTE.", "RVR", "RVR.", "RIV", "RIV.", "SHOAL", "SHL", "SHL.", "SHOALS", "SHLS", "SHOAR", "SHR", "SHOARS", "SHRS", "SHORE", "SHR", "SHORES", "SHRS", "SKYWAY", "SKWY", "SPNG", "SPG", "SPNGS", "SPGS", "SPRING", "SPRINGS", "SPRNG", "SPG.", "SPRNGS", "SPGS" , "SPURS", "SPUR", "SQR", "SQ", "SQRE", "SQ.", "SQRS", "SQS", "SQU", "SQU.", "SQUARE", "SQUARES", "SQS.", "STATION", "STA", "STA.", "STATN", "STA", "STN", "STN.", "STA.", "STR", "STR.", "ST", "ST.", "STRAV", "STRA", "STRAVE", "STRA.", "STRAVEN", "STRAVENUE", "STRAVN", "STREAM", "STRM", "STRM.", "STREET", "STREETS", "STS", "STS.", "STREME", "STRM", "STRT", "STRT.", "STRVN", "STRA", "STRVNUE", "SUMIT", "SMT", "SMT.", "SUMITT", "SUMMIT", "TERR", "TERR.", "TER", "TERRACE", "TER.", "THROUGHWAY", "TRWY", "TPK", "TPK.", "TPKE", "TR", "TR.", "TRL", "TRL.", "TRACE", "TRCE", "TRACES", "TRCE", "TRACK", "TRAK", "TRACKS", "TRAK", "TRAFFICWAY", "TRFY", "TRAIL", "TRL", "TRL.", "TRAILS", "TRK", "TRK.", "TRAK", "TRKS", "TRAK", "TRLS", "TRNPK", "TPKE", "TRPK", "TPKE", "TUNEL", "TUNL", "TUNLS", "TUNL.", "TUNNEL", "TUNNELS", "TUNNL", "TURNPIKE", "TPKE", "TURNPK", "TPKE.", "UNDERPASS", "UPAS", "UNION", "UN", "UN.", "UNIONS", "UNS", "UNS.", "VALLEY", "VLY", "VLY.", "VALLEYS", "VLYS", "VLYS.", "VALLY", "VDCT", "VIA", "VIADCT", "VIADUCT", "VIA.", "VIEW", "VW", "VW.", "VIEWS", "VWS", "VWS.", "VILL", "VLG", "VILLAG", "VLG.", "VILLAGE", "VILLAGES", "VLGS", "VILLE", "VL", "VL.", "VILLG", "VILLIAGE", "VIST", "VIST.", "VIS", "VISTA", "VIS.", "VLLY", "VLY", "VLY.", "VST", "VST.", "VSTA", "WALKS", "WALK", "WELL", "WL", "WL.", "WELLS", "WLS", "WLS.","WY", "WY.", "WAY" };
// Populated via: https://en.wikipedia.org/wiki/Category:Apartment_types
private List<string> ApartmentList = new List<string>() { "APT", "APT.", "APARTMENT", "HOTEL", "BASEMENT", "BASEMNT", "BEDSIT", "BUSINESS", "CHAMBRE DE BONNE", "CONDO", "CONDOMINIUM", "COMMUNUAL", "LOFT", "LOW-RISE", "ROOM", "RM", "RM.", "UNIT", "UNT", "UNT.", "#", "#." };
private List<string> DirectionalList = new List<string>() { "N", "W", "E", "S", "N.", "E.", "S.", "W.", "NW", "NW.", "NE", "NE.", "NORTHWEST", "NORTHEAST", "SOUTHWEST", "SOUTHEAST", "NORTH", "EAST", "WEST", "SOUTH" };
private List<int> NumbersToRemove = new List<int>();
private List<string> AllUSAZipsList = new List<string>();
private int numberOfElementsInAddress = 0;
private string potentialHouseNumberFirstChar = "";
private string houseNumber = "";
private string streetTypeFound = "";
private string apartmentFound = "";
private string inputAddressU = "";
private string zipcode = "";
private string directional = "";
private int streetTypePos1;
private int streetTypePos2;
private int directionalPos1;
private int directionalPos2;
private int apartmentPos1;
private int apartmentPos2;
private int zipPos1;
private int zipPos2;
private int statePos1;
private int statePos2;
private int cityPos1;
private int cityPos2;
private int fullStatePos1;
private int fullStatePos2;
private int houseNumberPos2;
private int zipIndex;
private int stringLength;
private bool gotHouseNumber;
private bool gotDirectional;
private bool gotZip;
private bool gotStreetType;
private bool gotApartment;
private bool gotZipState;
private bool gotZipFullState;
private bool gotZipCity;
public string HouseNumber { get; set; }
public string Directional { get; set; }
public string StreetName { get; set; }
public string StreetType { get; set; }
public string Zip { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Apartment { get; set; }
public string FullState { get; set; }
public void parseAddress(string inputAddress)
{
inputAddressU = inputAddress.ToUpper();
// Splits the incoming address into an array of strings seperated by space(s)
string[] fullAddress = inputAddressU.Split(' ', ',');
stringLength = inputAddressU.Length;
numberOfElementsInAddress = fullAddress.Count();
Console.WriteLine("Original Address: " + inputAddressU);
Console.WriteLine("--------------------------------------------------------------------");
// Test #1 Does the first element's first character in the array of strings contain a numeric value?
// if so 80/20 we can assume this is a housenumber..
if (numberOfElementsInAddress > 2 && inputAddress.ElementAt(0) != ' ')
{
potentialHouseNumberFirstChar = (fullAddress[0].Substring(0, 1));
if (DigitList.Any(X => potentialHouseNumberFirstChar.Contains(X)))
{
gotHouseNumber = true;
houseNumber = fullAddress[0];
HouseNumber = fullAddress[0];
houseNumberPos2 = inputAddressU.IndexOf(" ", 0);
}
}
// Test #2 see if this address contains a zip code..
if (numberOfElementsInAddress > 2)
{
foreach (string element in fullAddress)
{
if (element.Length == 5 && DigitList.Any(X => element.Contains(X)) && !element.Equals(HouseNumber) && element[0] >= '0' && element[0] <= '9' && element[1] >= '0' && element[1] <= '9' && element[2] >= '0' && element[2] <= '9' && element[3] >= '0' && element[3] <= '9' && element[4] >= '0' && element[4] <= '9')
{
// Info from: https://www.aggdata.com/node/86
string f = @"text file with a list of all possible USA zip codes";
List<string> lines = new List<string>();
using (StreamReader r = new StreamReader(f))
{
string line;
while ((line = r.ReadLine()) != null)
{
lines.Add(line);
}
}
foreach (string s in lines)
{
AllUSAZipsList.Add(s);
}
if (AllUSAZipsList.Any(X => element.Contains(X)))
{
zipIndex = AllUSAZipsList.IndexOf(element);
// Now since we have an exact zip code match we can find exact info about a person's city and state
// Info from: https://www.aggdata.com/node/86
string zipInfoFile = @"text file with a list of corresponding state abbreviations to zip code";
List<string> lines2 = new List<string>();
using (StreamReader x = new StreamReader(zipInfoFile))
{
string line;
while ((line = x.ReadLine()) != null)
{
lines2.Add(line);
}
}
// Info from: https://www.aggdata.com/node/86
string zipInfoFile2 = @"text file with list of corresponding full state names to zip code";
List<string> lines3 = new List<string>();
using (StreamReader y = new StreamReader(zipInfoFile2))
{
string line;
while ((line = y.ReadLine()) != null)
{
lines3.Add(line);
}
}
// Info from: https://www.aggdata.com/node/86
string zipInfoFile3 = @"text file with list of corresponding cities to zip code";
List<string> lines4 = new List<string>();
using (StreamReader y = new StreamReader(zipInfoFile3))
{
string line;
while ((line = y.ReadLine()) != null)
{
lines4.Add(line);
}
}
State = lines2[zipIndex];
if (fullAddress.Contains(State))
{
gotZipState = true;
statePos1 = inputAddressU.IndexOf(lines2[zipIndex]);
statePos2 = inputAddressU.IndexOf(lines2[zipIndex])+1;
}
FullState = lines3[zipIndex];
if (fullAddress.Contains(FullState))
{
gotZipFullState = true;
fullStatePos1 = inputAddressU.IndexOf(lines3[zipIndex]);
fullStatePos2 = inputAddressU.IndexOf(" ",fullStatePos2);
}
City = lines4[zipIndex];
if (fullAddress.Contains(City))
{
gotZipCity = true;
cityPos1 = inputAddressU.IndexOf(lines4[zipIndex]);
cityPos2 = inputAddressU.IndexOf(" ", cityPos2);
}
gotZip = true;
zipcode = element;
Zip = element;
zipPos1 = (inputAddressU.IndexOf(zipcode));
zipPos2 = inputAddressU.IndexOf(" ", zipPos1);
}
}
}
}
// Test #3 see if this address contains a directional..
// Make sure that directional is not last element in array..
if (numberOfElementsInAddress > 2 && inputAddress.ElementAt(0) != ' ')
{
foreach (string element in fullAddress)
{
if (DirectionalList.Any(X => element.Equals(X)))
{
gotDirectional = true;
Directional = element;
directionalPos1 = (inputAddressU.IndexOf(directional));
directionalPos2 = inputAddressU.IndexOf(" ", directionalPos1);
}
}
}
// Test #4 see if this address contains a street type
if (numberOfElementsInAddress > 2 && inputAddress.ElementAt(0) != ' ')
{
foreach (string element in fullAddress)
{
if (StreetTypesList.Any(X => element.Equals(X)))
{
gotStreetType = true;
streetTypeFound = element;
StreetType = element;
streetTypePos1 = (inputAddressU.IndexOf(streetTypeFound));
streetTypePos2 = inputAddressU.IndexOf(" ", streetTypePos1);
}
}
}
// Test #5 see if this address contains apartment information..
if (numberOfElementsInAddress > 2 && inputAddress.ElementAt(0) != ' ')
{
foreach (string element in fullAddress)
{
if (ApartmentList.Any(X => element.Contains(X)))
{
gotApartment = true;
apartmentFound = element;
apartmentPos1 = (inputAddressU.IndexOf(apartmentFound));
apartmentPos2 = inputAddressU.IndexOf(" ", apartmentPos1);
Apartment = inputAddressU.Substring(apartmentPos1, stringLength-apartmentPos1);
}
}
}
// Logic to determine which elements from array to remove..
if (gotHouseNumber == true)
{
if (houseNumberPos2 == -1)
{
houseNumberPos2 = inputAddressU.Length;
}
for (int i = 0; i < houseNumberPos2; i++)
{
NumbersToRemove.Add(i);
}
}
if (gotDirectional == true)
{
if (directionalPos2 == -1)
{
directionalPos2 = inputAddressU.Length;
}
for (int i = directionalPos1; i < directionalPos2; i++)
{
NumbersToRemove.Add(i);
}
}
if (gotStreetType == true)
{
if (streetTypePos2 == -1)
{
streetTypePos2 = inputAddressU.Length;
}
for (int i = streetTypePos1; i < streetTypePos2; i++)
{
NumbersToRemove.Add(i);
}
}
if (gotZip == true)
{
if (zipPos2 == -1)
{
zipPos2 = inputAddressU.Length;
}
for (int i = zipPos1; i < zipPos2; i++)
{
NumbersToRemove.Add(i);
}
}
if (gotApartment == true)
{
if (apartmentPos2 == -1)
{
apartmentPos2 = inputAddressU.Length;
}
for (int i = apartmentPos1; i < inputAddressU.Length; i++)
{
NumbersToRemove.Add(i);
}
}
if (gotZipState == true)
{
if (statePos2 == -1)
{
statePos2 = inputAddressU.Length;
}
//Only 2 chars..
NumbersToRemove.Add(statePos1);
NumbersToRemove.Add(statePos2);
}
if (gotZipFullState == true)
{
if (fullStatePos2 == -1)
{
fullStatePos2 = inputAddressU.Length;
}
for (int i = fullStatePos1; i < fullStatePos2; i++)
{
NumbersToRemove.Add(i);
}
}
if (gotZipCity == true)
{
if (cityPos2 == -1)
{
cityPos2 = inputAddressU.Length;
}
for (int i = cityPos1; i < cityPos2; i++)
{
NumbersToRemove.Add(i);
}
}
// Calculates remaining info and assumes it is the Street Name.. after all else is removed..
string streetName = string.Concat(inputAddressU.Select((x, i) => NumbersToRemove.Contains(i) ? string.Empty : x.ToString()));
char[] charsToTrim = { ' ', '\t', ',', };
StreetName = streetName.Trim(charsToTrim);
}
}
}