Чтение CSV файла в Go

Я учусь. Вот фрагмент кода, который читает CSV файл:

func parseLocation(file string) (map[string]Point, error) {
    f, err := os.Open(file)
    defer f.Close()
    if err != nil {
        return nil, err
    }
    lines, err := csv.NewReader(f).ReadAll()
    if err != nil {
        return nil, err
    }
    locations := make(map[string]Point)
    for _, line := range lines {
        name := line[0]
        lat, laterr := strconv.ParseFloat(line[1], 64)
        if laterr != nil {
            return nil, laterr
        }
        lon, lonerr := strconv.ParseFloat(line[2], 64)
        if lonerr != nil {
            return nil, lonerr
        }
        locations[name] = Point{lat, lon}
    }
    return locations, nil
}

Есть ли способ улучшить читаемость этого кода? если и нулевой шум.

Ответ 1

Go - очень многословный язык, однако вы можете использовать что-то вроде этого:

// predeclare err
func parseLocation(file string) (locations map[string]*Point, err error) {
    f, err := os.Open(file)
    if err != nil {
        return nil, err
    }
    defer f.Close() // this needs to be after the err check

    lines, err := csv.NewReader(f).ReadAll()
    if err != nil {
        return nil, err
    }

    //already defined in declaration, no need for :=
    locations = make(map[string]*Point, len(lines))
    var lat, lon float64 //predeclare lat, lon
    for _, line := range lines {
        // shorter, cleaner and since we already have lat and err declared, we can do this.
        if lat, err = strconv.ParseFloat(line[1], 64); err != nil {
            return nil, err
        }
        if lon, err = strconv.ParseFloat(line[2], 64); err != nil {
            return nil, err
        }
        locations[line[0]] = &Point{lat, lon}
    }
    return locations, nil
}

//редактировать

Более эффективная и правильная версия была опубликована @Dustin в комментариях, я добавляю ее здесь для полноты картины:

func parseLocation(file string) (map[string]*Point, error) {
    f, err := os.Open(file)
    if err != nil {
        return nil, err
    }
    defer f.Close()

    csvr := csv.NewReader(f)

    locations := map[string]*Point{}
    for {
        row, err := csvr.Read()
        if err != nil {
            if err == io.EOF {
                err = nil
            }
            return locations, err
        }

        p := &Point{}
        if p.lat, err = strconv.ParseFloat(row[1], 64); err != nil {
            return nil, err
        }
        if p.lon, err = strconv.ParseFloat(row[2], 64); err != nil {
            return nil, err
        }
        locations[row[0]] = p
    }
}

playground

Ответ 2

Я в основном скопировал мой ответ отсюда: https://www.dotnetperls.com/csv-go. Для меня это был лучший ответ, чем то, что я нашел в stackoverflow.

import (
    "bufio"
    "encoding/csv"
    "os"
    "fmt"
    "io"
)

func ReadCsvFile(filePath string)  {
    // Load a csv file.
    f, _ := os.Open(filePath)

    // Create a new reader.
    r := csv.NewReader(f)
    for {
        record, err := r.Read()
        // Stop at EOF.
        if err == io.EOF {
            break
        }

        if err != nil {
            panic(err)
        }
        // Display record.
        // ... Display record length.
        // ... Display all individual elements of the slice.
        fmt.Println(record)
        fmt.Println(len(record))
        for value := range record {
            fmt.Printf("  %v\n", record[value])
        }
    }
}

Ответ 3

Вы также можете прочитать содержимое каталога, чтобы загрузить все файлы CSV. А затем прочитайте все эти файлы CSV 1 на 1 с goroutines

csv файл:

101,300.00,11000901,1155686400
102,250.99,11000902,1432339200

файл main.go:

const sourcePath string = "./source"

func main() {
    dir, _ := os.Open(sourcePath)
    files, _ := dir.Readdir(-1)

    for _, file := range files {
        fmt.Println("SINGLE FILE: ")
        fmt.Println(file.Name())
        filePath := sourcePath + "/" + file.Name()
        f, _ := os.Open(filePath)
        defer f.Close()
        // os.Remove(filePath)

        //func
        go func(file io.Reader) {
            records, _ := csv.NewReader(file).ReadAll()
            for _, row := range records {
                fmt.Println(row)
            }
        }(f)

        time.Sleep(10 * time.Millisecond)// give some time to GO routines for execute
    }
}

И ВЫХОД будет:

$ go run main.go

SINGLE FILE:
batch01.csv
[101 300.00 11000901 1155686400]
[102 250.99 11000902 1432339200]

----------------- -------------- ------------------- --- ------- ---------------- ------------------- ----- ------ --------------

Ниже приведен пример со Invoice struct

func main() {
    dir, _ := os.Open(sourcePath)
    files, _ := dir.Readdir(-1)

    for _, file := range files {
        fmt.Println("SINGLE FILE: ")
        fmt.Println(file.Name())
        filePath := sourcePath + "/" + file.Name()
        f, _ := os.Open(filePath)
        defer f.Close()

        go func(file io.Reader) {
            records, _ := csv.NewReader(file).ReadAll()
            for _, row := range records {
                invoice := new(Invoice)
                invoice.InvoiceNumber = row[0]
                invoice.Amount, _ = strconv.ParseFloat(row[1], 64)
                invoice.OrderID, _ = strconv.Atoi(row[2])
                unixTime, _ := strconv.ParseInt(row[3], 10, 64)
                invoice.Date = time.Unix(unixTime, 0)

                fmt.Printf("Received invoice '%v' for $ %.2f \n", invoice.InvoiceNumber, invoice.Amount)
            }
        }(f)

        time.Sleep(10 * time.Millisecond)
    }
}

type Invoice struct {
    InvoiceNumber string
    Amount        float64
    OrderID       int
    Date          time.Time
}