Search code examples
goconcurrencychannelgoroutine

how comparing value from channel in Go


I have two channels, first give me some strings, which I need filter to same values, then result need sent to second channel

func main() {
    c := make(chan string, 5)
    o := make(chan string, 5)
    arr := []string{"aa", "ab", "ab", "bb", "bb", "ba", "cc"}
    for _, v := range arr {
        c <- v
        go removeDuplicates(c, o)
        time.Sleep(1 * time.Second)
        fmt.Println("output: ", <-o)
    }
}

func removeDuplicates(cIn, cOut chan string) {
   last := ""
   for cur, isOpen := <-cIn; isOpen; {
      if cur != last {
        fmt.Printf("val: %s, last: %s\n", cur, last) 
        last = cur
        cOut <- cur
        //close(cOut)
      }
   }
}

I try save previous value to "last" variable, but when I run the program, "last" is empty

val: aa, last: 
output:  aa
val: ab, last: 
output:  ab
val: ab, last:

also I don't know when and which channels need to be closed in this situation. Thank you for your help and attention


Solution

  • First fixing removeDuplicates()

    The problem is that you have an empty post statement in your for statement:

    for cur, isOpen := <-cIn; isOpen; {
        // ..
    }
    

    So you receive once from the cIn channel, but you never receive more, you do nothing in the post statement, so you just repeat the loop body, endlessly.

    Once the loop body is executed, you have to receive again:

    for cur, isOpen := <-cIn; isOpen; cur, isOpen = <-cIn {
        // ..
    }
    

    With this, output will be (try it on the Go Playground):

    val: aa, last: 
    output:  aa
    val: ab, last: aa
    output:  ab
    val: ab, last: 
    output:  ab
    val: bb, last: ab
    output:  bb
    val: bb, last: 
    output:  bb
    val: ba, last: ab
    output:  ba
    val: cc, last: 
    output:  cc
    

    But best would be to use for range over the channel:

    for cur := range cIn {
        if cur != last {
            fmt.Printf("val: %s, last: %s\n", cur, last)
            last = cur
            cOut <- cur
        }
    }
    

    This outputs the same. Try this one on the Go Playground.

    Now on to fixing the main()

    We see "invalid" output, values in output are still duplicated.

    This is because you launch multiple goroutines running removeDuplicates(). This is bad because the values sent on the input channel will be received by multiple goroutines, and if the duplicated values are not received by one, they can still be detected as unique, hence the same value will be sent more than once to the output.

    Have a single producer sending all values on the input channel, and once all values were sent, close the channel.

    Have a single goroutine filtering the values, using for range, and once the loop exits (all input values are consumed), close the output channel.

    And have a single goroutine receive values from the output channel, using for range, so you can eliminate that ugly time.Sleep:

    func main() {
        c := make(chan string, 5)
        o := make(chan string, 5)
    
        go func() {
            arr := []string{"aa", "ab", "ab", "bb", "bb", "ba", "cc"}
            for _, v := range arr {
                c <- v
            }
            close(c)
        }()
    
        go removeDuplicates(c, o)
    
        for v := range o {
            fmt.Println("output: ", v)
        }
    }
    
    func removeDuplicates(cIn chan string, cOut chan string) {
        last := ""
        for cur := range cIn {
            if cur != last {
                fmt.Printf("val: %s, last: %s\n", cur, last)
                last = cur
                cOut <- cur
            }
        }
        close(cOut)
    }
    

    This will output (try it on the Go Playground):

    val: aa, last: 
    val: ab, last: aa
    val: bb, last: ab
    val: ba, last: bb
    val: cc, last: ba
    output:  aa
    output:  ab
    output:  bb
    output:  ba
    output:  cc