2015-01-15 38 views
2

我试图从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语言,其中:

  • 混合的字段类型(整数,浮点数,字符串)是可能的。
  • 每列都有固定的字符大小,必要时用空格填充该插槽,但不同的列可能具有不同的大小。
  • 数字值前面可以加零。
  • 值可能会丢失,在这种情况下,它会给出零值。
  • 值可以是在任何位置的插槽,不一定右对齐(而不是例子,但它可能是可能的)

有一个聪明的方法来读取这样的事情,或者我应该分割,修整,检查并手动转换每个字段?

+0

我认为split/trim方法是你最好的选择。当你读入它时,它看起来像字节的顺序是标准化的,所以你可以运行一个循环,为每一行抓取字节n到n + x,并相应地转换它们。例如:'date,_:= time.Parse(“2006 01 02”,string(bytes [0:9]))'并继续使用'val:= strconv.Atoi(string.TrimSpace(string(bytes [ n:n + x])))等 – Verran 2015-01-15 19:17:40

回答

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。

+0

谢谢@ kopiczko,这是一个非常好的解决方案!它可以轻松演变为使用Fortran风格的格式来初始化扫描仪。你介意我把它做成一个软件包放进我的Github吗? – siritinga 2015-01-16 07:31:52

+1

如果你愿意,你可以这样做。 – kopiczko 2015-01-16 09:13:51

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

+0

我不确定这是否可以在记录之间出现多个空格。您可能想先删除连续的空格。 – fuz 2015-01-15 18:22:43

+0

问题在于值不会转换为int/float,如果缺少值或值之间没有空格,则会失败。 – siritinga 2015-01-15 19:00:58