这是一个元胞自动机规则(输入布尔值==左,中,右单元格)和输出布尔值.在Scala中表示这个的更好方法是什么?
trait Rule {
def ruleId() : Int
def rule(inputState:(Boolean, Boolean, Boolean)) : Boolean
override def toString : String = "Rule:" + ruleId
}
class Rule90 extends Rule {
def ruleId() = 90
def rule(inputState:(Boolean, Boolean, Boolean)) : Boolean = {
// Verbose version, show all 8 states
inputState match {
case (true, true, true) => false
case (true, false, true) => false
case (false, true, false) => false
case (false, false, false) => false
case _ => true
}
}
}
Run Code Online (Sandbox Code Playgroud)
代替
inputState match {
case (true, true, true) => false
case (true, false, true) => false
case (false, true, false) => false
case (false, false, false) => false
case _ => true
}
Run Code Online (Sandbox Code Playgroud)
你可以说
inputState match {
case (true, _, true) | (false, _, false) => false
case _ => true
}
Run Code Online (Sandbox Code Playgroud)
或者,简单但可能不那么清楚(取决于目的/背景)
def rule(inputState:(Boolean, Boolean, Boolean)) = inputState._1 != inputState._3
Run Code Online (Sandbox Code Playgroud)
其他答案集中在如何优化模式匹配以缩短模式匹配.但是,我认为这Rule90
可能只是一个例子.也许您的问题不是如何优化规则90的模式匹配,但是如果有更合适的方法来使用Scala结构定义规则类型.这是我对此的看法.
首先,我不建议为不同的规则制作子类.你应该有一个单独的Rule
类,所有具体的规则都是它的实例.这是我对Rule
班级的定义:
case class Rule(val id:Int, f:PartialFunction[(Boolean,Boolean,Boolean),Boolean])
extends (((Boolean,Boolean,Boolean))=>Boolean) {
def apply(state:(Boolean,Boolean,Boolean)) = f(state)
}
Run Code Online (Sandbox Code Playgroud)
现在,Rule
该类也是一个适当的Scala函数.您可以非常轻松地实例化它,如下所示:
val rule90 = Rule(90, {
case (true, true, true) => false
case (true, false, true) => false
case (false, true, false) => false
case (false, false, false) => false
case _ => true
})
Run Code Online (Sandbox Code Playgroud)
这就是我选择f
成为a 的原因PartialFunction
,因此我们可以使用Scala语法来定义部分函数而不需要任何开销,就像这样.缺点是如果匹配不详尽,编译器不会抱怨.您需要下定决心,这对您来说更重要:代码简洁或详尽的错误检查.
如果是后者,则可以通过将其类型声明f
为a 来更改为a .在这种情况下,您需要通过将闭包传递给构造函数来声明特定规则.Function
((Boolean,Boolean,Boolean))=>Boolean
f
您可以Rule
非常轻松地应用该功能:
val state = (true, false, true)
val newState = rule90(state) // should be false
Run Code Online (Sandbox Code Playgroud)
此外,你还可以做更多的技巧.假设您在列表中包含所有规则:
val ruleList = List(rule01, rule02, rule03 /* .... */)
Run Code Online (Sandbox Code Playgroud)
然后,您可以例如从规则ID到规则实例进行映射,如下所示:
val rules = ruleList.map(r=>(r.id, r)).toMap
Run Code Online (Sandbox Code Playgroud)
你可以访问并使用这样的规则:
val state = (true, false, true)
val newState = rules(90)(state)
Run Code Online (Sandbox Code Playgroud)
或者,为了获得所有规则的所有下一个状态:
val state = (true, false, true)
val newStates = rules mapValues _(state)
Run Code Online (Sandbox Code Playgroud)
并访问以下结果之一:
val newState_90 = newStates(90)
Run Code Online (Sandbox Code Playgroud)
很酷.但是,您也可以使用原始Rule
定义完成大部分操作.我只想稍微想一下这个想法,因为我喜欢细胞自动机.
一个观察......在典型的使用中,您会发现该sliding
方法是将数据输入自动机的最简单方法.它的工作原理如下:
scala> val input = Seq(1,2,3,4,5,6,7,8,9)
input: Seq[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9)
scala> input.sliding(3).toList
res4: List[Seq[Int]] =List(
List(1, 2, 3),
List(2, 3, 4),
...
List(6, 7, 8),
List(7, 8, 9))
Run Code Online (Sandbox Code Playgroud)
要确保输出的序列数sliding
等于输入元素的数量,您需要在两侧填充输入序列:
scala> (0 +: input :+ 0).sliding(3).toList
res9: List[Seq[Int]] = List(
List(0, 1, 2),
List(1, 2, 3),
...
List(7, 8, 9),
List(8, 9, 0))
Run Code Online (Sandbox Code Playgroud)
那么理论足够了,回到代码!
对于你的例子(因为我理解了一些潜在的问题)我在false
这里用值填充序列.
因为sliding
将输出序列而不是元组,我创建了seqYieldsTrue
帮助方法来处理转换.我也将其重命名rule
为apply
,以便您的类可以直接用作函数:
trait Rule {
def ruleId: Int //abstract
def yieldsTrue(input: (Boolean,Boolean,Boolean)): Boolean //abstract
override def toString: String = "Rule:" + ruleId
private def seqYieldsTrue(input: Seq[Boolean]) = {
assert(input.size == 3)
input match {
case Seq(a,b,c) => yieldsTrue((a,b,c))
case _ => error("invalid input size")
}
}
def apply(input: Seq[Boolean]) =
(false +: input :+ false).sliding(3) map { seqYieldsTrue }
}
class Rule90 extends Rule {
val ruleId = 90
val falsehoods = Seq(
(true, true, true),
(true, false, true),
(false, true, false),
(false, false, false)
)
def yieldsTrue(input: (Boolean,Boolean,Boolean)) = !falsehoods.contains(input)
}
Run Code Online (Sandbox Code Playgroud)
然后,我说我理解了潜在的问题!因此,让我们放弃所有这些繁琐的手动规则定义,让编译器为我们生成批量:)
如果你不介意一些有点笨拙......
class WolframAutomata(val ruleId: Int) extends Rule {
def pow2(x: Int) = math.pow(2,x).toInt
def isBitSet(x: Int, bit: Int) = (x & pow2(bit)) > 0
// 8 possible input patterns corresponding to
// different combinations of 3 input bits
val inputs = (0 to 7) map { id =>
Tuple3(
isBitSet(id, 2),
isBitSet(id, 1),
isBitSet(id, 0)
) -> id
} toMap
//each of the 8 input rules corresponds to one bit in the ruleId
val outputs = inputs mapValues { isBitSet(ruleId, _) }
def yieldsTrue(input: (Boolean,Boolean,Boolean)) = outputs(input)
}
Run Code Online (Sandbox Code Playgroud)
(从此处获取的ID生成自动机的规则:http://www.wolframscience.com/nksonline/page-53#previous)
像这样工作,您也可以将逻辑回滚到Rule特征中,因为如果只有一个子类,则几乎不需要单独的抽象特征.yieldsTrue
在这种情况下你也可以安全地取消,直接与output
val 一起工作.我会把它作为练习留给读者......
把它们放在一起(删除无用的REPL输出线):
scala> val r90 = new WolframAutomata(90)
r90: WolframAutomata = Rule:90
scala> def destringify(s:String) = s map { case 'X' => true; case _ => false } toSeq
scala> val input = destringify(".......X.......")
scala> val output = Iterator.iterate(input)(r90.apply(_).toSeq) toSeq
scala> def stringify(xs: Seq[Boolean]) = xs map {case true => "X"; case _ => "."} mkString
//can you see what it is yet?
scala> val sierpinski = output.take(10).map(stringify).mkString("\n")
sierpinski: String =
.......X.......
......X.X......
.....X...X.....
....X.X.X.X....
...X.......X...
..X.X.....X.X..
.X...X...X...X.
X.X.X.X.X.X.X.X
...............
...............
Run Code Online (Sandbox Code Playgroud)
请原谅所有的toSeq
电话,他们主要是强制评估,以便你可以在REPL上看到一些实际的输出,而不仅仅是non-empty iterator
归档时间: |
|
查看次数: |
846 次 |
最近记录: |