Trying to code an observer pattern or a publish/submit pattern for a cellular automaton

97 views
Skip to first unread message

Serge Hulne

unread,
Nov 14, 2021, 9:29:26 AM11/14/21
to golang-nuts
Hi,

I am trying to code an observer pattern or a publish/submit pattern for a sort of cellular automaton.

The classical observer pattern does not to the trick because if a cell A subscribes to changes in a cell B and vice-versa, the application will run out of stack owing to the recursive approach (B.update() will call A.update() and so on and the app will run out of stack). 

So I though about using a publish/subscribe pattern where respective cells pass each other messages, rather than calling each other's update() methods.

Here is a simple example with two cells A and B:

```
package main

import (
"fmt"
ps "publish/pubsub"
)

func main() {

fmt.Printf("Starting\n")

chEnd := make(chan int)

// initialize
a := ps.NewNode(1, 0)
b := ps.NewNode(2, 0)

// connect nodes
a.Connect(b.ChOut)
b.Connect(a.ChOut)

// Start listening
a.Listen()
b.Listen()

// Start sending data on one arbitrary node
// to start the process.
a.ChIn <- 10

<-chEnd
}
```
and the corresponding lib

```package lib

import (
"fmt"
)

type Node struct {
Id int
State int
ChOut chan int
ChIn chan int
}

func NewNode(id int, state int) Node {
chout := make(chan int)
var chin chan int
return Node{id, state, chout, chin}
}

func (p *Node) Broadcast(inItem int) {
p.ChOut <- inItem + 1
//time.Sleep(100 * time.Millisecond)
}

func (p *Node) Listen() {
go func() {
for {
select {
case inItem := <-p.ChIn:
fmt.Printf("%d: %d\n", p.Id, inItem)
p.Broadcast(inItem)
}
}
}()
}

func (p *Node) Connect(ch chan int) {
p.ChIn = ch
}
```
Each node has a input and an output channe.
The input channel of B is the output channel of A and vice-versa.

Every update consists merely of incrementing the data passed by the other cell.

It seems to work. So far, so good.

I tried to with a set of 4 cells A, B, C, D, in order to simulate a one dimensional cellular automaton of sorts.

In this second attempt, 
  • each cell has two input channels (let and right) to listen to its closest left- and right-hand neighbour, respectively (ChinL and ChinR).
  • each cell has to output channels to communicate its latest updated state to its closest neighbours (ChoutL and ChoutR).
I must have done something wrong in the implementation of that scheme with 4 cells, because it yields odd results : the values passed back and forth between the 4 cells seem to hit a threshold instead of increasing at every consecutive step: here is the code:

```
package main

import (
"fmt"
ps "publish/pubsub"
)

func main() {

fmt.Printf("Starting\n")

chEnd := make(chan int)

// initialize
a := ps.NewNode(1, 0)
b := ps.NewNode(2, 0)
c := ps.NewNode(3, 0)
d := ps.NewNode(4, 0)

// connect nodes
a.ChInL = d.ChOutR
a.ChInR = b.ChOutL

b.ChInL = a.ChOutR
b.ChInR = c.ChOutL

c.ChInL = b.ChOutR
c.ChInR = d.ChOutL

d.ChInL = c.ChOutR
d.ChInR = a.ChOutL

// Start listening
go a.Listen()
go b.Listen()
go c.Listen()
go d.Listen()

go a.Broadcast()
go b.Broadcast()
go c.Broadcast()
go d.Broadcast()

// Start sending data on one arbitrary node
// to start the process.
a.ChInL <- 1

// Dummy read on channel to make main() wait
<-chEnd
}

/*
A B C D
LR LR LR LR
*/

```

and the corresponding lib

```
package main

import (
"fmt"
ps "publish/pubsub"
)

func main() {

fmt.Printf("Starting\n")

chEnd := make(chan int)

// initialize
a := ps.NewNode(1, 0)
b := ps.NewNode(2, 0)
c := ps.NewNode(3, 0)
d := ps.NewNode(4, 0)

// connect nodes
a.ChInL = d.ChOutR
a.ChInR = b.ChOutL

b.ChInL = a.ChOutR
b.ChInR = c.ChOutL

c.ChInL = b.ChOutR
c.ChInR = d.ChOutL

d.ChInL = c.ChOutR
d.ChInR = a.ChOutL

// Start listening
go a.Listen()
go b.Listen()
go c.Listen()
go d.Listen()

go a.Broadcast()
go b.Broadcast()
go c.Broadcast()
go d.Broadcast()

// Start sending data on one arbitrary node
// to start the process.
a.ChInL <- 1

// Dummy read on channel to make main() wait
<-chEnd
}

/*
A B C D
LR LR LR LR
*/

```

package pubsub

import (
"fmt"
"strings"
)

type Node struct {
Id int
State int
ChOutL chan int
ChOutR chan int
ChInL chan int
ChInR chan int
ChIO chan int
}

func NewNode(id int, state int) Node {
choutL := make(chan int)
choutR := make(chan int)
var chinL chan int
var chinR chan int
chIO := make(chan int)
return Node{id, state, choutL, choutR, chinL, chinR, chIO}
}

func (p *Node) Broadcast() {
for item := range p.ChIO {
p.ChOutL <- item + 1
p.ChOutR <- item + 1
fmt.Printf("%d: %d %s\n", p.Id, item, strings.Repeat("*", item))
}
}

func (p *Node) Listen() {
for {
//time.Sleep(100 * time.Millisecond)
select {
case inItem := <-p.ChInL:
go func() {
p.ChIO <- inItem
}()
case inItem := <-p.ChInR:
go func() {
p.ChIO <- inItem
}()
}
}
}

```






Sean Liao

unread,
Nov 14, 2021, 12:21:00 PM11/14/21
to golang-nuts
that's exponential growth, every value results in 2 output values
input "1" x1, get "2" x2, "3" x4, "4" x8, "5" x16, ...
also your use of goroutines to send mean you'll still run out of memory at some point

Message has been deleted

Serge Hulne

unread,
Nov 14, 2021, 1:18:35 PM11/14/21
to golang-nuts
Here is another solution:
package pubsub

import (
"fmt"
"math/rand"
"strings"
"time"
)

type Node struct {
Id int
State int
ChOutL chan int
ChOutR chan int
ChInL chan int
ChInR chan int
ChIO chan int
}

func NewNode(id int, state int) Node {
choutL := make(chan int)
choutR := make(chan int)
var chinL chan int
var chinR chan int
chIO := make(chan int)
return Node{id, state, choutL, choutR, chinL, chinR, chIO}
}

func (p *Node) Broadcast() {
for item := range p.ChIO {
rnd := rand.Intn(2)
if rnd == 0 {
p.ChOutL <- item + 1
} else {
p.ChOutR <- item + 1
}
fmt.Printf("%d: %d %s\n", p.Id, item, strings.Repeat("*", item))
}
}

func (p *Node) Listen() {
for {
time.Sleep(100 * time.Millisecond)
select {
case inItem := <-p.ChInL:
p.ChIO <- inItem
case inItem := <-p.ChInR:
p.ChIO <- inItem
}
}
}



Reply all
Reply to author
Forward
0 new messages