Scala. Pattern Matching



Продолжил работу по созданию учебных материалов по Scala.
Выкладываю черновую версию одной из глав.

Сопоставление по образцу (pattern matching). Начало.


В качестве вариантов перевода слова match с английского языка словарь Lingvo приводит следующие варианты:

match [mæʧ]

3)
    а) подбирать(под пару, под стать; по цвету, форме)
        to be well / ill matched — быть хорошо / плохо подобранным; хорошо / плохо сочетаться
        ...
    б) подходить, соответствовать (под пару; по цвету, форме)
        dress with a hat to match — платье с удачно подобранной шляпкой
    ...

В языке Scala ключевое слово match также используется для того, чтобы подбирать или находить соответствие:

e match { 
    case p1 => e1 
    // ... 
    case pn => en 
}

Где e, e1, en – некие выражения (expression) , а p1, pn – шаблоны (pattern) по которыми мы пытаемся найти соответствия.

Такая конструкция напоминает обычный switch / case из языков программирования Java или С/C++.

Сравним примеры:

Scala Java C
a match {
  case 0 => print("zero") 

  case 1 => print("one") 

  case _ => print("too much")
}
switch (a) {
   case 0: 
        System.out.print("zero"); 
        break;

   case 1: 
        System.out.print("one"); 
        break;

   default: 
       System.out.print("too much");
       break;
}
switch (a) {
  case 0: printf("zero"); 
  break;

  case 1: printf("zero"); 
  break; 

  default: printf("too much"); 
   break;
}

Tips. В Scala можно указать сразу несколько условий в case

a match {
    case 1 | 2 | 3  => println("один, два, или три")
}

Рассмотрим подробнее наш пример. В нем объект a “сравнивается” с разными шаблонами.
Для a равному 0, будет будет исполнен код print(“zero”).
Для а равному 1, будет выполнено print(“one”).
Для остальных случаев в Scala есть символ-джокер. Он обозначается символом подчёркивания _ . Таким образом case _ сработает в том случае, если другие шаблоны не подошли. Похожее поведение в Java реализуется с помощью слова default .

Tips. Символ-джокер, он же wild-card. По аналогии с карточными играми, например покером, в котором такая “дикая” карта (wild card) может стать другой картой по желанию игрока (например “пятым” тузом).
Простыми примерами таких символов может служить знак * в Unix/Windows скриптах или символ % в SQL-ном LIKE. В Scala wild-card “_” используется не только в case-ах, но и в других конструкциях.

Важной особенностью языка Scala заключается в том, что в отличие от switch/case С или Java, match умеет работать со строками. Более того match можно использовать для сопоставление объектов так называемых case классов, работу с которыми бы подробно рассмотрим в следующей главе.

Tips. Eсли мы попробуем например декомпилировать (javap -c ) указанный выше пример, мы обнаружим что Scala компилятор создаёт не плохой байт-код, аналогичный тому если бы мы писали на Java используя switch/case.

Итак, попробуем использовать полученные знания.
Представим, что у нас есть некоторый набор команд К. Например К = { F, +, – } :
F → пойти прямо
+ → повернуть направо
→ повернуть налево

Напишем программу, которая будет исполнять эти команды:

   // полный текст программы будет приведён в конце главы
   cmd foreach (i =>
        i match {
          case 'F' => {
        	  g.setColor(Color.red)
              val x2 = (x + length*cos(α)).toInt
              val y2 = (y + length*sin(α)).toInt
              g.drawLine(x, y, x2,y2)
              x = x2; y = y2;
            }
          case '+' => α += 0.5*Pi
          case '-' => α -= 0.5*Pi
        }
      )

Здесь cmd – обозначает строку с последовательностью различных команд. Например “F−F++F−F”, будет означать – прямо, налево, прямо, направо, направо, прямо, налево, прямо.
Значение переменных x, y, α будут задавать текущие координаты и направление движения нашей системы.

Используем ещё раз силу match и применив какую-нибудь простую L-system-у, напишем программу которая будет рекурсивно создавать нам красивую последовательность команд.

Например, пусть будет следующее правило преобразований:
F → F+F-F-FF+F+F-F
+ → +
– → –

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

def next(x:String): String = {
    var result = new StringBuilder()
    x.foreach(p=> p match {
          case 'F' => result ++= "F+F-F-FF+F+F-F"
          case i => result +=i
       }
    )
    result.toString
}

Вызывая рекурсивно несколько раз данную функцию и направив полученную цепочку команд для отрисовки изображения, мы можем получить довольно занятные изображения.

Исходный код. В качестве эксперимента можно таким образом нарисовать салфетку серпинского или снежинку коха.

// Использование программного кода только в учебных целях. 
// Автор: Виталий Л. vit@programmisty.com 
import java.awt.Color
import java.awt.image.BufferedImage
import java.io.File
import javax.imageio.ImageIO
import scala.math._

object FractalLab {

  
  def main(s:Array[String]) {
    // Пользуемся только стандартным JavaAPI. Никакие процессингы тут не нужны. Учимся все-таки...  
    val w = 512; val h = w; // квадрат 512x512
    val image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB)
    val g = image.getGraphics()

    var α = 0.0; var x = 0; var y = h/2; // координаты. внимание, α - альфа. сохраняйте исходник в UTF-8.
    
    def draw(cmd:String, length: Int) {
	// Используем match
      cmd foreach (i =>
        i match {
          case 'F' => {
        	  g.setColor(Color.red)
              val x2 = (x + length*cos(α)).toInt
              val y2 = (y + length*sin(α)).toInt
              g.drawLine(x, y, x2,y2)
              x = x2; y = y2;
            }
          case '+' => α += 0.5*Pi
          case '-' => α -= 0.5*Pi
        }
      )
    }
    // вспомогательная функция генератор цепочки команд
    def generate(s:String, level:Int, f:(String)=>String ):String = {
    	if (level > 0)
    	   generate(f(s), level-1, f)  
    	else s
    }

    // функция создающая новые команды, на базе старых
    def next(x:String): String = {
      var result = new StringBuilder()
      // используем match
      x.foreach(p=> p match {
          case 'F' => result ++= "F+F-F-FF+F+F-F"
          case i => result +=i
        }
      )
      result.toString
    }
    // рисуем с шагов 2 пикселя цепочку команда 4 поколения.
    draw(generate("F", 4, next), 2)
    // сохраняем в файл
    ImageIO.write(image, "png", new File("koch.png"))
  }
}

Продолжение следует…

Любое использование либо копирование материалов или подборки материалов сайта, элементов дизайна и оформления допускается лишь с разрешения правообладателя и только со ссылкой на источник: programador.ru

Телеграм канал: @prgrmdr
Почта для связи: vit [at] programmisty.com