/
types.go
163 lines (147 loc) · 3.3 KB
/
types.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
package types
import (
"encoding/json"
"errors"
"github.com/gonum/matrix"
mtx "github.com/gonum/matrix/mat64"
"io"
"math"
"sync"
)
var (
ErrAPI = errors.New("api error")
ErrNoData = errors.New("no data")
ErrNotFound = errors.New("not found")
ErrBadQuery = errors.New("bad query")
)
// Work around for handling NaN values in JSON
// https://github.com/golang/go/issues/3480
type value float64
func (v value) MarshalJSON() ([]byte, error) {
if math.IsNaN(float64(v)) {
return json.Marshal(nil)
}
return json.Marshal(float64(v))
}
func (v *value) UnmarshalJSON(data []byte) error {
if err := json.Unmarshal(data, nil); err != nil {
val := float64(0.0)
if err := json.Unmarshal(data, &val); err == nil {
*v = value(val)
}
} else {
*v = value(math.NaN())
}
return nil
}
type values []value
type Client interface {
Datasets() ([]*Dataset, error)
Write(*Dataset) error
Delete(string) error
Query(*Query) (*Dataset, error)
}
// Stats contain statistics about the
// underlying data in a dataset
type Stats struct {
Rows int
Columns int
}
type dataset struct {
Name string
Columns []string
Stats *Stats
Mtx []value
}
// Dataset consists of a name and
// an ordered array of column names
type Dataset struct {
Name string // Name of this dataset
Columns []string // Ordered array of cols
Mtx *mtx.Dense `json:"-"` // Dense Matrix contains all values in the dataset
Stats *Stats
lock sync.RWMutex
index int
WithValues bool
}
func (ds *Dataset) MarshalJSON() ([]byte, error) {
ds.stats()
out := &dataset{
Name: ds.Name,
Columns: ds.Columns,
Stats: ds.Stats,
}
if ds.WithValues && ds.Mtx != nil {
r, c := ds.Mtx.Dims()
out.Mtx = make([]value, r*c)
for i, val := range ds.Mtx.RawMatrix().Data {
out.Mtx[i] = value(val)
}
}
return json.Marshal(out)
}
func (ds *Dataset) UnmarshalJSON(data []byte) error {
in := &dataset{}
if err := json.Unmarshal(data, in); err != nil {
return err
}
ds.Name = in.Name
ds.Columns = in.Columns
ds.Stats = in.Stats
return matrix.Maybe(func() {
if ds.WithValues && in.Mtx != nil {
values := make([]float64, ds.Stats.Rows*ds.Stats.Columns)
for i := 0; i < len(in.Mtx); i++ {
values[i] = float64(in.Mtx[i])
}
ds.Mtx = mtx.NewDense(ds.Stats.Rows, ds.Stats.Columns, values)
}
})
}
// stats updates the Stats struct
func (ds *Dataset) stats() {
if ds.Stats == nil {
ds.Stats = &Stats{}
}
if ds.Mtx != nil {
ds.Stats.Rows, ds.Stats.Columns = ds.Mtx.Dims()
}
}
// Len returns the length (number of rows) of the dataset
func (ds Dataset) Len() int {
len := 0
if ds.Mtx != nil {
len, _ = ds.Mtx.Dims()
}
return len
}
// CPos returns the position of a column
// name in a dataset. If the column
// does not exist it returns -1
func (ds Dataset) CPos(name string) int {
for i, col := range ds.Columns {
if name == col {
return i
}
}
return -1
}
// Next returns the next row of values
// If all values have been traversed
// it returns io.EOF. Implements the
// loader.Reader interface
func (ds *Dataset) Next() ([]float64, error) {
ds.lock.Lock()
defer ds.lock.Unlock()
if ds.Mtx == nil {
return nil, ErrNoData
}
r, _ := ds.Mtx.Dims()
if ds.index >= r {
ds.index = 0
return nil, io.EOF
}
rows := ds.Mtx.RawRowView(ds.index)
ds.index++
return rows, nil
}