美文网首页
一个有趣的求解质数的golang代码

一个有趣的求解质数的golang代码

作者: StormZhu | 来源:发表于2018-11-21 23:03 被阅读0次

    看到一个golang写的求质数的程序,第一眼看上去很难理解,理解了之后又觉得很有趣,特此分析一下。

    代码

    package main
    
    import "fmt"
    
    // Send the sequence 2, 3, 4, ... to channel 'ch'.
    func generate(ch chan int) {
        for i := 2; ; i++ {
            ch <- i // Send 'i' to channel 'ch'.
        }
    }
    
    // Copy the values from channel 'in' to channel 'out',
    // removing those divisible by 'prime'.
    func filter(in, out chan int, prime int) {
        for {
            i := <-in // Receive value of new variable 'i' from 'in'.
            if i%prime != 0 {
                out <- i // Send 'i' to channel 'out'.
            }
        }
    }
    
    // The prime sieve: Daisy-chain filter processes together.
    func main() {
        ch := make(chan int) // Create a new channel.
        go generate(ch)      // Start generate() as a goroutine.
        for i:=0; i<20; i++{
            prime := <-ch
            fmt.Print(prime, " ")
            ch1 := make(chan int)
            go filter(ch, ch1, prime)
            ch = ch1
        }
    }
    

    分析

    首先,求质数(素数)有很多种方法,这个代码用的方法是:

    假设有一个数n,已知小于n的所有质数的集合是arr,如果arr中的每个质数都不能够整除n,则n就是质数。这是因为任意一个合数都可以分解成质数的乘积。

    例如,

    因为 2 是质数,初始化 arr=[2];

    当n=3,3 % 2 != 0, 则 n=3是质数, arr=[2,3];

    当n=4,4 % 2 == 0, 不满足要求,则4不是质数, arr不变;

    当n=5,,5 % 2 != 0, 5 % 3 != 0, 则 n=5是质数, arr=[2,3,5];

    ... 依此类推

    两个主要的函数

    1. generate函数就是往通道ch中放入n(从2开始)进行筛选。这个ch是没有缓冲区的通道。程序一开始,就启动一个协程执行generate函数(称之为generate协程)。

    2. filter函数有三个参数,两个无缓冲区的通道,inout,和一个质数prime。首先从in中取出一个数据i,如果i不能够被prime整除,则说明它有可能是个质数,则将其放入out通道中。

      重点有两个地方,首先filter函数居然有个for循环(说明不止使用一次),其次,out的数据由谁取?

    主逻辑

       for i:=0; i<20; i++{
           prime := <-ch   // [1]
           fmt.Print(prime, " ")
           ch1 := make(chan int)  // [2]
           go filter(ch, ch1, prime)  // [3]
           ch = ch1   // [4]
       }
    

    首先,代码[1]从通道ch中取出一个数2,它必然是质数。代码[2]新建了一个无缓冲的通道ch1,代码[3]启动一个协程运行fliter函数(称之为fliter1协程),其中参数inchoutch1prime2,语句[4]是将通道ch赋值为了通道ch1,然后又转到了语句[1],这实质上就是从ch1中取数据,也其实就是从fliter1协程的out通道取数据。

    fliter1协程的in通道会接收到数字3(由generate协程放入),经过判断,将3放入了out通道。在语句[1]中,从ch也就是out通道中取出3并打印。

    当判断数字3时,如图所示:

    输入3时.png

    打印出数字3之后,又启动了一个协程运行fliter函数,称之为fliter2协程,它的参数in是通道ch,实际上就是之前的ch1,也就是fliter1协程的out通道!而out通道最后又赋值给了ch。而这时的prime变成了3。这样做显而易见,因为再次新来一个数的时候,不仅需要看能不能被2整除(由fliter1协程判断),还要看能不能被3整除(由fliter2协程判断)。

    当判断数字4时,由于fliter1协程判断不通过,所有并没有放入fliter2协程,语句[1]也就不能取出数,就会阻塞。

    当判断数字5时,传入fliter1协程,通过,再次传入fliter2协程,继续判断通过,从out通道传出,实际上就是语句[1]ch通道,所有打印出5

    当判断数字5时,如图所示:

    输入5时.png

    所以,可以推理出,数字n前面有m个质数,就会产生mfliter协程,这些协程串联在一起,每个协程负责判断能否被某个质数整除,如果不能,则传递个给下一个协程进行判断,直至输出。

    总结

    • ch=ch1的时候,原先的ch并没有消失,还被之前启动的fliter协程保存着。
    • 巧妙使用通道的性质,将多个协程进行串联。
    • 最开始我还以为是一个并行的筛选质数的代码,结果并不是,其实是串行执行。

    相关文章

      网友评论

          本文标题:一个有趣的求解质数的golang代码

          本文链接:https://www.haomeiwen.com/subject/orahqqtx.html