2

I'm trying to scan a list of integers from a string into an array (or alternatively, a slice)

package main

import "fmt"

func main() {
    var nums [5]int
    n, _ := fmt.Sscan("1 2 3 4 5", &nums)  // doesn't work
    fmt.Println(nums)
}

What do I need to pass as second argument to Sscan in order for this to work?

I know I could pass nums[0], nums[1] ... etc., but I'd prefer a single argument.

Johannes Charra
  • 29,455
  • 6
  • 42
  • 51
  • Have a look at http://stackoverflow.com/questions/9862443/golang-is-there-a-better-way-read-a-file-of-integers-into-an-array – Intermernet Feb 19 '15 at 11:54

3 Answers3

6

I don't think this is possible as a convenient one-liner. As Sscan takes ...interface{}, you would need to pass slice of interfaces as well, hence converting your array first:

func main() {
    var nums [5]int

    // Convert to interfaces
    xnums := make([]interface{}, len(nums))
    for n := range nums {
        xnums[n] = &nums[n]
    }

    n, err := fmt.Sscan("1 2 3 4 5", xnums...)
    if err != nil {
        fmt.Printf("field %d: %s\n", n+1, err)
    }

    fmt.Println(nums)
}

http://play.golang.org/p/1X28J7JJwl

Obviously you could mix different types in your interface array, so it would make the scanning of more complex string easier. For simply space-limited integers, you might be better using strings.Split or bufio.Scanner along with strconv.Atoi.

Johannes Charra
  • 29,455
  • 6
  • 42
  • 51
tomasz
  • 12,574
  • 4
  • 43
  • 54
  • I like this answer, it uses Sscan's reflection cleverly. You can change `var nums [5]int` to `var nums [5]string` and it still works :-) . Also, it returns *sort of* sensible values for silly input! See http://play.golang.org/p/sWapMMVTcM. Only problem is possibly the use of `0` for invalid input when it's `[5]int`. `strconv.Atoi` is probably therefore recommended. – Intermernet Feb 19 '15 at 12:28
  • @Intermernet, you obviously still have error checking within `Sccan`, I should've checked for the returned errors in my answer really. See a modified example of yours: http://play.golang.org/p/F2werhtqdU [updated my answer to reflect this!] – tomasz Feb 19 '15 at 12:48
  • Yes, I was being picky :-) I learnt a good shortcut from your answer, so thanks again! – Intermernet Feb 19 '15 at 12:52
3

To allow this to work on more than just hard-coded strings, it's probably better to use a bufio.Scanner, and an io.Reader interface to do this:

package main

import (
    "bufio"
    "fmt"
    "io"
    "strconv"
    "strings"
)

func scanInts(r io.Reader) ([]int, error) {
    s := bufio.NewScanner(r)
    s.Split(bufio.ScanWords)
    var ints []int
    for s.Scan() {
        i, err := strconv.Atoi(s.Text())
        if err != nil {
            return ints, err
        }
        ints = append(ints, i)
    }
    return ints, s.Err()
}

func main() {
    input := "1 2 3 4 5"
    ints, err := scanInts(strings.NewReader(input))
    if err != nil {
        fmt.Println(err)
    }
    fmt.Println(ints)
}

Produces:

[1 2 3 4 5]

Playground

Intermernet
  • 18,604
  • 4
  • 49
  • 61
  • Thank you. This looks a little too complex for my simple use case, but I learned some new things (still in the beginner phase with go, as you might have guessed from the question). – Johannes Charra Feb 19 '15 at 12:34
  • @JohannesCharra No problem, the important bit that you will probably need to remember, is that the string may not contain just integers, and your code needs to take that into account. `strconv.Atoi` will check that the next "thing" can be converted to an integer, and if not, it will return an error. You should use it, whatever solution you end up adopting for this problem. – Intermernet Feb 19 '15 at 12:37
1

Unless you're trying to use Sscann specifically you can also try this as an alternative:

  • split the input string by spaces
  • iterate the resulting array
  • convert each string into an int
  • store the resulting value into an int slice

Like this:

package main

import (
    "fmt"
    "strconv"
    "strings"
)

func main() {

    nums := make([]int, 0)
    for _, s := range strings.Split("1 2 3 4 5", " ") {
        i, e := strconv.Atoi(s)
        if e != nil {
            i = 0 // that was not a number, default to 0
        }
        nums = append(nums, i)
    }
    fmt.Println(nums)
}

http://play.golang.org/p/rCZl46Ixd4

OscarRyz
  • 196,001
  • 113
  • 385
  • 569