更快的输入扫描

时间:2015-07-10 05:49:21

标签: string go

我正在尝试解决可以找到的here

的SPOJ问题

以下是我的解决方案:

package main

import "fmt"
import "bufio"
import "os"

func main() {
    var n, k int
    var num int
    var divisible int

    in := bufio.NewReader(os.Stdin)

    fmt.Fscan(in, &n)
    fmt.Fscan(in, &k)

    for n > 0 {
        fmt.Fscan(in, &num)

        if num%k == 0 {
            divisible++
        }

        n--
    }

    fmt.Println(divisible)
}

代码工作正常。这里的问题是我在SPOJ中执行它时会出现超时。

我第一次只使用fmt.Scan,但后来遇到this线程,建议我使用bufio代替更快的输入扫描。

但我仍然遇到超时问题。我只是循环以获取所有输入,并且在此循环内我确定输入是否可被整除。所以,我认为它不是循环,而是输入扫描需要时间。如何改进这一点以更快地读取输入?或者是其他地方的问题?

3 个答案:

答案 0 :(得分:9)

您可以使用bufio.Scanner从输入中读取行。

由于我们一直在阅读数字,因此我们可以创建一个高度优化的转换器来获取数字。我们应该避免使用创建string的{​​{3}},因为我们可以从Scanner.Text()返回的原始字节中获取数字。 Scanner.Text()返回与Scanner.Bytes()相同的标记,但它首先转换为string,这显然较慢,并生成“垃圾”并为gc工作。

所以这是一个转换器函数,它从原始字节中获取int

func toInt(buf []byte) (n int) {
    for _, v := range buf {
        n = n*10 + int(v-'0')
    }
    return
}

toInt()有效,因为[]byte包含数字十进制格式的字符串表示形式的UTF-8编码字节序列,其中仅包含'0'..'9'范围内的数字其UTF-8编码字节一对一映射(一个字节用于一个数字)。从数字到字节的映射只是一个转变:'0' -> 48'1' -> 49等。

使用此完整的应用程序:

package main

import (
    "bufio"
    "fmt"
    "os"
)

func main() {
    var n, k, c int
    scanner := bufio.NewScanner(os.Stdin)

    scanner.Scan()
    fmt.Sscanf(scanner.Text(), "%d %d", &n, &k)

    for ;n > 0; n-- {
        scanner.Scan()
        if toInt(scanner.Bytes())%k == 0 {
            c++
        }
    }

    fmt.Println(c)
}

func toInt(buf []byte) (n int) {
    for _, v := range buf {
        n = n*10 + int(v-'0')
    }
    return
}

此解决方案比调用Scanner.Bytes()的速度快4倍。

备注:

在上面的解决方案中,我假设输入是有效的,即它始终包含有效数字,并且在第一个之后包含至少n行(这给出了nk)。

如果输入在n+1行之后关闭,我们可以使用简化的for(我们甚至不需要递减并依赖n):

for scanner.Scan() {
    if toInt(scanner.Bytes())%k == 0 {
        c++
    }
}

答案 1 :(得分:1)

尝试使用bufio.Scanner(如您提到的主题中所示):

fmt.Scan(&n)
fmt.Scan(&k)

scanner := bufio.NewScanner(os.Stdin)
for n > 0 {
    scanner.Scan()
    k, _ := strconv.Atoi(scanner.Text())
    ...

答案 2 :(得分:0)

我编写了3个版本进行比较。 第一个使用fmt.Scanf("%d", &v),第二个使用字节转换数字(例如@icza),第三个使用strconv.Atoi进行转换。要以这种方式使用初始化扫描仪的功能:

scanner := bufio.NewScanner(os.Stdin)
scanner.Split(bufio.ScanWords)

因此,每次我调用scan.Scan时,它都会返回一个由空格分隔的令牌。 功能如下:

func scanFromBytes(scanner *bufio.Scanner) (n int) {
    scanner.Scan()

    buf := scanner.Bytes()
    for _, v := range buf {
        n = n*10 + int(v-'0')
    }

    return
}

并且:

func scanAtoi(scanner *bufio.Scanner) (n int) {
    scanner.Scan()
    
    n, _ = strconv.Atoi(scanner.Text())

    return
}

我已经用一个大文件(40k个测试)进行了测试,每个测试读取大约8个整数。 fmt.Scanf解决方案大约需要1.9s的时间(比其他解决方案要长)。 在这两个函数中,我得到了大约0.8s。但是scanAtoi总是比scanFromBytes少0.05秒,除了第一次(可能会发生一些缓存)。