英文:
Implementing a generic filter for slice or map of structs in Go
问题
我有多个结构体,其中包含一个"DateLastModified"字段,如下所示:
type Thing struct {
DateLastModified time.Time
...
}
type Thing2 struct {
DateLastModified time.Time
...
}
我有这些结构体的切片或映射:
things := []Thing{...}
thing2s := []Thing2{...}
//或者
things := make(map[string]Thing)
thing2s := make(map[string]Thing2)
我想要做的是根据它们的DateLastModified
字段对这些切片或映射进行过滤。
这在基本方式下很容易实现,但我对Go语言更感兴趣。
我想知道的是:是否有一种方式可以实现这种过滤,使我可以像这样做:
filteredThings := filterSliceOnTime(things, someTimeToFilterOn)
filtered2Things := filterSliceOnTime(thing2s, someTimeToFilterOn)
//或者
filteredThings := filterMapOnTime(things, someTimeToFilterOn)
filtered2Things := filterMapOnTime(thing2s, someTimeToFilterOn)
我试图弄清楚的是如何减少冗余代码,因为所有这些结构体都有DateLastModified
字段。
谢谢!
英文:
I have multiple structs with a "DateLastModified" field, like so:
type Thing struct {
DateLastModified time.Time
...
}
type Thing2 struct {
DateLastModified time.Time
...
}
I have slices or maps of each of these structs:
things := []Thing{...}
thing2s := []Thing2{...}
//or
things := make(map[string]Thing)
thing2s := make(map[string]Thing2)
What I'd like to do is filter each of those slices or maps on their DateLastModified
field.
This is simple to implement in a basic way, but I'm interested in learning more about Go.
What I'm wondering is: is there a way to implement that filter in such a way that I could do something like:
filteredThings := filterSliceOnTime(things, someTimeToFilterOn)
filtered2Things := filterSliceOnTime(thing2s, someTimeToFilterOn)
//or
filteredThings := filterMapOnTime(things, someTimeToFilterOn)
filtered2Things := filterMapOnTime(thing2s, someTimeToFilterOn)
The thing I'm trying to figure out is how to reduce redundant code since all these structs do have that DateLastModified field.
Thanks!
答案1
得分: 6
Go没有协变/逆变的概念,因此filterXOnTime
无法处理不同底层V
的结构体的切片[]V
或映射map[K]V
。
使用接口
你可以声明一个接口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}]
}
Go 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 playground: https://go2goplay.golang.org/p/FELhv0NSr5A
英文:
Go has no notion of co-/contra-variance, therefore filterXOnTime
will not be able to operate on slices []V
or maps map[K]V
of structs of different underlying V
s.
With an interface
What you can do is declaring an interface I
that exposes the common behavior, and have both structs implement that interface:
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
}
At this point you can have slices and maps of I
, and your filter function can work (accept and return) with those:
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
}
The effectiveness of this approach is slightly limited by the fact that you have to remap []ThingX
to []Modifiable
and vice versa in order to use the "generic" function.
With a helper func
To mitigate the maintenance hassle of the solution above, you can use instead a helper filter function that operates on one single item, so you don't have to map complex types back and forth:
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}]
}
Go 1.18 and type parameters
With Go 1.18 (early 2022) and the introduction of generics, you will be able instead to use the slices and maps directly. You will still have to declare the interface to provide the proper constraint for the type parameter, and the structs will still have to implement it.
With the current draft design, that might look like:
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 playground: https://go2goplay.golang.org/p/FELhv0NSr5A
答案2
得分: 0
你可以提取一个基本结构体,其中包含字段 DateLastModified time.Time
,以及像 isBefore(start)
、isAfter(end)
、isBetween(start, end)
这样的方法。
英文:
You can extract a base struct which has field DateLastModified time.Time
and methods like isBefore(start)
, isAfter(end)
, isBetween(start, end)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论