-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathqualify.go
142 lines (111 loc) · 2.72 KB
/
qualify.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
package qualify
import (
"sync"
"github.com/bsm/qualify/internal/intsets"
)
// Field identifies a particular fact field
type Field int
// --------------------------------------------------------------------
// Fact instances must implement a simple interface
type Fact interface {
// AppendFieldValues must append of values for a given field to dst
AppendFieldValues(dst []int, field Field) []int
}
// Qualifier instances can be used to determine qualified outcomes for
// any given fact.
type Qualifier struct {
outcomes []int
fields []Field
implicit fieldMap
oneOf fieldValueMap
oneOfX fieldMultiMap
noneOf fieldValueMap
setPool, islPool sync.Pool
}
// Qualify will find outcomes matching the given fact and append
// them to dst
func (q *Qualifier) Qualify(dst []int, fact Fact) []int {
if len(q.fields) == 0 {
return dst
}
var oc *intsets.Dense // outcome candidates
fc := q.recycleSet() // per-field candidates (from pool)
defer q.setPool.Put(fc)
vv := q.recycleSlice() // init scratch slice (from pool)
defer q.islPool.Put(vv)
for _, field := range q.fields {
fc.Clear()
// merge any implicit outcomes for this
// field
if set, ok := q.implicit[field]; ok {
fc.UnionWith(set)
}
// retrieve fact values
vv.S = fact.AppendFieldValues(vv.S[:0], field)
// merge all explicit oneOf outcomes
for _, val := range vv.S {
fv := fieldValue{Field: field, Value: val}
if set, ok := q.oneOf[fv]; ok {
fc.UnionWith(set)
}
}
// assign candidates
if oc == nil {
oc = q.recycleSet()
defer q.setPool.Put(oc)
oc.Copy(fc)
} else {
oc.IntersectionWith(fc)
}
// now, exclude all explicit noneOf outcomes
for _, val := range vv.S {
fv := fieldValue{Field: field, Value: val}
if set, ok := q.noneOf[fv]; ok {
oc.DifferenceWith(set)
}
}
// finally, check if it's worth proceeding
if oc.IsEmpty() {
return dst
}
}
// extract candidate positions (from pool)
poss := q.recycleSlice()
defer q.islPool.Put(poss)
poss.S = oc.AppendTo(poss.S)
// check each candidate against oneOfX restrictions
for _, pos := range poss.S {
outcome := q.outcomes[pos]
skip := false
for field, multi := range q.oneOfX[outcome] {
vv.S = fact.AppendFieldValues(vv.S[:0], field)
if !multi.Match(fc, vv.S...) {
skip = true
break
}
}
if !skip {
dst = append(dst, outcome)
}
}
return dst
}
func (q *Qualifier) recycleSet() *intsets.Dense {
if v := q.setPool.Get(); v != nil {
s := v.(*intsets.Dense)
s.Clear()
return s
}
return new(intsets.Dense)
}
func (q *Qualifier) recycleSlice() *intSlice {
if v := q.islPool.Get(); v != nil {
s := v.(*intSlice)
s.S = s.S[:0]
return s
}
return new(intSlice)
}
type intSlice struct {
S []int
}