在Go语言中实现一个通用的过滤器,用于切片或映射的结构体。

huangapple go评论89阅读模式
英文:

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 Vs.

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)

huangapple
  • 本文由 发表于 2021年6月12日 22:51:45
  • 转载请务必保留本文链接:https://go.coder-hub.com/67949978.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定