Scala,将布尔元组的模式表示为其他东西

Ber*_*own 0 scala

这是一个元胞自动机规则(输入布尔值==左,中,右单元格)和输出布尔值.在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)

Deb*_*ski 5

代替

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)


Mad*_*doc 5

其他答案集中在如何优化模式匹配以缩短模式匹配.但是,我认为这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))=>Booleanf

您可以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定义完成大部分操作.我只想稍微想一下这个想法,因为我喜欢细胞自动机.


Kev*_*ght 5

一个观察......在典型的使用中,您会发现该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帮助方法来处理转换.我也将其重命名ruleapply,以便您的类可以直接用作函数:

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在这种情况下你也可以安全地取消,直接与outputval 一起工作.我会把它作为练习留给读者......

把它们放在一起(删除无用的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