Реализация универсального фильтра для среза или карты структур в Go

# #go

Вопрос:

У меня есть несколько структур с полем «DateLastModified», например:

 type Thing struct {
  DateLastModified time.Time
  ...
}

type Thing2 struct {
  DateLastModified time.Time
  ...
}

 

У меня есть срезы или карты каждой из этих структур:

 things := []Thing{...}
thing2s := []Thing2{...}

//or

things := make(map[string]Thing)
thing2s := make(map[string]Thing2)
 

Что я хотел бы сделать, так это отфильтровать каждый из этих фрагментов или карт на их DateLastModified поле.

Это просто реализовать базовым способом, но мне интересно узнать больше о Go.

Мне интересно вот что: есть ли способ реализовать этот фильтр таким образом, чтобы я мог сделать что-то вроде:

 filteredThings := filterSliceOnTime(things, someTimeToFilterOn)
filtered2Things := filterSliceOnTime(thing2s, someTimeToFilterOn)

//or

filteredThings := filterMapOnTime(things, someTimeToFilterOn)
filtered2Things := filterMapOnTime(thing2s, someTimeToFilterOn)
 

Дело в том, что я пытаюсь понять, как уменьшить избыточный код, поскольку во всех этих структурах есть это поле DateLastModified.

Спасибо!

Ответ №1:

Go не имеет понятия о ко-/контра-дисперсии, поэтому filterXOnTime не сможет оперировать срезами []V или картами map[K]V структур различных базовых V s.

С интерфейсом

Что вы можете сделать, так это объявить интерфейс I , который отображает общее поведение, и чтобы обе структуры реализовали этот интерфейс:

 type Modifiable interface {
    GetDateLastModified() time.Time
}

type Thing struct {
    DateLastModified time.Time
    ...
}

func (t Thing) GetDateLastModified() time.Time {
    return t.DateLastModified
}

type Thing2 struct {
    DateLastModified time.Time
    ...
}

func (t Thing2) GetDateLastModified() time.Time {
    return t.DateLastModified
}
 

На этом этапе у вас могут быть срезы и карты I , и ваша функция фильтра может работать (принимать и возвращать) с ними:

 func filterSliceOnTime(modifiables []Modifiable, someTimeToFilterOn time.Time) []Modifiable { 
    var filtered []Modifiable
    for _, m := range modifiables {
        if m.GetDateLastModified.After(someTimeToFilterOn) {
             filtered = append(filtered, m)
        }
    }
    return filtered
}
 

Эффективность этого подхода несколько ограничена тем фактом, что []ThingX []Modifiable для использования «общей» функции вам необходимо переназначить и наоборот.

С помощью вспомогательной функции

Чтобы уменьшить проблемы с обслуживанием вышеприведенного решения, вы можете вместо этого использовать вспомогательную функцию фильтра, которая работает с одним элементом, поэтому вам не нужно сопоставлять сложные типы взад и вперед:

 func checkOne(v Modifiable, filterOn time.Time) bool {
    return v.GetDateLastModified().After(filterOn)
}

func main() {
    a := make(map[string]Thing, 0)
    a["foo"] = Thing{time.Now()}

    filtered := make(map[string]Thing, 0)
    for k, v := range a {
        if checkOne(v, time.Now().Add(-2*time.Hour)) {
             filtered[k] = v
        }
    }
    
    fmt.Println(filtered) // map[foo:{blah}]
}
 

Перейдите 1.18 и введите параметры

С Go 1.18 (начало 2022 года) и внедрением дженериков вы сможете вместо этого напрямую использовать срезы и карты. Вам все равно придется объявить интерфейс, чтобы обеспечить надлежащее ограничение для параметра типа, и структурам все равно придется его реализовать.

С текущим эскизным проектом это может выглядеть так:

 func filterSliceOnTime[T Modifiable](s []T, filterOn time.Time) []T {
    var filtered []T
    for _, v := range s {
        if v.GetDateLastModified().After(filterOn) {
            filtered = append(filtered, v)
        }
    }
    return filtered
}

 

Игровая площадка Go2: https://go2goplay.golang.org/p/FELhv0NSr5A

Комментарии:

1. Это именно то руководство, которое я надеялся получить. Большое спасибо, что научили меня!

Ответ №2:

Вы можете извлечь базовую структуру , в которой есть поле DateLastModified time.Time и методы, такие как isBefore(start) , isAfter(end) , isBetween(start, end)