2015-01-15 34 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