fmt.Sscanf documentation says: Sscanf scans the argument string, storing successive space-separated values into successive arguments as determined by the format
| character is not space and each format placeholder is independent of any other, so %s| does not mean match any string up to a first | but instead match any string not including space, and then a |.
You can process file line by line, split each line e.g using strings.Split() by | and then parse values into variables as you need:
row := "20220105|AACG|1805439|32577|3484265|B,Q,N"
tokens := strings.Split(row, "|")
if len(tokens) != 6 {
log.Fatal("Bad line in file...")
}
// Do appropriate conversions...
date, err := strconv.Atoi(tokens[0])
if err != nil {
log.Fatal("first token is not an integer")
}
symbol := tokens[1]
// ...
fmt.Println(date, symbol)
or as Volker pointed out in his comment you can use package encoding/csv and set its Reader Comma (separator) option to |:
f := `20220105|AA|1051302|4323|3132468|B,Q,N
20220105|AAA|61|0|62|Q`
r := csv.NewReader(strings.NewReader(f))
r.Comma = '|'
data, err := r.ReadAll()
if err != nil {
log.Fatal(err)
}
// Process data, do appropriate conversions
fmt.Print(data)
Answer from blami on Stack OverflowHi! With the occasion of this year’s adventofcode i decided to learn golang because it looked really cool. Today i had to parse some input which was of the following format: string: int-int or int-int For example
Departure time: 10-20 or 22-23
I read the text file line by line and tried the following
fmt.Sscanf(scanner.Text(),“%s: %d-%d or %d-%d,...);
This said that the input string doesnt match the format After that I’ve tried
fmt.Sscanf(scanner.Text(),“%s %d-%d or %d-%d,...);
This didnt panic but read the whole line into the first string. After that I’ve tried separating the string i needed and just parse the sequence of ints So i wrote
fmt.Sscanf(numbers_only,”%d-%d or %d-%d”,...)
This again said that the input doesn’t match the format. Any idea what I did wrong, or if indeed golang doesnt work perfectly with scanf? From my experience with c/c++, I know that doing the exact same thing there would work, but i cant figure out why it doesnt in golang.
From the documentation:
Sscanf scans the argument string, storing successive space-separated values into successive arguments as determined by the format
It looks like you're doing Advent of Code though, and several of the "keys" have multiple words. I would suggest simply using strings.Split as in the following:
text := "Departure time: 10-20 or 22-23"
parts := strings.Split(text, ":")
key := parts[0]
var a, b, c, d int
fmt.Sscanf(parts[1], "%d-%d or %d-%d", &a, &b, &c, &d)
fmt.Println(key)
fmt.Println(a, b, c, d)
I'm weird and I like to use regular expressions for parsing like this so I had something like this:
var re = regexp.MustCompile(`(?m)^(?P<ruleName>[a-z ]+): (?P<min1>\d+)-(?P<max1>\d+) or (?P<min2>\d+)-(?P<max2>\d+)$`)
and then extracted the values:
match := re.FindStringSubmatch(line)
result := make(map[string]string)
for i, name := range re.SubexpNames() {
if i != 0 && name != "" {
result[name] = match[i]
}
}
I'm sure it can be done much more elegantly.
go - Golang: How do I use fmt.Fscanf() & fmt.Sscanf() correctly? - Stack Overflow
fmt: confusion about Scanf patterns, newline, space
go - Sscanf of multiple string fields in golang - Stack Overflow
fmt: Scanf / Sscanf does not properly treat carriage return symbol, behaves different with C function
I've been teaching myself competitive programming using Go (yes, I'm aware it's a better idea to do it with c++, but I currently work in Go and intend to interview with it).
I came across a post saying avoid Scanf - is the entire family slow or just Scanf specifically?
What would you use in place of fmt.Sscanf(str, "%s%d%s%d", ...)? a for loop and strconv?
Your updated code was much easier to compile without the line numbers, but it was missing the package and import statements.
Looking at your code, I noticed a few things. Here's my revised version of your code.
package main
import (
"bufio"
"fmt"
"io"
"os"
"strconv"
"strings"
"container/vector"
)
func main() {
n := scanf(os.Stdin)
fmt.Println()
fmt.Println(len(n), n)
}
func scanf(in io.Reader) []int {
var nums vector.IntVector
rd := bufio.NewReader(os.Stdin)
str, err := rd.ReadString('\n')
for err != os.EOF {
fields := strings.Fields(str)
for _, f := range fields {
if i, err := strconv.Atoi(f); err == nil {
nums.Push(i)
}
}
str, err = rd.ReadString('\n')
}
return nums
}
I might want to use any input file for scanf(), not just Stdin; scanf() takes an io.Reader as a parameter.
You wrote: nums := new(vector.IntVector), where type IntVector []int. This allocates an integer slice reference named nums and initializes it to zero, then the new() function allocates an integer slice reference and initializes it to zero, and then assigns it to nums. I wrote: var nums vector.IntVector, which avoids the redundancy by simply allocating an integer slice reference named nums and initializing it to zero.
You didn't check the err value for strconv.Atoi(), which meant invalid input was converted to a zero value; I skip it.
To copy from the vector to a new slice and return the slice, you wrote:
r := make([]int, nums.Len())
for i := 0; i < nums.Len(); i++ {
r[i] = nums.At(i)
}
return r
First, I simply replaced that with an equivalent, the IntVector.Data() method: return nums.Data(). Then, I took advantage of the fact that type IntVector []int and avoided the allocation and copy by replacing that by: return nums.
This example always reads in a line at a time and returns the entire line as a string. If you want to parse out specific values from it you could.
package main
import (
"fmt"
"bufio"
"os"
"strings"
)
func main() {
value := Input("Please enter a value: ")
trimmed := strings.TrimSpace(value)
fmt.Printf("Hello %s!\n", trimmed)
}
func Input(str string) string {
print(str)
reader := bufio.NewReader(os.Stdin)
input, _ := reader.ReadString('\n')
return input
}