Scala, представляющий образец логического кортежа во что-то другое - PullRequest
0 голосов
/ 10 января 2011

Это правило клеточных автоматов (входное логическое значение == Левая, центральная, правая ячейка) и выходное логическое значение. Как лучше представить это в 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 ]

5 голосов
/ 10 января 2011

Одно наблюдение ... При обычном использовании вы обнаружите, что метод 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)
}

(правило для генерации автоматов из идентификаторов, взятых отсюда: http://www.wolframscience.com/nksonline/page-53#previous)

Работая так, вы также можете свернуть логику обратно в черту правила, так как нет особой необходимости в отдельномабстрактная черта, если когда-нибудь будет только один подкласс. Вы также можете безопасно покончить с 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

5 голосов
/ 10 января 2011

Другие ответы были направлены на то, как оптимизировать сопоставление с образцом, чтобы сделать его короче. Тем не менее, я думаю, что 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 /* .... */)

Затем вы можете, например, сделать преобразование из идентификатора правила в экземпляр правила следующим образом:

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. Я просто хотел немного поиграть с этой идеей, потому что я люблю клеточные автоматы.

5 голосов
/ 10 января 2011

вместо

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
2 голосов
/ 10 января 2011

Вы можете использовать экстракторы, чтобы придать смысл значениям состояния ввода

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
}
0 голосов
/ 10 января 2011

Упрощено далее ...

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 уже имеет это.

0 голосов
/ 10 января 2011

Еще один момент: если ваши ruleIds должны быть константами, вы можете (и должны) объявлять их как значения val, а не как null-arg defs Как и ваши определения, определенные значения могут быть абстрактными в признаке и конкретными в классе.

trait Rule {
        val ruleId : Int
}

class Rule90 extends Rule {
        val ruleId= 90     
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...