-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathfunctions.go
286 lines (261 loc) · 6.87 KB
/
functions.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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
package evidence
import (
"errors"
"fmt"
"math"
"regexp"
"sort"
"strings"
"sync"
"github.com/google/go-cmp/cmp"
)
// functionKey is an unexported hashable map key for functions
type functionKey string
func (fk functionKey) FocalElements() (fks []functionKey) {
// Special-case the empty set
if string(fk) == "" {
return
}
fs := strings.Split(string(fk), ",")
fks = make([]functionKey, 0, len(fs))
for i := range fs {
fks = append(fks, functionKey(fs[i]))
}
return fks
}
// IsSubset returns true if a possibility is a subset of another possibility
func (fk functionKey) IsSubset(ofk functionKey) bool {
count := 0
fkfe := fk.FocalElements()
for _, a := range fkfe {
for _, b := range ofk.FocalElements() {
if a == b {
count++
break
}
}
}
return count == len(fkfe)
}
// IsSuperset returns true if a possibility is a superset of another possibility
func (fk functionKey) IsSuperset(ofk functionKey) bool {
count := 0
ofkfe := ofk.FocalElements()
for _, a := range ofkfe {
for _, b := range fk.FocalElements() {
if a == b {
count++
break
}
}
}
return count == len(ofkfe)
}
// Intersect returns the functionKey that would be the intersection of the two
// keys.
func (fk functionKey) Intersect(ofk functionKey) (ifk functionKey) {
var intersectingKeys []string
ofkfe := ofk.FocalElements()
for _, a := range ofkfe {
for _, b := range fk.FocalElements() {
if a == b {
intersectingKeys = append(intersectingKeys, string(a))
break
}
}
}
return K(intersectingKeys...)
}
// Union returns the functionKey that would be the union of the two keys.
func (fk functionKey) Union(ofk functionKey) (ifk functionKey) {
var unionedKeys []string
for _, a := range fk.FocalElements() {
unionedKeys = append(unionedKeys, string(a))
}
for _, b := range ofk.FocalElements() {
unionedKeys = append(unionedKeys, string(b))
}
// K takes care of dupes
return K(unionedKeys...)
}
// Powerset returns all combinations of function keys within this function key.
func (fk functionKey) Powerset() (fks []functionKey) {
focals := fk.FocalElements()
n := len(focals)
for num := 0; num < (1 << uint(n)); num++ {
combination := []string{}
for i := 0; i < n; i++ {
// bit set
if num&(1<<uint(i)) != 0 {
// append to the combination
combination = append(combination, string(focals[i]))
}
}
fks = append(fks, K(combination...))
}
return
}
// String presents a human-readable version of the function key
func (fk functionKey) String() string {
return fmt.Sprintf("{%s}", string(fk))
}
var keyValidator = regexp.MustCompile(`^[a-z0-9-]+$`)
// K generates a mass key from a set of string values.
func K(focals ...string) functionKey {
// This function can't reasonably return an error due to the context in which
// it's intended to be used, so remove dupes and sort to generate our key.
focusSet := make(stringSet)
for _, focus := range focals {
if !keyValidator.MatchString(focus) {
panic(fmt.Sprintf(
"invalid focus key name (%q), must be lowercase alphanumeric or hyphen",
focus))
}
focusSet[focus] = struct{}{}
}
keys := make([]string, 0, len(focusSet))
for k := range focusSet {
keys = append(keys, k)
}
sort.Strings(keys)
return functionKey(strings.Join(keys, ","))
}
// A Function is a mapping of possibilities to values in the 0.0 to 1.0 range,
// usually probabilities.
type Function struct {
focalSet stringSet
possibilities map[functionKey]float64
mux sync.Mutex
}
// TODO: Need a hash func for efficient equality testing and O(1) lookups
func (f *Function) init() {
if f.possibilities == nil {
f.possibilities = make(map[functionKey]float64)
}
if f.focalSet == nil {
f.focalSet = make(stringSet)
}
}
// Set assigns a probability to a given possibility.
func (f *Function) Set(key functionKey, probability float64) (err error) {
f.mux.Lock()
f.init()
// If we don't truncate, floating point errors may cause values to go over 1.0
probability = floatFixed(probability, 5)
if probability < 0.0 || probability > 1.0 {
f.mux.Unlock()
return errors.New("probability out of range")
}
// Don't validate further as this can lead to difficulties when changing a
// mass function in-place. We're only validating the input in isolation.
f.possibilities[key] = probability
for _, focus := range key.FocalElements() {
f.focalSet[string(focus)] = exists
}
f.mux.Unlock()
return nil
}
// Get assigns a probability to a given possibility.
func (f *Function) Get(key functionKey) (probability float64) {
f.mux.Lock()
probability = f.getUnsafe(key)
f.mux.Unlock()
return
}
// Get assigns a probability to a given possibility.
func (f *Function) getUnsafe(key functionKey) (probability float64) {
f.init()
var ok bool
if probability, ok = f.possibilities[key]; !ok {
// If the key is missing, the probability is zero.
probability = 0.0
}
return
}
// sort.Interface for function key lists
type fkList struct {
fks []functionKey
}
func (fkl fkList) Len() int {
return len(fkl.fks)
}
func (fkl fkList) Less(i, j int) bool {
ifk := fkl.fks[i]
jfk := fkl.fks[j]
if len(ifk) == len(jfk) {
return strings.Compare(string(ifk), string(jfk)) < 0
}
return len(ifk) < len(jfk)
}
func (fkl fkList) Swap(i, j int) {
ifk := fkl.fks[i]
jfk := fkl.fks[j]
fkl.fks[i] = jfk
fkl.fks[j] = ifk
}
// Possibilities returns a lexically sorted slice of possibilities
func (f *Function) Possibilities() (fks []functionKey) {
f.mux.Lock()
f.init()
for p := range f.possibilities {
fks = append(fks, p)
}
fkl := fkList{
fks: fks,
}
sort.Sort(fkl)
f.mux.Unlock()
return fkl.fks
}
// Select returns a new function containing just the specified subset.
func (f *Function) Select(fks []functionKey) (nf *Function) {
f.mux.Lock()
f.init()
nf = &Function{}
nf.init()
for _, fk := range fks {
nf.Set(fk, f.getUnsafe(fk))
}
f.mux.Unlock()
return nf
}
// Powerset returns all combinations of function keys for this Function.
func (f *Function) Powerset() (fks []functionKey) {
f.mux.Lock()
f.init()
focals := make([]string, 0, len(f.focalSet))
for focal := range f.focalSet {
focals = append(focals, focal)
}
n := len(focals)
for num := 0; num < (1 << uint(n)); num++ {
combination := []string{}
for i := 0; i < n; i++ {
// bit set
if num&(1<<uint(i)) != 0 {
// append to the combination
combination = append(combination, focals[i])
}
}
fks = append(fks, K(combination...))
}
f.mux.Unlock()
return
}
func floatEq(a float64, b float64) bool {
const tolerance = 0.0025
opt := cmp.Comparer(func(x, y float64) bool {
diff := math.Abs(x - y)
mean := math.Abs(x+y) / 2.0
// https://www.juliaferraioli.com/blog/2018/06/golang-testing-floats/
if math.IsNaN(diff / mean) {
return true
}
return (diff / mean) < tolerance
})
return cmp.Equal(a, b, opt)
}
func floatFixed(num float64, precision int) float64 {
output := math.Pow(10, float64(precision))
return float64(math.Round(num*output)) / output
}