Skip to main content
added 11 characters in body
Source Link

My aim is to clean the data and to separate it into its respective fields: HouseNumber, Directional, StreetName, StreetType, Zip, City, State, Apartment

My aim is to clean the data and to separate it into its respective fields: HouseNumber, Directional, StreetName, Zip, City, State, Apartment

My aim is to clean the data and to separate it into its respective fields: HouseNumber, Directional, StreetName, StreetType, Zip, City, State, Apartment

rm tag from title
Link
svick
  • 24.3k
  • 4
  • 52
  • 89

Parsing US addresses without regular expression for performance C#

Source Link

Parsing US addresses without regular expression for performance C#

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, 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);

        }
    }
}