mirror of
https://github.com/metabarcoding/obitools4.git
synced 2025-06-29 16:20:46 +00:00
rename goutils to obiutils
Former-commit-id: 2147f53db972bba571dfdae30c51b62d3e69cec5
This commit is contained in:
366
pkg/obiutils/goutils.go
Normal file
366
pkg/obiutils/goutils.go
Normal file
@ -0,0 +1,366 @@
|
||||
package obiutils
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
"github.com/barkimedes/go-deepcopy"
|
||||
)
|
||||
|
||||
// InterfaceToInt converts a interface{} to an integer value if possible.
|
||||
// If not a "NotAnInteger" error is returned via the err
|
||||
// return value and val is set to 0.
|
||||
func InterfaceToString(i interface{}) (val string, err error) {
|
||||
err = nil
|
||||
val = fmt.Sprintf("%v", i)
|
||||
return
|
||||
}
|
||||
|
||||
// NotAnInteger defines a new type of Error : "NotAnInteger"
|
||||
type NotAnInteger struct {
|
||||
message string
|
||||
}
|
||||
|
||||
// Error() retreives the error message associated to the "NotAnInteger"
|
||||
// error. Tha addition of that Error message make the "NotAnInteger"
|
||||
// complying with the error interface
|
||||
func (m *NotAnInteger) Error() string {
|
||||
return m.message
|
||||
}
|
||||
|
||||
// InterfaceToInt converts a interface{} to an integer value if possible.
|
||||
// If not a "NotAnInteger" error is returned via the err
|
||||
// return value and val is set to 0.
|
||||
func InterfaceToInt(i interface{}) (val int, err error) {
|
||||
|
||||
err = nil
|
||||
val = 0
|
||||
|
||||
switch t := i.(type) {
|
||||
case int:
|
||||
val = t
|
||||
case int8:
|
||||
val = int(t) // standardizes across systems
|
||||
case int16:
|
||||
val = int(t) // standardizes across systems
|
||||
case int32:
|
||||
val = int(t) // standardizes across systems
|
||||
case int64:
|
||||
val = int(t) // standardizes across systems
|
||||
case float32:
|
||||
val = int(t) // standardizes across systems
|
||||
case float64:
|
||||
val = int(t) // standardizes across systems
|
||||
case uint8:
|
||||
val = int(t) // standardizes across systems
|
||||
case uint16:
|
||||
val = int(t) // standardizes across systems
|
||||
case uint32:
|
||||
val = int(t) // standardizes across systems
|
||||
case uint64:
|
||||
val = int(t) // standardizes across systems
|
||||
default:
|
||||
err = &NotAnInteger{"value attribute cannot be casted to an integer"}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// NotAnInteger defines a new type of Error : "NotAnInteger"
|
||||
type NotAnFloat64 struct {
|
||||
message string
|
||||
}
|
||||
|
||||
// Error() retreives the error message associated to the "NotAnInteger"
|
||||
// error. Tha addition of that Error message make the "NotAnInteger"
|
||||
// complying with the error interface
|
||||
func (m *NotAnFloat64) Error() string {
|
||||
return m.message
|
||||
}
|
||||
|
||||
// InterfaceToInt converts a interface{} to an integer value if possible.
|
||||
// If not a "NotAnInteger" error is returned via the err
|
||||
// return value and val is set to 0.
|
||||
func InterfaceToFloat64(i interface{}) (val float64, err error) {
|
||||
|
||||
err = nil
|
||||
val = 0
|
||||
|
||||
switch t := i.(type) {
|
||||
case int:
|
||||
val = float64(t)
|
||||
case int8:
|
||||
val = float64(t) // standardizes across systems
|
||||
case int16:
|
||||
val = float64(t) // standardizes across systems
|
||||
case int32:
|
||||
val = float64(t) // standardizes across systems
|
||||
case int64:
|
||||
val = float64(t) // standardizes across systems
|
||||
case float32:
|
||||
val = float64(t) // standardizes across systems
|
||||
case float64:
|
||||
val = t // standardizes across systems
|
||||
case uint8:
|
||||
val = float64(t) // standardizes across systems
|
||||
case uint16:
|
||||
val = float64(t) // standardizes across systems
|
||||
case uint32:
|
||||
val = float64(t) // standardizes across systems
|
||||
case uint64:
|
||||
val = float64(t) // standardizes across systems
|
||||
default:
|
||||
err = &NotAnFloat64{"value attribute cannot be casted to a float value"}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// NotABoolean defines a new type of Error : "NotAMapInt"
|
||||
type NotAMapInt struct {
|
||||
message string
|
||||
}
|
||||
|
||||
// Error() retreives the error message associated to the "NotAnInteger"
|
||||
// error. Tha addition of that Error message make the "NotAnInteger"
|
||||
// complying with the error interface
|
||||
func (m *NotAMapInt) Error() string {
|
||||
return m.message
|
||||
}
|
||||
|
||||
func InterfaceToIntMap(i interface{}) (val map[string]int, err error) {
|
||||
err = nil
|
||||
|
||||
switch i := i.(type) {
|
||||
case map[string]int:
|
||||
val = i
|
||||
case map[string]interface{}:
|
||||
val = make(map[string]int, len(i))
|
||||
for k, v := range i {
|
||||
val[k], err = InterfaceToInt(v)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
case map[string]float64:
|
||||
val = make(map[string]int, len(i))
|
||||
for k, v := range i {
|
||||
val[k] = int(v)
|
||||
}
|
||||
default:
|
||||
err = &NotAMapInt{"value attribute cannot be casted to a map[string]int"}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// NotABoolean defines a new type of Error : "NotAMapInt"
|
||||
type NotAMapFloat64 struct {
|
||||
message string
|
||||
}
|
||||
|
||||
// Error() retreives the error message associated to the "NotAnInteger"
|
||||
// error. Tha addition of that Error message make the "NotAnInteger"
|
||||
// complying with the error interface
|
||||
func (m *NotAMapFloat64) Error() string {
|
||||
return m.message
|
||||
}
|
||||
|
||||
func InterfaceToFloat64Map(i interface{}) (val map[string]float64, err error) {
|
||||
err = nil
|
||||
|
||||
switch i := i.(type) {
|
||||
case map[string]float64:
|
||||
val = i
|
||||
case map[string]interface{}:
|
||||
val = make(map[string]float64, len(i))
|
||||
for k, v := range i {
|
||||
val[k], err = InterfaceToFloat64(v)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
case map[string]int:
|
||||
val = make(map[string]float64, len(i))
|
||||
for k, v := range i {
|
||||
val[k] = float64(v)
|
||||
}
|
||||
default:
|
||||
err = &NotAMapFloat64{"value attribute cannot be casted to a map[string]float64"}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// NotABoolean defines a new type of Error : "NotABoolean"
|
||||
type NotABoolean struct {
|
||||
message string
|
||||
}
|
||||
|
||||
// Error() retreives the error message associated to the "NotABoolean"
|
||||
// error. Tha addition of that Error message make the "NotABoolean"
|
||||
// complying with the error interface
|
||||
func (m *NotABoolean) Error() string {
|
||||
return m.message
|
||||
}
|
||||
|
||||
// It converts an interface{} to a bool, and returns an error if the interface{} cannot be converted
|
||||
// to a bool
|
||||
func InterfaceToBool(i interface{}) (val bool, err error) {
|
||||
|
||||
err = nil
|
||||
val = false
|
||||
|
||||
switch t := i.(type) {
|
||||
case int:
|
||||
val = t != 0
|
||||
case int8:
|
||||
val = t != 0 // standardizes across systems
|
||||
case int16:
|
||||
val = t != 0 // standardizes across systems
|
||||
case int32:
|
||||
val = t != 0 // standardizes across systems
|
||||
case int64:
|
||||
val = t != 0 // standardizes across systems
|
||||
case float32:
|
||||
val = t != 0 // standardizes across systems
|
||||
case float64:
|
||||
val = t != 0 // standardizes across systems
|
||||
case uint8:
|
||||
val = t != 0 // standardizes across systems
|
||||
case uint16:
|
||||
val = t != 0 // standardizes across systems
|
||||
case uint32:
|
||||
val = t != 0 // standardizes across systems
|
||||
case uint64:
|
||||
val = t != 0 // standardizes across systems
|
||||
default:
|
||||
err = &NotABoolean{"value attribute cannot be casted to a boolean"}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// If the interface{} can be cast to an int, return true.
|
||||
func CastableToInt(i interface{}) bool {
|
||||
|
||||
switch i.(type) {
|
||||
case int,
|
||||
int8, int16, int32, int64,
|
||||
float32, float64,
|
||||
uint8, uint16, uint32, uint64:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// > It copies the contents of the `src` map into the `dest` map, but if the value is a map, slice, or
|
||||
// array, it makes a deep copy of it
|
||||
func MustFillMap(dest, src map[string]interface{}) {
|
||||
|
||||
for k, v := range src {
|
||||
if IsAMap(v) || IsASlice(v) || IsAnArray(v) {
|
||||
v = deepcopy.MustAnything(v)
|
||||
}
|
||||
dest[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// Read a whole file into the memory and store it as array of lines
|
||||
// It reads a file line by line, and returns a slice of strings, one for each line
|
||||
func ReadLines(path string) (lines []string, err error) {
|
||||
var (
|
||||
file *os.File
|
||||
part []byte
|
||||
prefix bool
|
||||
)
|
||||
if file, err = os.Open(path); err != nil {
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
reader := bufio.NewReader(file)
|
||||
buffer := bytes.NewBuffer(make([]byte, 0))
|
||||
for {
|
||||
if part, prefix, err = reader.ReadLine(); err != nil {
|
||||
break
|
||||
}
|
||||
buffer.Write(part)
|
||||
if !prefix {
|
||||
lines = append(lines, buffer.String())
|
||||
buffer.Reset()
|
||||
}
|
||||
}
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func AtomicCounter(initial ...int) func() int {
|
||||
counterMutex := sync.Mutex{}
|
||||
counter := 0
|
||||
if len(initial) > 0 {
|
||||
counter = initial[0]
|
||||
}
|
||||
|
||||
nextCounter := func() int {
|
||||
counterMutex.Lock()
|
||||
defer counterMutex.Unlock()
|
||||
val := counter
|
||||
counter++
|
||||
|
||||
return val
|
||||
}
|
||||
|
||||
return nextCounter
|
||||
}
|
||||
|
||||
// Marshal is a UTF-8 friendly marshaler. Go's json.Marshal is not UTF-8
|
||||
// friendly because it replaces the valid UTF-8 and JSON characters "&". "<",
|
||||
// ">" with the "slash u" unicode escaped forms (e.g. \u0026). It preemptively
|
||||
// escapes for HTML friendliness. Where text may include any of these
|
||||
// characters, json.Marshal should not be used. Playground of Go breaking a
|
||||
// title: https://play.golang.org/p/o2hiX0c62oN
|
||||
func JsonMarshal(i interface{}) ([]byte, error) {
|
||||
buffer := &bytes.Buffer{}
|
||||
encoder := json.NewEncoder(buffer)
|
||||
encoder.SetEscapeHTML(false)
|
||||
err := encoder.Encode(i)
|
||||
return bytes.TrimRight(buffer.Bytes(), "\n"), err
|
||||
}
|
||||
|
||||
func IsAMap(value interface{}) bool {
|
||||
return reflect.TypeOf(value).Kind() == reflect.Map
|
||||
}
|
||||
|
||||
func IsAnArray(value interface{}) bool {
|
||||
return reflect.TypeOf(value).Kind() == reflect.Array
|
||||
}
|
||||
|
||||
func IsASlice(value interface{}) bool {
|
||||
return reflect.TypeOf(value).Kind() == reflect.Slice
|
||||
}
|
||||
|
||||
func HasLength(value interface{}) bool {
|
||||
_, ok := value.(interface{ Len() int })
|
||||
return IsAMap(value) || IsAnArray(value) || IsASlice(value) || ok
|
||||
}
|
||||
func Len(value interface{}) int {
|
||||
l := 1
|
||||
|
||||
if IsAMap(value) || IsAnArray(value) || IsASlice(value) {
|
||||
vc := reflect.ValueOf(value)
|
||||
l = vc.Len()
|
||||
}
|
||||
|
||||
if vc, ok := value.(interface{ Len() int }); ok {
|
||||
l = vc.Len()
|
||||
}
|
||||
|
||||
return l
|
||||
}
|
101
pkg/obiutils/gzipfile.go
Normal file
101
pkg/obiutils/gzipfile.go
Normal file
@ -0,0 +1,101 @@
|
||||
package obiutils
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"compress/gzip"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
type Wfile struct {
|
||||
compressed bool
|
||||
close bool
|
||||
out io.WriteCloser
|
||||
gf *gzip.Writer
|
||||
fw *bufio.Writer
|
||||
}
|
||||
|
||||
func OpenWritingFile(name string, compressed bool, append bool) (*Wfile, error) {
|
||||
|
||||
flags := os.O_WRONLY | os.O_CREATE
|
||||
|
||||
if append {
|
||||
flags |= os.O_APPEND
|
||||
}
|
||||
fi, err := os.OpenFile(name, flags, 0660)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var gf *gzip.Writer
|
||||
var fw *bufio.Writer
|
||||
|
||||
if compressed {
|
||||
gf = gzip.NewWriter(fi)
|
||||
fw = bufio.NewWriter(gf)
|
||||
} else {
|
||||
gf = nil
|
||||
fw = bufio.NewWriter(fi)
|
||||
}
|
||||
|
||||
return &Wfile{
|
||||
compressed: compressed,
|
||||
close: true,
|
||||
out: fi,
|
||||
gf: gf,
|
||||
fw: fw,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func CompressStream(out io.WriteCloser, compressed bool, close bool) (*Wfile, error) {
|
||||
var gf *gzip.Writer
|
||||
var fw *bufio.Writer
|
||||
|
||||
if compressed {
|
||||
gf = gzip.NewWriter(out)
|
||||
fw = bufio.NewWriter(gf)
|
||||
} else {
|
||||
gf = nil
|
||||
fw = bufio.NewWriter(out)
|
||||
}
|
||||
|
||||
return &Wfile{
|
||||
compressed: compressed,
|
||||
close: close,
|
||||
out: out,
|
||||
gf: gf,
|
||||
fw: fw,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (w *Wfile) Write(p []byte) (n int, err error) {
|
||||
return w.fw.Write(p)
|
||||
}
|
||||
|
||||
func (w *Wfile) WriteString(s string) (n int, err error) {
|
||||
return w.fw.Write([]byte(s))
|
||||
}
|
||||
|
||||
func (w *Wfile) Close() error {
|
||||
var err error
|
||||
err = nil
|
||||
|
||||
w.fw.Flush()
|
||||
|
||||
if w.compressed {
|
||||
err = w.gf.Close()
|
||||
}
|
||||
|
||||
var err2 error
|
||||
err2 = nil
|
||||
|
||||
if w.close {
|
||||
err2 = w.out.Close()
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
err = err2
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
83
pkg/obiutils/minmax.go
Normal file
83
pkg/obiutils/minmax.go
Normal file
@ -0,0 +1,83 @@
|
||||
package obiutils
|
||||
|
||||
import "golang.org/x/exp/constraints"
|
||||
|
||||
func MinInt(x, y int) int {
|
||||
if x < y {
|
||||
return x
|
||||
}
|
||||
return y
|
||||
}
|
||||
|
||||
func MaxInt(x, y int) int {
|
||||
if x < y {
|
||||
return y
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
func MinMaxInt(x, y int) (int, int) {
|
||||
if x < y {
|
||||
return x, y
|
||||
}
|
||||
return y, x
|
||||
}
|
||||
|
||||
func MinUInt16(x, y uint16) uint16 {
|
||||
if x < y {
|
||||
return x
|
||||
}
|
||||
return y
|
||||
}
|
||||
|
||||
func MaxUInt16(x, y uint16) uint16 {
|
||||
if x < y {
|
||||
return y
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
func MinSlice[T constraints.Ordered](vec []T) T {
|
||||
if len(vec) == 0 {
|
||||
panic("empty slice")
|
||||
}
|
||||
min := vec[0]
|
||||
for _, v := range vec {
|
||||
if v < min {
|
||||
min = v
|
||||
}
|
||||
}
|
||||
return min
|
||||
}
|
||||
|
||||
func MaxSlice[T constraints.Ordered](vec []T) T {
|
||||
if len(vec) == 0 {
|
||||
panic("empty slice")
|
||||
}
|
||||
max := vec[0]
|
||||
for _, v := range vec {
|
||||
if v > max {
|
||||
max = v
|
||||
}
|
||||
}
|
||||
return max
|
||||
}
|
||||
|
||||
func RangeSlice[T constraints.Ordered](vec []T) (min, max T) {
|
||||
if len(vec) == 0 {
|
||||
panic("empty slice")
|
||||
}
|
||||
|
||||
min = vec[0]
|
||||
max = vec[0]
|
||||
for _, v := range vec {
|
||||
if v > max {
|
||||
max = v
|
||||
}
|
||||
if v < min {
|
||||
min = v
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
86
pkg/obiutils/ranks.go
Normal file
86
pkg/obiutils/ranks.go
Normal file
@ -0,0 +1,86 @@
|
||||
package obiutils
|
||||
|
||||
import (
|
||||
"sort"
|
||||
)
|
||||
|
||||
// intRanker is a helper type for the rank function.
|
||||
type intRanker struct {
|
||||
x []int // Data to be ranked.
|
||||
r []int // A list of indexes into f that reflects rank order after sorting.
|
||||
}
|
||||
|
||||
// ranker satisfies the sort.Interface without mutating the reference slice, f.
|
||||
func (r intRanker) Len() int { return len(r.x) }
|
||||
func (r intRanker) Less(i, j int) bool { return r.x[r.r[i]] < r.x[r.r[j]] }
|
||||
func (r intRanker) Swap(i, j int) { r.r[i], r.r[j] = r.r[j], r.r[i] }
|
||||
|
||||
func IntOrder(data []int) []int {
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
r := make([]int, len(data))
|
||||
rk := intRanker{
|
||||
x: data,
|
||||
r: r,
|
||||
}
|
||||
|
||||
for i := 0; i < len(data); i++ {
|
||||
rk.r[i] = i
|
||||
}
|
||||
|
||||
sort.Sort(rk)
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func ReverseIntOrder(data []int) []int {
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
r := make([]int, len(data))
|
||||
rk := intRanker{
|
||||
x: data,
|
||||
r: r,
|
||||
}
|
||||
|
||||
for i := 0; i < len(data); i++ {
|
||||
rk.r[i] = i
|
||||
}
|
||||
|
||||
sort.Sort(sort.Reverse(rk))
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
type Ranker[T sort.Interface] struct {
|
||||
x T // Data to be ranked.
|
||||
r []int // A list of indexes into f that reflects rank order after sorting.
|
||||
}
|
||||
|
||||
// ranker satisfies the sort.Interface without mutating the reference slice, f.
|
||||
func (r Ranker[_]) Len() int { return len(r.r) }
|
||||
func (r Ranker[T]) Less(i, j int) bool { return r.x.Less(r.r[i], r.r[j]) }
|
||||
func (r Ranker[_]) Swap(i, j int) { r.r[i], r.r[j] = r.r[j], r.r[i] }
|
||||
|
||||
func Order[T sort.Interface](data T) []int {
|
||||
ldata := data.Len()
|
||||
if ldata == 0 {
|
||||
return nil
|
||||
}
|
||||
r := make([]int, ldata)
|
||||
rk := Ranker[T]{
|
||||
x: data,
|
||||
r: r,
|
||||
}
|
||||
|
||||
for i := 0; i < ldata; i++ {
|
||||
rk.r[i] = i
|
||||
}
|
||||
|
||||
sort.Sort(rk)
|
||||
|
||||
return r
|
||||
}
|
23
pkg/obiutils/slices.go
Normal file
23
pkg/obiutils/slices.go
Normal file
@ -0,0 +1,23 @@
|
||||
package obiutils
|
||||
|
||||
func Contains[T comparable](arr []T, x T) bool {
|
||||
for _, v := range arr {
|
||||
if v == x {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func LookFor[T comparable](arr []T, x T) int {
|
||||
for i, v := range arr {
|
||||
if v == x {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func RemoveIndex[T comparable](s []T, index int) []T {
|
||||
return append(s[:index], s[index+1:]...)
|
||||
}
|
Reference in New Issue
Block a user