patterngoMinor
Read CSV into 2D float array in Go
Viewed 0 times
readfloatintocsvarray
Problem
Depending on how you count, this is my first Go program. I'm trying to read a CSV into a two-dimensional array of some numeric type, and then print it out.
(I want to use this to read "edge weights" to build a Graph; that is my next mission, unrelated to the code below.)
So the code below works. But particularly as I'm new to the language, I'd like to know:
Here 'tis; rip her apart if you want! Trying to learn.
```
package csfloat
import (
"encoding/csv"
"fmt"
"os"
"strconv"
"strings"
)
// make2dFloatArray makes a new 2d array of float64s based on the
// rowCount and colCount provided as arguments
func make2dFloatArray(rowCount int, colCount int) [][]float64 {
values := make([][]float64, rowCount)
for rowIndex := range values {
values[rowIndex] = make([]float64, colCount)
}
return values
}
// stringValuesToFloats converts a 2d array of strings into a 2d array
// of float64s.
func stringValuesToFloats(stringValues [][]string) ([][]float64, error) {
values := make2dFloatArray(len(stringValues), len(stringValues[0]))
for rowIndex, _ := range values {
for colIndex, _ := range values[rowIndex] {
var err error = nil
trimString :=
strings.TrimSpace(stringValues[rowIndex][colIndex])
values[rowIndex][colIndex], err =
strconv.ParseFloat(trimString, 64)
if err != nil {
fmt.Println(err)
return values, err
}
}
}
return values, nil
}
// ReadFromCsv will read the csv file at filePath and return its
// contents as a 2d array of floats
func ReadFromCsv(filePath string) ([][]float64, error) {
file, err := os.Open(filePath)
if err != nil {
(I want to use this to read "edge weights" to build a Graph; that is my next mission, unrelated to the code below.)
So the code below works. But particularly as I'm new to the language, I'd like to know:
- Are there shorter ways to accomplish the same functionality?
- Any ways to make this code more idiomatic?
- float64 feels arbitrary, but Go is statically typed -- any way I can make this more dynamic, allowing other types?
Here 'tis; rip her apart if you want! Trying to learn.
```
package csfloat
import (
"encoding/csv"
"fmt"
"os"
"strconv"
"strings"
)
// make2dFloatArray makes a new 2d array of float64s based on the
// rowCount and colCount provided as arguments
func make2dFloatArray(rowCount int, colCount int) [][]float64 {
values := make([][]float64, rowCount)
for rowIndex := range values {
values[rowIndex] = make([]float64, colCount)
}
return values
}
// stringValuesToFloats converts a 2d array of strings into a 2d array
// of float64s.
func stringValuesToFloats(stringValues [][]string) ([][]float64, error) {
values := make2dFloatArray(len(stringValues), len(stringValues[0]))
for rowIndex, _ := range values {
for colIndex, _ := range values[rowIndex] {
var err error = nil
trimString :=
strings.TrimSpace(stringValues[rowIndex][colIndex])
values[rowIndex][colIndex], err =
strconv.ParseFloat(trimString, 64)
if err != nil {
fmt.Println(err)
return values, err
}
}
}
return values, nil
}
// ReadFromCsv will read the csv file at filePath and return its
// contents as a 2d array of floats
func ReadFromCsv(filePath string) ([][]float64, error) {
file, err := os.Open(filePath)
if err != nil {
Solution
- You might want to check that the input of
stringValuesToFloatsis valid (that every row has the same number of columns).
- If you do that, you should document this behavior in
ReadFromCsv.
- Replace all
for foo, _ := range …byfor foo := range …. When you're not using the second argument, you don't need to discard it explicitly.
- But instead of iterating on
values, you could iterate onstringValues, and the inner loop could befor colIndex, s := range …, and you could usesinstead ofstringValues[rowIndex][colIndex].
var err error = nil→var err error.
- When a function returns
something, errorand you want to return an error, you usually return the default value ofsomethingalong with the error. So instringValuesToFloats, you would returnnil, errrather thanvalues, err. This helps you make sure that you're not going to use the return value when you return an error.
- Instead of printing the error with
fmt.Println, you probably want to uselog.Errorfand add an error message. Or not print anything at all and let the callers deal with the error (after all, it's why you return it), possibly returningfmt.Errorf("stringValuesToFloat: couldn't parse value %s", err)instead of justerr.
- Close the file after usage. Add
defer file.Close().
- Use a
csv.Readerwith optionTrimLeadingSpaceinstead of trimming whitespace by hand. Your CSV file shouldn't have space after values (arguably, it shouldn't have spaces anywhere).
- On "does this look like Go", I'd say you have too much whitespace (empty lines in functions are seldom used), line returns (you never see them right after declarations (
:=) or assigments (=)), and your variable names are Java-level verbose. In Go, you would useiinstead ofrowIndex,floatArrayinstead ofmake2dFloatArray,ConvertCSVinstead ofReadFromCSV, etc.
And, no, there's no easy way to have something more dynamic that
float64, unless you have a specific use case in mind (and then, you would use an interface, but this is out of scope for this question). See this for a detailed discussion on why Go doesn't have generics for now.Context
StackExchange Code Review Q#158168, answer score: 3
Revisions (0)
No revisions yet.