diff --git a/packages/i18nify-go/country.go b/packages/i18nify-go/country.go index e0901704..5d55c3a5 100644 --- a/packages/i18nify-go/country.go +++ b/packages/i18nify-go/country.go @@ -14,22 +14,22 @@ type Country struct { } // GetCountryMetadata retrieves metadata information for the country. -func (c Country) GetCountryMetadata() metadata.MetadataInformation { +func (c *Country) GetCountryMetadata() metadata.MetadataInformation { return metadata.GetMetadataInformation(c.Code) } // GetCountryPhoneNumber retrieves telephone number information for the country. -func (c Country) GetCountryPhoneNumber() phonenumber.CountryTeleInformation { +func (c *Country) GetCountryPhoneNumber() phonenumber.CountryTeleInformation { return phonenumber.GetCountryTeleInformation(c.Code) } // GetCountrySubDivisions retrieves subdivision information for the country. -func (c Country) GetCountrySubDivisions() subdivisions.CountrySubdivisions { +func (c *Country) GetCountrySubDivisions() subdivisions.CountrySubdivisions { return subdivisions.GetCountrySubdivisions(c.Code) } // GetCountryCurrency retrieves currency information for the country. -func (c Country) GetCountryCurrency() []currency.CurrencyInformation { +func (c *Country) GetCountryCurrency() []currency.CurrencyInformation { // Retrieve metadata information for the country. countryMetadata := c.GetCountryMetadata() @@ -43,34 +43,25 @@ func (c Country) GetCountryCurrency() []currency.CurrencyInformation { } // GetStatesByZipCode retrieves the states with zipcode for the country. -func (c Country) GetStatesByZipCode(zipcode string) []subdivisions.State { - subdivision := subdivisions.GetCountrySubdivisions(c.Code) +func (c *Country) GetStatesByZipCode(zipcode string) []subdivisions.State { + subdivision := c.GetCountrySubDivisions() return subdivision.GetStatesByZipCode(zipcode) } // GetCitiesByZipCode retrieves the cities with zipcode for the country. -func (c Country) GetCitiesByZipCode(zipcode string) []subdivisions.City { - // Get the subdivision - subdivision := subdivisions.GetCountrySubdivisions(c.Code) - // Get list of all the states which have zipCode included. - states := subdivision.GetStatesByZipCode(zipcode) - var cities []subdivisions.City - // Get all cities with the zipCode from all the states. - for _, state := range states { - // Retrieve Cities with the zipCode from the state. - cities = append(cities, state.GetCitiesByZipCode(zipcode)...) - } - return cities +func (c *Country) GetCitiesByZipCode(zipcode string) []subdivisions.City { + subdivision := c.GetCountrySubDivisions() + return subdivision.GetCitiesWithZipCode(zipcode) } // IsValidZipCode returns whether a pinCode is valid for the country or not. -func (c Country) IsValidZipCode(zipcode string) bool { +func (c *Country) IsValidZipCode(zipcode string) bool { return len(c.GetStatesByZipCode(zipcode)) > 0 } // NewCountry creates a new Country instance with the given country code. func NewCountry(code string) ICountry { - return Country{ + return &Country{ Code: code, } } diff --git a/packages/i18nify-go/modules/country_subdivisions/country_subdivisions.go b/packages/i18nify-go/modules/country_subdivisions/country_subdivisions.go index 896c7717..0f8ad0d9 100644 --- a/packages/i18nify-go/modules/country_subdivisions/country_subdivisions.go +++ b/packages/i18nify-go/modules/country_subdivisions/country_subdivisions.go @@ -17,6 +17,9 @@ import ( //go:embed data var subDivJsonDir embed.FS +// Cache to avoid duplicate reads for same countryCode +var countrySubDivisionStore = make(map[string]CountrySubdivisions) + // DataFile is the directory where JSON files containing country subdivision data are stored. " // UnmarshalCountrySubdivisions parses JSON data into a CountrySubdivisions struct. @@ -33,8 +36,14 @@ func (r *CountrySubdivisions) Marshal() ([]byte, error) { // CountrySubdivisions contains information about country subdivisions. type CountrySubdivisions struct { - CountryName string `json:"country_name"` // CountryName represents the name of the country. - States map[string]State `json:"states"` // States contains information about states or provinces within the country. + CountryName string `json:"country_name"` // CountryName represents the name of the country. + States map[string]State `json:"states"` // States contains information about states or provinces within the country. + ZipCodes CountryZipCodeMap `json:"-"` // CountryZipCodeMap contains information for reverse lookup on zipcodes within a country +} +type CountryZipCodeMap struct { + zipcodeToStates map[string][]State + zipcodeToCities map[string][]City + IsInitialized bool } // GetCountryName returns the name of the country. @@ -49,31 +58,24 @@ func (r *CountrySubdivisions) GetStates() map[string]State { // GetStatesByZipCode returns the list of states that have at least one city with the specified zip code. func (r *CountrySubdivisions) GetStatesByZipCode(code string) []State { - var states []State - for _, state := range r.States { - for _, city := range state.Cities { - for _, zipcode := range city.Zipcodes { - if zipcode == code { - states = append(states, state) - break - } - } - } - } - return states + return r.ZipCodes.zipcodeToStates[code] } // GetCitiesWithZipCode returns the list of cities with the specified zip code. func (r *CountrySubdivisions) GetCitiesWithZipCode(code string) []City { - var cities []City - for _, state := range r.States { - cities = append(cities, state.GetCitiesByZipCode(code)...) - } - return cities + return r.ZipCodes.zipcodeToCities[code] +} + +// IsValidZipCode returns if the input zipcode is valid or not +func (r *CountrySubdivisions) IsValidZipCode(code string) bool { + return len(r.ZipCodes.zipcodeToStates[code]) > 0 } // GetCountrySubdivisions retrieves subdivision information for a specific country code. func GetCountrySubdivisions(code string) CountrySubdivisions { + if _, present := countrySubDivisionStore[code]; present { + return countrySubDivisionStore[code] + } // Read JSON data file containing country subdivision information. completePath := filepath.Join("data/", code+".json") subDivJsonData, err := subDivJsonDir.ReadFile(completePath) @@ -83,9 +85,37 @@ func GetCountrySubdivisions(code string) CountrySubdivisions { } // Unmarshal JSON data into CountrySubdivisions struct. allSubDivData, _ := UnmarshalCountrySubdivisions(subDivJsonData) + // Initialise zipcode map to get faster retrieval for Cities and States + initializeZipCodeMap(&allSubDivData) + // Store calculated CountrySubDivisions into the cache + countrySubDivisionStore[code] = allSubDivData return allSubDivData } +// initializeZipCodeMap builds the zip code maps for the given CountrySubdivisions. +func initializeZipCodeMap(subdivisions *CountrySubdivisions) { + // Ensure zip code maps are initialized only once. + if subdivisions.ZipCodes.IsInitialized { + return + } + var zipcodeToStates = make(map[string][]State) + var zipcodeToCities = make(map[string][]City) + + // Iterate through all states and cities to populate the zip code maps. + + for _, state := range subdivisions.States { + for _, city := range state.Cities { + for _, zipcode := range city.Zipcodes { + zipcodeToStates[zipcode] = append(zipcodeToStates[zipcode], state) + zipcodeToCities[zipcode] = append(zipcodeToCities[zipcode], city) + } + } + } + subdivisions.ZipCodes.zipcodeToStates = zipcodeToStates + subdivisions.ZipCodes.zipcodeToCities = zipcodeToCities + subdivisions.ZipCodes.IsInitialized = true +} + // NewCountrySubdivisions creates a new CountrySubdivisions instance. func NewCountrySubdivisions(countryName string, states map[string]State) *CountrySubdivisions { return &CountrySubdivisions{ @@ -105,25 +135,6 @@ func (r *State) GetCities() []City { return r.Cities } -// IsValidZipCode return whether input zipcode is valid or not. -func (r *State) IsValidZipCode(zipcode string) bool { - return len(r.GetCitiesByZipCode(zipcode)) > 0 -} - -// GetCitiesByZipCode returns the City based on the input code. -func (r *State) GetCitiesByZipCode(code string) []City { - var cities []City - for _, city := range r.Cities { - for _, zipcode := range city.Zipcodes { - if zipcode == code { - cities = append(cities, city) - break - } - } - } - return cities -} - // GetName returns the name of the state. func (r *State) GetName() string { return r.Name diff --git a/packages/i18nify-go/modules/country_subdivisions/country_subdivisions_test.go b/packages/i18nify-go/modules/country_subdivisions/country_subdivisions_test.go index 777dc6c0..76c22ee6 100644 --- a/packages/i18nify-go/modules/country_subdivisions/country_subdivisions_test.go +++ b/packages/i18nify-go/modules/country_subdivisions/country_subdivisions_test.go @@ -139,33 +139,26 @@ func TestGetCitiesWithZipCode(t *testing.T) { } func TestIsValidZipCode(t *testing.T) { - state := State{ - Name: "Karnataka", - Cities: []City{ - {Name: "Bengaluru", Timezone: "Asia/Kolkata", Zipcodes: []string{"560018", "560116"}}, - {Name: "Mysore", Timezone: "Asia/Kolkata", Zipcodes: []string{"570001"}}, - }, - } - - assert.True(t, state.IsValidZipCode("560116")) - assert.False(t, state.IsValidZipCode("999999")) -} - -func TestGetCitiesByZipCode(t *testing.T) { - state := State{ - Name: "Karnataka", - Cities: []City{ - {Name: "Bengaluru", Timezone: "Asia/Kolkata", Zipcodes: []string{"560018", "560116"}}, - {Name: "Mysore", Timezone: "Asia/Kolkata", Zipcodes: []string{"570001"}}, + data := CountrySubdivisions{ + CountryName: "India", + States: map[string]State{ + "KA": { + Name: "Karnataka", + Cities: []City{ + {Name: "Bengaluru", Timezone: "Asia/Kolkata", Zipcodes: []string{"560018", "560116"}}, + {Name: "Mysore", Timezone: "Asia/Kolkata", Zipcodes: []string{"570001"}}, + }, + }, + "MH": { + Name: "Maharashtra", + Cities: []City{ + {Name: "Mumbai", Timezone: "Asia/Kolkata", Zipcodes: []string{"400001"}}, + {Name: "Pune", Timezone: "Asia/Kolkata", Zipcodes: []string{"560116"}}, + }, + }, }, } - cities := state.GetCitiesByZipCode("560116") - - assert.Equal(t, 1, len(cities)) - assert.Equal(t, "Bengaluru", cities[0].GetName()) - - // Test with a zip code that does not exist - cities = state.GetCitiesByZipCode("999999") - assert.Equal(t, 0, len(cities)) + assert.True(t, data.IsValidZipCode("400001")) + assert.False(t, data.IsValidZipCode("199901")) }