观察者模式或发布/订阅模式适用于细胞自动机。

huangapple go评论156阅读模式
英文:

Observer pattern or a Publish/Subscribe pattern for a cellular automaton

问题

我正在尝试为一种细胞自动机编写观察者模式或发布/订阅模式。

经典的观察者模式行不通,因为如果细胞A订阅细胞B的变化,反之亦然,应用程序将因为递归调用(B.update()将调用A.update()等等),导致堆栈溢出。

因此,我考虑使用发布/订阅模式,其中各个细胞通过传递消息而不是调用彼此的update()方法来通信。

这里有一个简单的示例,其中包含两个细胞A和B:

package main

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

func main() {

    fmt.Printf("Starting\n")

    chEnd := make(chan int)

    // 初始化
    a := ps.NewNode(1, 0)
    b := ps.NewNode(2, 0)

    // 连接节点
    a.Connect(b.ChOut)
    b.Connect(a.ChOut)

    // 开始监听
    a.Listen()
    b.Listen()

    // 在一个任意节点上开始发送数据
    // 以启动进程。
    a.ChIn <- 10

    <-chEnd
}

对应的库如下:

package pubsub

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
}

每个节点都有一个输入和一个输出通道。
B的输入通道是A的输出通道,反之亦然。

每次更新仅仅是将另一个细胞传递的数据加一。

看起来是有效的。到目前为止,一切都好。

我尝试使用一组4个细胞A、B、C、D来做类似的事情,以模拟一维细胞自动机。

在这第二次尝试中,
每个细胞都有两个输入通道(左和右)来监听其最近的左右邻居(ChinL和ChinR),
每个细胞都有两个输出通道,用于将其最新更新的状态传递给最近的邻居(ChoutL和ChoutR)。
在这个使用4个细胞的方案的实现中,我一定做错了什么,因为它产生了奇怪的结果:4个细胞之间来回传递的值似乎达到了一个阈值,而不是在每个连续的步骤中递增:以下是代码:

package main

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

func main() {

    fmt.Printf("Starting\n")

    chEnd := make(chan int)

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

    // 连接节点
    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

    // 开始监听
    go a.Listen()
    go b.Listen()
    go c.Listen()
    go d.Listen()

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

    // 在一个任意节点上开始发送数据
    // 以启动进程。
    a.ChInL <- 1

    // 读取通道上的虚拟数据,使main()等待
    <-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
            }()
        }
    }
}

为了完整起见,这是上述模块的go.mod文件:

module publish

go 1.17

英文:

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 thought 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 (
    &quot;fmt&quot;
    ps &quot;publish/pubsub&quot;
)

func main() {

    fmt.Printf(&quot;Starting\n&quot;)

    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 &lt;- 10

    &lt;-chEnd
}

and the corresponding lib

package pubsub

import (
    &quot;fmt&quot;
)

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 &lt;- inItem + 1
    //time.Sleep(100 * time.Millisecond)
}

func (p *Node) Listen() {
    go func() {
        for {
            select {
            case inItem := &lt;-p.ChIn:
                fmt.Printf(&quot;%d: %d\n&quot;, 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 do something similar 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 (
    &quot;fmt&quot;
    ps &quot;publish/pubsub&quot;
)

func main() {

    fmt.Printf(&quot;Starting\n&quot;)

    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 &lt;- 1

    // Dummy read on channel to make main() wait
    &lt;-chEnd
}

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

and the corresponding lib


package pubsub

import (
    &quot;fmt&quot;
    &quot;strings&quot;
)

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 &lt;- item + 1
        p.ChOutR &lt;- item + 1
        fmt.Printf(&quot;%d: %d %s\n&quot;, p.Id, item, strings.Repeat(&quot;*&quot;, item))
    }
}

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

For completeness, here is the go.mod for the modules above:

module publish

go 1.17

答案1

得分: 4

对于节点在 p.ChInLp.ChInR 上接收到的每个信号,你发送两个信号。第一个信号发送到 p.ChOutL,第二个信号发送到 p.ChOutR。由于每个节点都这样做,信号的数量将呈指数增长。

在本地运行时,阈值在20到25之间。因此大约有2^25(33554432)个信号在传递。要看到26,程序将需要处理67108864个信号。因此,它将超过这个阈值,但速度会指数级变慢。

现在来修复一下。我认为你应该实现一种 tick 系统。所以,不是在每次自动机发生变化时发送更新信号,而是每个 tick 发送一次更新信号,比如每秒20次。

或者更好的是,不使用例程和通道,只需创建一个节点的切片并循环遍历它们(同样只在同一个 tick/循环中更新邻居而不传播)。循环的每次迭代都会改变所有节点的状态。我相信这也是其他自动机(如生命游戏)的工作原理。现在,你可以以计算机的最快速度运行模拟。

英文:

For every signal a node receives on p.ChInL or p.ChInR you send out 2 signals. The first to p.ChOutL and the second to p.ChOutR. Since every node does this there will be an exponential amount of signals going around.

When running it locally the threshold is between 20 and 25. So about 2^25 33554432 signals going around. To see 26, the program will need to process 67108864 signals. So it will go past this threshold, just exponentially slower.

Now for the fix. I think you should implement some sort of tick system. So instead of sending update signals for every change to the automaton you send one update every tick like 20 times per second.

Maybe better yet, instead of using routines and channels, just make an slice of nodes and loop over them (again only updating the neighbor and not propagating it in the same tick/loop). Each iteration of the loop the state of all nodes has changed. I believe this is also how other automatons like game-of-live work. Now you can run the simulation as fast as your computer can run.

答案2

得分: 0

这是一个可能的替代方案:

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
		}
	}
}

