Read CSV into 2D float array in Go 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:


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 {
        return nil, err
    }

    reader := csv.NewReader(file)
    stringValues, err := reader.ReadAll()
    if err != nil {
        return nil, err
    }

    values, err := stringValuesToFloats(stringValues)
    if err != nil {
        return nil, err
    }

    return values, nil
}
You might want to check that the input of stringValuesToFloats is 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 … by for 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 on stringValues, and the inner loop could be for colIndex, s := range …, and you could use s instead of stringValues[rowIndex][colIndex].

var err error = nil → var err error.
When a function returns something, error and you want to return an error, you usually return the default value of something along with the error. So in stringValuesToFloats, you would return nil, err rather than values, 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 use log.Errorf and 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 returning fmt.Errorf("stringValuesToFloat: couldn't parse value %s", err) instead of just err.
Close the file after usage. Add defer file.Close().
Use a csv.Reader with option TrimLeadingSpace instead 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 use i instead of rowIndex, floatArray instead of make2dFloatArray, ConvertCSV instead of ReadFromCSV, 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.