如何在Go中提取相同类型的循环详细信息?

I have the following type:

type entity struct {
    components []Component
}

func NewEntity(componentsLength ComponentType) Entity {
    return &entity{
        components: make([]Component, componentsLength),
    }
}

When coding, I noticed the following repeated pattern:

func (e *entity) HasComponent(components ...Component) bool {
    for _, c := range components {
        if e.components[c.Type()] == nil {
            return false
        }
    }
    return true
}

func (e *entity) HasAnyComponent(components ...Component) bool {
    for _, c := range components {
        if e.components[c.Type()] != nil {
            return true
        }
    }
    return false
}

Go seems okay with a lot of duplicated code when you're writing similar functions for different types. However, in this case the types are the same. I'm still having trouble refactoring the code to generalize the use of the for loop and if statement.

How can I refactor these functions so that the list handling is common, but the boolean operators and values are abstracted out? And should I?

Yes, I think you should refactor this code, not merely to make it more compact, but to better express the set-theoretical nature of the operations you're performing. Both functions are asking a question about the intersection of e.components and components.

What you're essentially saying with HasComponent—which should probably be called HasAllComponents—is that the intersection of e.components and components is equal to components. In other words, the size of the intersection is the same as the size of components.

As for HasAnyComponent, what you're saying is that the size of the intersection is at least one.

Both functions can be expressed succinctly by testing the size of the intersection, which you can obtain with, say, a function that has this signature:

func (e *entity) CountComponents(components ...Component) int

However, your original code has short-circuit logic that stops looking at further components once it knows the answer. You can incorporate such a short-circuited loop into a function with this signature:

func (e *entity) HasMinimumComponents(minimum int, components ...Component) bool

In the loop, increment a count variable whenever e.components[c.Type()] != nil. Return true as soon as count >= minimum.

Now you can efficiently implement HasAllComponents with a one-line body:

return entity.HasMinimumComponents(len(components), components...)

And the body of HasAnyComponent becomes:

return entity.HasMinimumComponents(1, components...)

Below is a program that implements this idea with slightly different data types. I defined a more abstract kind of Entity that contains a map[int]bool. You should have no trouble adapting the idea to your own program.

package main

import (
        "fmt"
)

type Entity struct {
        ValueMap map[int]bool
}

func MakeEntity(values ...int) *Entity {
        entity := Entity{map[int]bool{}}
        for _, value := range values {
                entity.ValueMap[value] = true
        }
        return &entity
}

func (entity *Entity) HasMinimumValues(minimum int, values ...int) bool {
        count := 0
        if minimum == 0 {
                return true
        }
        for _, value := range values {
                if entity.ValueMap[value] {
                        count++
                        if count >= minimum {
                                return true
                        }
                }
        }
        return false
}

func (entity *Entity) HasAllValues(values ...int) bool {
        return entity.HasMinimumValues(len(values), values...)
}

func (entity *Entity) HasAnyValue(values ...int) bool {
        return entity.HasMinimumValues(1, values...)
}

func main() {
        entity := MakeEntity(1, 3, 5, 7, 9)
        fmt.Printf("%t
", entity.HasAllValues(3, 9))
        fmt.Printf("%t
", entity.HasAllValues(3, 9, 12))
        fmt.Printf("%t
", entity.HasAnyValue(9, 12, 15))
        fmt.Printf("%t
", entity.HasAnyValue(12, 15))
}