希望对你有帮助!

英文:

Here is one possible alternative:

package pubsub
import (
&quot;fmt&quot;
&quot;math/rand&quot;
&quot;strings&quot;
&quot;time&quot;
)
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 &lt;- item + 1
} else {
p.ChOutR &lt;- item + 1
}
fmt.Printf(&quot;%d: %d %s\n&quot;, p.Id, item, strings.Repeat(&quot;*&quot;, item))
}
}
func (p *Node) Listen() {
for {
time.Sleep(100 * time.Millisecond)
select {
case inItem := &lt;-p.ChInL:
p.ChIO &lt;- inItem
case inItem := &lt;-p.ChInR:
p.ChIO &lt;- inItem
}
}
}

答案3

得分: 0

这是第二个替代方案:

package main

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

func main() {

	fmt.Printf("Hello\n")

	chEnd := make(chan int)

	A := ps.NewNode(1, 0)
	B := ps.NewNode(2, 0)
	C := ps.NewNode(3, 0)
	D := ps.NewNode(4, 0)

	B.LeftNode = &A
	B.RightNode = &C
	C.LeftNode = &B
	C.RightNode = &D
	D.LeftNode = &C
	D.RightNode = &A
	A.LeftNode = &D
	A.RightNode = &B

	A.Listen()
	B.Listen()
	C.Listen()
	D.Listen()

	A.State = 1
	<-chEnd

}

//----

package pubsub

import (
	"fmt"
	"strings"
	"sync"
	"time"
)

type Node struct {
	Id         int
	State      int
	LeftNode   *Node
	RightNode  *Node
	LeftState  int
	RightState int
}

var m sync.Mutex

func NewNode(id int, state int) Node {
	return Node{id, state, nil, nil, 0, 0}
}

func (n *Node) Listen() {
	go func() {
		for {
			m.Lock()
			time.Sleep(10 * time.Millisecond)
			if n.LeftState != n.LeftNode.State {
				n.LeftState = n.LeftNode.State
				n.State = n.LeftNode.State + 1
				fmt.Printf("%d: %d %s\n", n.Id, n.State, strings.Repeat("*", n.State))
			}
			m.Unlock()
		}
	}()
	go func() {
		for {
			m.Lock()
			time.Sleep(10 * time.Millisecond)
			if n.RightState != n.RightNode.State {
				n.RightState = n.RightNode.State
				n.State = n.RightNode.State + 1
				fmt.Printf("%d: %d %s\n", n.Id, n.State, strings.Repeat("*", n.State))
			}
			m.Unlock()
		}
	}()
}
英文:

Here is a second alternative:

package main
import (
&quot;fmt&quot;
ps &quot;pub/pubsub&quot;
)
func main() {
fmt.Printf(&quot;Hello\n&quot;)
chEnd := make(chan int)
A := ps.NewNode(1, 0)
B := ps.NewNode(2, 0)
C := ps.NewNode(3, 0)
D := ps.NewNode(4, 0)
B.LeftNode = &amp;A
B.RightNode = &amp;C
C.LeftNode = &amp;B
C.RightNode = &amp;D
D.LeftNode = &amp;C
D.RightNode = &amp;A
A.LeftNode = &amp;D
A.RightNode = &amp;B
A.Listen()
B.Listen()
C.Listen()
D.Listen()
A.State = 1
&lt;-chEnd
}
//----
package pubsub
import (
&quot;fmt&quot;
&quot;strings&quot;
&quot;sync&quot;
&quot;time&quot;
)
type Node struct {
Id         int
State      int
LeftNode   *Node
RightNode  *Node
LeftState  int
RightState int
}
var m sync.Mutex
func NewNode(id int, state int) Node {
return Node{id, state, nil, nil, 0, 0}
}
func (n *Node) Listen() {
go func() {
for {
m.Lock()
time.Sleep(10 * time.Millisecond)
if n.LeftState != n.LeftNode.State {
n.LeftState = n.LeftNode.State
n.State = n.LeftNode.State + 1
fmt.Printf(&quot;%d: %d %s\n&quot;, n.Id, n.State, strings.Repeat(&quot;*&quot;, n.State))
}
m.Unlock()
}
}()
go func() {
for {
m.Lock()
time.Sleep(10 * time.Millisecond)
if n.RightState != n.RightNode.State {
n.RightState = n.RightNode.State
n.State = n.RightNode.State + 1
fmt.Printf(&quot;%d: %d %s\n&quot;, n.Id, n.State, strings.Repeat(&quot;*&quot;, n.State))
}
m.Unlock()
}
}()
}

huangapple
  • 本文由 发表于 2021年11月14日 22:34:27
  • 转载请务必保留本文链接:https://go.coder-hub.com/69964054.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定