Продолжил работу по созданию учебных материалов по Scala.
Выкладываю черновую версию одной из глав.
Сопоставление по образцу (pattern matching). Начало.
В качестве вариантов перевода слова match с английского языка словарь Lingvo приводит следующие варианты:
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"))
}
}
Продолжение следует…