Files
2026-07-13 11:17:57 +05:00

115 lines
2.0 KiB
Go

package series
import (
"encoding/csv"
"fmt"
"os"
"slices"
"strconv"
"strings"
)
type CSVData struct {
Columns []string
Rows [][]string
index map[string]int
}
func ReadCSV(path string) (*CSVData, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
records, err := csv.NewReader(file).ReadAll()
if err != nil {
return nil, err
}
if len(records) == 0 {
return &CSVData{Columns: []string{}, Rows: [][]string{}, index: map[string]int{}}, nil
}
data := &CSVData{
Columns: records[0],
Rows: records[1:],
index: make(map[string]int, len(records[0])),
}
for i, column := range data.Columns {
data.index[strings.ToLower(strings.TrimSpace(column))] = i
}
return data, nil
}
func (d *CSVData) Index(name string) (int, error) {
idx, ok := d.index[strings.ToLower(name)]
if !ok {
return -1, fmt.Errorf("missing column %q", name)
}
return idx, nil
}
func (d *CSVData) NumericColumns(names ...string) ([][]float64, error) {
indices := make([]int, len(names))
for i, name := range names {
idx, err := d.Index(name)
if err != nil {
return nil, err
}
indices[i] = idx
}
values := make([][]float64, len(names))
for i := range values {
values[i] = make([]float64, 0, len(d.Rows))
}
maxIndex := MaxInt(indices...)
for _, row := range d.Rows {
if len(row) <= maxIndex {
continue
}
parsed := make([]float64, len(indices))
valid := true
for i, idx := range indices {
value, err := ParseFloat(row[idx])
if err != nil {
valid = false
break
}
parsed[i] = value
}
if !valid {
continue
}
for i, value := range parsed {
values[i] = append(values[i], value)
}
}
return values, nil
}
func ParseFloat(value string) (float64, error) {
return strconv.ParseFloat(strings.TrimSpace(value), 64)
}
func MaxInt(values ...int) int {
max := 0
for _, value := range values {
if value > max {
max = value
}
}
return max
}
func MaxLastHalf(values []float64) float64 {
if len(values) == 0 {
return 0
}
return slices.Max(values[len(values)/2:])
}