I have the following structs defined for movies and TV shows:
type Movie struct {
ID string `json:"id"`
Viewers int `json:"count"`
}
type TVShow struct {
ID string `json:"id"`
Season int `json:"season"`
Episode int `json:"episode"`
Viewers int `json:"count"`
}
Then I have the following structs that contain several movies or TV shows by country:
type Movies struct {
PenultimateMonth map[string][]Movie
LastMonth map[string][]Movie
}
type TVShows struct {
PenultimateMonth map[string][]TVShow
LastMonth map[string][]TVShow
}
Finally, I have a data struct that holds everything:
type Data struct {
Movies Movies
Seasons Seasons
}
What I need to do is collect all IDs of all movies and TV shows from penultimate and last month.
I figured out that I can use reflection for that, but I only managed to iterate over each Data
element individually instead of all:
func GetIDs(data *Data, country string) []string {
var ids []string
movies := reflect.ValueOf(data.Movies).Elem()
tvShows := reflect.ValueOf(data.TVShows).Elem()
for i := 0; i < movies.NumField(); i++ {
moviesSubset := movies.Field(i).Interface().(map[string][]Movie)
for _, movie := range moviesSubset[country] {
ids = append(ids, movie.ID)
}
}
for i := 0; i < tvShows.NumField(); i++ {
tvShowsSubset := tvShows.Field(i).Interface().(map[string][]TVShow)
for _, tvShow := range tvShowsSubset[country] {
ids = append(ids, tvShow.ID)
}
}
return ids
}
Is it possible to simplify the GetIDs
function so that I don't need the two separate blocks for movies and TV shows, but only one to collect all IDs?
Use an interface:
type identifiable interface {
GetID() string
}
type identifiables interface {
GetIDs() []string
}
Which you can implement like:
func (m Movie) GetID() string { return m.ID }
And use to collect IDs polymorphically.
You could write it for each type, or make the maps store the interface and implement it once.
type identifiable interface {
GetID() string
}
type identifiables interface {
GetIDs() []string
}
func (m Movie) GetID() string { return m.ID }
type movies []Movie
type moviesByCountry map[string]movies
func (m movies) GetIDs() (ret []string) {
for _, movie := range m {
ret = append(ret, movie.GetID())
}
return
}
func (m moviesByCountry) GetIDs() (ret []string) {
for _, slice := range m {
ret = append(ret, slice.GetIDs()...)
}
return
}
func (d Data) GetCountryIDs(country string) []string {
return gatherIDs(d.TVShows[country], d.Movies[country])
}
func gatherIDs(collections ...identifiables) (ret []string) {
for _, collection := range collections {
ret = append(ret, collection.GetIDs()...)
}
return
}
Here's a working playground example.
Not very efficient but simple and consistent IMHO. If that's a problem it can be optimized by passing in an accumulator slice, but I would suggest an interface with a private method of you go that route.
I think there's a reasonable case to be made for
type identifiableByCountry interface {
GetCountryIDs(string) []string
}
since both Data
and the map(s) could implement that.
If you prefer working with lambdas, you could also use something like:
type collection interface {
walk(func(identifiable))
}
and implement that on the collection types, using it as
c.walk(func(o identifiable) {
ids = append(ids, o.GetID())
})