首页 > 解决方案 > 如何将 int 和 string 集合的常用方法重构为一个公共基础?

问题描述

考虑下面的程序,它定义了两种类型IntSet和分别StringSet包含ints 和strings 的集合。

这些类型的Add()AddRange()Contains()ContainsAnyLength()基本相同(仅参数类型不同)。

我可以定义独立的函数Add(), AddRange(), ... 没有方法接收器和带有or的interface{}参数,但我希望这些方法与集合保持耦合。IntSetStringSet

如果我使用composition,则基本结构无法访问map[...]bool子结构的 。

重构上述五种方法以消除代码重复的正确方法是什么?

程序:

package main

import (
    "fmt"
    "sort"
    "strconv"
    "strings"
)


type IntSet map[int]bool
type StringSet map[string]bool

func NewStringSet(vs []string) StringSet {
    ss := StringSet{}
    for _, v := range vs {
        ss.Add(v)
    }
    return ss
}

func (ss StringSet) Add(v string) bool {
    _, found := ss[v]
    ss[v] = true
    return !found
}

func (ss StringSet) AddRange(vs []string) {
    for _, v := range vs {
        ss[v] = true
    }
}

func (ss StringSet) Contains(v string) bool {
    _, found := ss[v]
    return found
}

func (ss StringSet) ContainsAny(vs []string) bool {
    for _, v := range vs {
        if _, found := ss[v]; found {
            return true
        }
    }
    return false
}

func (ss StringSet) Length() int {
    return len(ss)
}

func (ss StringSet) Stringify() string {
    vs := make([]string, len(ss))
    i := 0
    for v := range ss {
        vs[i] = v
        i++
    }
    return strings.Join(vs, ",")
}

func NewIntSet(vs []int) IntSet {
    is := IntSet{}
    for _, v := range vs {
        is.Add(v)
    }
    return is
}

func (is IntSet) Add(v int) bool {
    _, found := is[v]
    is[v] = true
    return !found
}

func (is IntSet) AddRange(vs []int) {
    for _, v := range vs {
        is[v] = true
    }
}

func (is IntSet) Contains(v int) bool {
    _, found := is[v]
    return found
}

func (is IntSet) ContainsAny(vs []int) bool {
    for _, v := range vs {
        if _, found := is[v]; found {
            return true
        }
    }
    return false
}

func (is IntSet) Length() int {
    return len(is)
}

func (is IntSet) Stringify() string {
    vs := make([]int, 0)
    for v := range is {
        vs = append(vs, v)
    }
    sort.Ints(vs)
    ws := make([]string, 0)
    for v := range vs {
        s := strconv.Itoa(v)
        ws = append(ws, s)
    }
    return strings.Join(ws, ",")
}

标签: gogenericsset

解决方案


只需保留重复的代码。就维护开销而言,有五种方法不是问题。

无论如何,这是一个带有泛型的强制性示例,它也适用于Go2 操场

package main

import (
    "fmt"
)

type Set[T comparable] map[T]bool

func NewSet[T comparable](vs []T) Set[T] {
    ss := Set[T]{}
    for _, v := range vs {
        ss.Add(v)
    }
    return ss
}

func (s Set[T]) Add(v T) bool {
    _, found := s[v]
    s[v] = true
    return !found
}

func (s Set[T]) AddRange(vs []T) {
    for _, v := range vs {
        s[v] = true
    }
}

func (s Set[T]) Contains(v T) bool {
    _, found := s[v]
    return found
}

func (s Set[T]) ContainsAny(vs []T) bool {
    for _, v := range vs {
        if _, found := s[v]; found {
            return true
        }
    }
    return false
}

func (s Set[T]) Length() int {
    return len(s)
}

func (s Set[T]) Stringify() string {
    vs := make([]interface{}, len(s))
    i := 0
    for v := range s {
        vs[i] = v
        i++
    }
    return fmt.Sprintf("%v", vs)
}

func main() {
    sset := NewSet([]string{"foo", "bar"})
    sset.Add("baz")
    fmt.Println(sset.Stringify()) // [foo bar baz]

    iset := NewSet([]int{12, 13, 14})
    iset.Add(20)
    fmt.Println(iset.Stringify()) // [12 13 14 20]
}

尤其是:

  • 的类型参数中使用的约束Set必须是comparable,因为映射键必须支持比较运算符 ( ==, !=)
  • 类型参数必须在所有接收器中显式重复,但不需要重复约束。所以你有func (s Set[T]) ...所有的方法
  • 的实现Stringify()很讨厌,因为类型参数T comparable不支持字符串操作。这只是一个comparable. 所以上面我天真地使用[]interface{}and fmt.Sprintf,它只是完成了这项工作

推荐阅读