读取具有固定宽度和缺失值的表格数据

时间:2015-01-15 16:37:24

标签: go tabular

我正在尝试从Go中的磁盘读取一个表,其中包含混合整数和浮点数,其中每个字段的宽度都是固定的(每个字段占用固定数量的地方,如果太短,则前面是空格)值可能会丢失(并且应该默认为零)。

该文件位于:https://celestrak.com/SpaceData/sw20100101.txt

用于读取它的Fortran格式写在标题中:

FORMAT(I4,I3,I3,I5,I3,8I3,I4,8I4,I4,F4.1,I2,I4,F6.1,I2,5F6.1)

并且这些行看起来像这样(最后几行,带有空格):

2014 12 29 2475  2 20 30 23 33 37 47 33 47 270   7  15   9  18  22  39  18  39  21 1.1 5  64 127.1 0 150.4 156.0 131.4 153.3 160.9
2014 12 30 2475  3 30 40 37 20 30 27 27 23 233  15  27  22   7  15  12  12   9  15 0.8 4  66 126.0 0 150.3 156.1 130.3 152.7 161.0
2014 12 31 2475  4 13 23 13 17 20 33 13 17 150   5   9   5   6   7  18   5   6   8 0.4 2  65 129.2 0 150.5 156.3 133.6 152.4 161.3
2015 01 01 2475  5 20 10 10 10 10 20 20 30 130   7   4   4   4   4   7   7  15   6       101 138.0 0 150.7 156.6 142.7 152.1 161.7
2015 01 02 2475  6 30 10 20 20 30 20 30 40 200  15   4   7   7  15   7  15  27  12       113 146.0 0 150.9 157.0 151.0 152.2 162.1
2015 01 03 2475  7 50 30 30 30 30 20 20 10 220  48  15  15  15  15   7   7   4  15       122 149.0 0 151.0 157.2 154.1 152.4 162.4

我一直在尝试使用一个聪明的格式字符串来与Sscanf一起使用(例如“%4d%3d%3d%5d ...”),但它不适用于空格,或者如果数字不是右对齐的话它的插槽。

我正在寻找一种方法来阅读它,就像在Fortran中一样:

  • 可以使用混合字段类型(整数,浮点数,字符串)。
  • 每列都有固定大小的字符,如有必要,请填充空格,但不同的列可能会有不同的大小。
  • 数字值前面可能是零。
  • 值可能会丢失,在这种情况下,它会给出零值。
  • 值可以位于广告位中的任何位置,不一定是右对齐(不是示例,但可能是这样)

有没有一种聪明的方法来读取这样的东西,还是我应该手动拆分,修剪,检查和转换每个字段?

2 个答案:

答案 0 :(得分:2)

package main

import "fmt"
import "reflect"
import "strconv"
import "strings"

type scanner struct {
    len   int
    parts []int
}

func (ss *scanner) Scan(s string, args ...interface{}) (n int, err error) {
    if i := len(s); i != ss.len {
        return 0, fmt.Errorf("exepected string of size %d, actual %d", ss.len, i)
    }
    if len(args) != len(ss.parts) {
        return 0, fmt.Errorf("expected %d args, actual %d", len(ss.parts), len(args))
    }
    n = 0
    start := 0
    for ; n < len(args); n++ {
        a := args[n]
        l := ss.parts[n]
        if err = scanOne(s[start:start+l], a); err != nil {
            return
        }
        start += l
    }
    return n, nil
}

func newScan(parts ...int) *scanner {
    len := 0
    for _, v := range parts {
        len += v
    }
    return &scanner{len, parts}
}

func scanOne(s string, arg interface{}) (err error) {
    s = strings.TrimSpace(s)
    switch v := arg.(type) {
    case *int:
        if s == "" {
            *v = int(0)
        } else {
            *v, err = strconv.Atoi(s)
        }
    case *int32:
        if s == "" {
            *v = int32(0)
        } else {
            var val int64
            val, err = strconv.ParseInt(s, 10, 32)
            *v = int32(val)
        }
    case *int64:
        if s == "" {
            *v = int64(0)
        } else {
            *v, err = strconv.ParseInt(s, 10, 64)
        }
    case *float32:
        if s == "" {
            *v = float32(0)
        } else {
            var val float64
            val, err = strconv.ParseFloat(s, 32)
            *v = float32(val)
        }
    case *float64:
        if s == "" {
            *v = float64(0)
        } else {
            *v, err = strconv.ParseFloat(s, 64)
        }
    default:
        val := reflect.ValueOf(v)
        err = fmt.Errorf("can't scan type: " + val.Type().String())
    }
    return
}

func main() {
    s := newScan(2, 4, 2)
    var a int
    var b float32
    var c int32

    s.Scan("12 2.2 1", &a, &b, &c)
    fmt.Printf("%d %f %d\n", a, b, c)

    s.Scan("1      2", &a, &b, &c)
    fmt.Printf("%d %f %d\n", a, b, c)

    s.Scan("        ", &a, &b, &c)
    fmt.Printf("%d %f %d\n", a, b, c)
}

输出:

12 2.200000 1
1 0.000000 1
0 0.000000 0

请注意,扫描函数返回n个已解析的参数和错误。如果缺少值,则函数将其设置为0.实现主要取自fmt.Scanf。

答案 1 :(得分:0)

您可以将带有分隔符的csv编码设置为空白空间。像这样的东西

import (
"encoding/csv"
"os"
)
file, _:=os.Open("/SpaceData/sw20100101.txt")
csvreader:=csv.NewReader(file)
csvreader.Comma=' '
csvreader.FieldsPerRecord=33
csvreader.TrimLeadingSpace=true
parsedout, _ := csvreader.Read()

这是工作示例https://play.golang.org/p/Tsp72D4vsR