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

时间:2011-01-09 23:38:54

标签: 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
            }            
        }
    }

6 个答案:

答案 0 :(得分: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
}
你可以说

inputState match {
  case (true,  _,  true) | (false, _, false) => false
  case _   => true
}

或者,简单但可能不那么清楚(取决于目的/背景)

def rule(inputState:(Boolean, Boolean, Boolean)) = inputState._1 != inputState._3

答案 1 :(得分: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)
}

现在,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
})

这就是我选择f成为PartialFunction的原因,因此我们可以使用Scala语法来定义部分函数而不需要任何开销,就像这样。缺点是如果匹配不详尽,编译器不会抱怨。你需要下定决心,这对你来说更重要:代码简洁或详尽的错误检查。

如果是后者,您可以将f更改为Function,方法是将其类型声明为((Boolean,Boolean,Boolean))=>Boolean。在这种情况下,您需要通过将f作为闭包传递给构造函数来声明特定规则。

您可以非常轻松地应用Rule功能:

val state = (true, false, true)
val newState = rule90(state) // should be false

此外,还有更多技巧可以做。假设您在列表中包含所有规则:

val ruleList = List(rule01, rule02, rule03 /* .... */)

然后,您可以例如从规则ID到规则实例进行映射,如下所示:

val rules = ruleList.map(r=>(r.id, r)).toMap

您可以访问并使用这样的规则:

val state = (true, false, true)
val newState = rules(90)(state)

或者,为了获得所有规则的所有下一个状态:

val state = (true, false, true)
val newStates = rules mapValues _(state)

并访问其中一个结果:

val newState_90 = newStates(90)

非常酷。但是,您也可以使用原始Rule定义完成大部分操作。我只想稍微想一想这个想法,因为我喜欢细胞自动机。

答案 2 :(得分: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))

为确保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))

那么理论足够了,回到代码!

对于你的例子(因为我理解了一些潜在的问题)我在这里用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)
}

然后,我确实说我明白了潜在的问题!因此,让我们废除所有这些繁琐的手动规则定义,并让编译器为我们生成批次:)

如果你不介意一些有点笨拙......

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

(从此处获取的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
...............
...............

请原谅所有toSeq来电,他们主要强制进行评估,以便您可以在REPL上看到一些实际输出,而不仅仅是non-empty iterator

答案 3 :(得分:2)

您可以使用提取器在输入状态值上添加含义

object EqualLeftAndRight {
  def unapply(inputState:(Boolean, Boolean, Boolean)) = inputState._1 == inputState._3
}

def rule(inputState:(Boolean, Boolean, Boolean)) : Boolean =
  inputState match {
    case EqualLeftAndRight() => true
    case _ => false
}

另外,要扩展@Madoc的答案,因为你传递了部分功能,所以不需要涵盖所有情况:

case class Rule(val id:Int)(f:PartialFunction[(Boolean,Boolean,Boolean),Boolean]) extends (((Boolean,Boolean,Boolean))=>Boolean) {
  def apply(state:(Boolean,Boolean,Boolean)) = if (f.isDefinedAt(state)) f(state) else false
}

val rule90 = Rule(90) {
  case EqualLeftAndRight() => true
}

答案 4 :(得分:0)

还有一点,如果你的ruleIds是常量,那么你可以(并且应该)将它们声明为val而不是null-arg defs。与defs一样,定义的val可以是特征中的抽象,也可以是类中的具体。

trait Rule {
        val ruleId : Int
}

class Rule90 extends Rule {
        val ruleId= 90     
}

答案 5 :(得分:0)

进一步简化......

Welcome to Scala version 2.8.0.final (Java HotSpot(TM) Client VM, Java 1.6.0_21).
Type in expressions to have them evaluated.
Type :help for more information.

scala> def rule(inputState:(Boolean, Boolean, Boolean)) = inputState._1 != inputState._3
rule: (inputState: (Boolean, Boolean, Boolean))Boolean

scala> rule((true, false, true))
res0: Boolean = false

scala> rule((true, false, false))
res1: Boolean = true

scala> rule((false, false, false))
res2: Boolean = false

scala> rule((false, true, false)) 
res3: Boolean = false

scala> rule((false, true, true)) 
res4: Boolean = true

哎呀,对不起,我看到@Debilski已经有这个了。