Статьи

Scala Tutorial — условное исполнение с блоками if-else и соответствием

Предисловие

Это третья часть руководств для начинающих программистов, попадающих в Scala. Другие посты находятся в этом блоге, и вы можете получить ссылки на эти и другие ресурсы на странице ссылок курса по компьютерной лингвистике, для которого я их создаю. Кроме того, вы можете найти эту и другие серии учебников на странице JCG Java Tutorials .

Conditionals

Переменные приходят и переменные уходят, и они принимают разные значения в зависимости от ввода. Как правило, нам необходимо применять различные способы поведения, обусловленные этими ценностями. Например, давайте смоделируем тендер в баре в Остине, который должен убедиться, что он не дает алкоголь лицам младше 21 года.

1
2
3
4
5
6
7
8
scala> def serveBeer (customerAge: Int) = if (customerAge >= 21) println("beer") else println("water")
serveBeer: (customerAge: Int)Unit
  
scala> serveBeer(23)
beer
  
scala> serveBeer(19)
water

То, что мы сделали здесь, — это стандартное использование условных выражений для создания того или иного действия — в данном случае просто печатается одно или другое сообщение. Выражение в if (…) является логическим значением, истинным или ложным . Вы можете увидеть это, просто выполнив неравенство напрямую:

1
2
scala> 19 >= 21
res7: Boolean = false

И эти выражения могут быть объединены в соответствии со стандартными правилами для соединения и дизъюнкции логических. Конъюнкция обозначается знаком &&, а дизъюнкция — || ,

1
2
3
4
5
scala> 19 >= 21 || 5 > 2
res8: Boolean = true
  
scala> 19 >= 21 && 5 > 2
res9: Boolean = false

Чтобы проверить равенство, используйте == .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
scala> 42 == 42
res10: Boolean = true
  
scala> "the" == "the"
res11: Boolean = true
  
scala> 3.14 == 6.28
res12: Boolean = false
  
scala> 2*3.14 == 6.28
res13: Boolean = true
  
scala> "there" == "the" + "re"
res14: Boolean = true

Оператор равенства == отличается от оператора присваивания = , и вы получите ошибку, если попытаетесь использовать = для тестов на равенство.

01
02
03
04
05
06
07
08
09
10
11
12
scala> 5 = 5
<console>:1: error: ';' expected but '=' found.
5 = 5
^
  
scala> x = 5
<console>:10: error: not found: value x
val synthvar$0 = x
^
<console>:7: error: not found: value x
x = 5
^

Первый пример совершенно плох, потому что мы не можем надеяться присвоить значение такой константе, как 5. В последнем примере ошибка жалуется на отсутствие значения x . Это потому, что это допустимая конструкция, предполагая, что переменная var была определена ранее.

1
2
3
4
5
scala> var x = 0
x: Int = 0
  
scala> x = 5
x: Int = 5

Напомним, что переменным var можно присвоить им новое значение. Тем не менее, на самом деле нет необходимости использовать vars большую часть времени, и есть много преимуществ при использовании vals . Я буду помогать вам думать в этих терминах, пока мы идем вместе. А пока попробуйте игнорировать тот факт, что в языке существуют переменные!

Вернуться к условию. Во-первых, вот несколько операторов сравнения:

х == у (х равен у)
х! = у (х не равно у)
х> у (х больше, чем у)
х <у (х меньше у)
x> = y (x равен y или больше y)
x <= y (x равен y или меньше y)

Эти операторы работают с любым типом, имеющим естественный порядок, включая строки.

1
2
3
4
5
6
7
8
scala> "armadillo" < "bear"
res25: Boolean = true
  
scala> "armadillo" < "Bear"
res26: Boolean = false
  
scala> "Armadillo" < "Bear"
res27: Boolean = true

Понятно, что это не обычный алфавитный порядок, к которому вы привыкли. Вместо этого он основан на кодировке символов ASCII.

Очень красивая и полезная вещь об условных выражениях в Scala заключается в том, что они возвращают значение. Таким образом, следующее является допустимым способом установки значений переменных x и y .

1
2
3
4
5
scala> val x = if (true) 1 else 0
x: Int = 1
  
scala> val y = if (false) 1 else 0
y: Int = 0

Здесь не так впечатляет, но давайте вернемся к бармену, и вместо функции serveBeer, печатающей строку, мы можем заставить его вернуть строку, представляющую напиток, «пиво» ​​в случае 21+ лет и «воду» в противном случае ,

1
2
3
4
5
6
7
8
scala> def serveBeer (customerAge: Int) = if (customerAge >= 21) "beer" else "water"
serveBeer: (customerAge: Int)java.lang.String
  
scala> serveBeer(42)
res21: java.lang.String = beer
  
scala> serveBeer(20)
res22: java.lang.String = water

Обратите внимание, как первая функция serveBeer вернула Unit, а эта возвращает String. Единица означает, что никакое значение не возвращается — как правило, это не рекомендуется по причинам, о которых мы здесь не говорим. Независимо от этого, общая схема условного присваивания, показанная выше, является тем, что вы будете часто использовать.

У условных выражений также может быть больше, чем просто одно и то же . Например, скажем, что бармен просто подает напитки, соответствующие его возрасту, и что 21+ получают пиво, подростки получают газировку, а маленькие дети — сок.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
scala> def serveDrink (customerAge: Int) = {
|     if (customerAge >= 21) "beer"
|     else if (customerAge >= 13) "soda"
|     else "juice"
| }
serveDrink: (customerAge: Int)java.lang.String
  
scala> serveDrink(42)
res35: java.lang.String = beer
  
scala> serveDrink(16)
res36: java.lang.String = soda
  
scala> serveDrink(6)
res37: java.lang.String = juice

И, конечно, булевы выражения в любом из ifs или else if могут быть комплексными соединениями и дизъюнкциями небольших выражений. Давайте рассмотрим пример, ориентированный на вычислительную лингвистику, который может воспользоваться этим и который мы продолжим строить в последующих уроках.

Все (надеюсь) знают, что такое часть речи. (Если нет, то посмотрите «Грамматический рок» на YouTube.) В компьютерной лингвистике мы склонны использовать очень подробные наборы тегов, которые выходят далеко за рамки «существительного», «глагола», «прилагательного» и так далее. Например, набор тегов из банка Penn Treebank использует NN для существительных единственного числа (таблица), NNS для существительных множественного числа (таблицы), NNP для имени существительного единственного числа (John) и NNPS для имени существительного множественного числа (Vikings).

Вот аннотированное предложение с постагами из первого предложения части «Уолл Стрит джорнал» банка «Пенн Трибэнк» в формате слово / постаг.

Индекс / DT / NN / IN / DT 100 / CD крупнейший / JJS Nasdaq / NNP financial / JJ акции / NNS выросли / умеренно VBD / RB как / IN well / RB ./.

Мы вскоре увидим, как обрабатывать их в массовом порядке , но сейчас давайте создадим функцию, которая с помощью условных выражений превращает отдельные теги, такие как «NNP» в «NN» и «JJS» в «JJ». Мы оставим все остальные постаги такими, какие они есть.

Мы начнем с неоптимального решения, а затем уточним его. Первое, что вы можете попробовать — создать регистр для каждого тега полной формы и вывести соответствующий сокращенный тег.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
scala> def shortenPos (tag: String) = {
|     if (tag == "NN") "NN"
|     else if (tag == "NNS") "NN"
|     else if (tag == "NNP") "NN"
|     else if (tag == "NNPS") "NN"
|     else if (tag == "JJ") "JJ"
|     else if (tag == "JJR") "JJ"
|     else if (tag == "JJS") "JJ"
|     else tag
| }
shortenPos: (tag: String)java.lang.String
  
scala> shortenPos("NNP")
res47: java.lang.String = NN
  
scala> shortenPos("JJS")
res48: java.lang.String = JJ

Таким образом, он выполняет свою работу, но существует большая избыточность — в частности, возвращаемое значение одинаково для многих случаев. Мы можем использовать дизъюнкты, чтобы справиться с этим.

1
2
3
4
5
def shortenPos2 (tag: String) = {
  if (tag == "NN" || tag == "NNS" || tag == "NNP" || tag == "NNP") "NN"
  else if (tag == "JJ" || tag == "JJR" || tag == "JJS") "JJ"
  else tag
}

Это логически эквивалентно.

Есть более простой способ сделать это, используя свойства Strings. Здесь метод startWith очень полезен.

1
2
3
4
5
scala> "NNP".startsWith("NN")
res51: Boolean = true
  
scala> "NNP".startsWith("VB")
res52: Boolean = false

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

1
2
3
4
5
def shortenPos3 (tag: String) = {
  if (tag.startsWith("NN")) "NN"
  else if (tag.startsWith("JJ")) "JJ"
  else tag
}

Это позволяет очень легко добавить дополнительное условие, которое сворачивает все теги глагола в «VB». (Оставлено как упражнение.)

Последнее замечание об условных присваиваниях: они могут возвращать все, что вам нравится, поэтому, например, следующие действительны. Например, вот (очень) простой (и очень несовершенный) английский родословный, который возвращает корень и суффикс.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
scala> def splitWord (word: String) = {
|     if (word.endsWith("ing")) (word.slice(0,word.length-3), "ing")
|     else if (word.endsWith("ed")) (word.slice(0,word.length-2), "ed")
|     else if (word.endsWith("er")) (word.slice(0,word.length-2), "er")
|     else if (word.endsWith("s")) (word.slice(0,word.length-1), "s")
|     else (word,"")
| }
splitWord: (word: String)(String, java.lang.String)
  
scala> splitWord("walked")
res10: (String, java.lang.String) = (walk,ed)
  
scala> splitWord("walking")
res11: (String, java.lang.String) = (walk,ing)
  
scala> splitWord("booking")
res12: (String, java.lang.String) = (book,ing)
  
scala> splitWord("baking")
res13: (String, java.lang.String) = (bak,ing)

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

1
2
3
scala> val (stem, suffix) = splitWord("walked")
stem: String = walk
suffix: java.lang.String = ed

согласование

Scala предоставляет еще один очень мощный способ кодирования условного выполнения, называемый сопоставлением . Они имеют много общего с блоками if-else, но имеют некоторые приятные дополнительные функции. Мы вернемся к сокращающему постагу, начиная с полного списка из тегов и того, что нужно делать в каждом случае, как наша первая попытка с if-else.

01
02
03
04
05
06
07
08
09
10
11
12
13
def shortenPosMatch (tag: String) = tag match {
  case "NN" => "NN"
  case "NNS" => "NN"
  case "NNP" => "NN"
  case "NNPS" => "NN"
  case "JJ" => "JJ"
  case "JJR" => "JJ"
  case "JJS" => "JJ"
  case _ => tag
}
  
scala> shortenPosMatch("JJR")
res14: java.lang.String = JJ

Обратите внимание, что последний случай с подчеркиванием «_» — это действие по умолчанию , аналогичное «else» в конце блока if-else.

Сравните это с КРП-ELSE функции shortenPos от до того , что было много повторений в своем определении вида «еще если (тег ==«. Совпадение заявления позволяют делать то же самое, но гораздо более сжато и , возможно, гораздо более ясно. Конечно, мы можем сократить это.

1
2
3
4
5
def shortenPosMatch2 (tag: String) = tag match {
  case "NN" | "NNS" | "NNP" | "NNPS" => "NN"
  case "JJ" | "JJR" | "JJS" => "JJ"
  case _ => tag
}

Который немного более читабелен, чем if-else shortenPosMatch2, определенный ранее.

В дополнение к удобочитаемости операторы сопоставления обеспечивают некоторую логическую защиту. Например, если у вас случайно два перекрывающихся случая, вы получите ошибку.

1
2
3
4
5
6
7
8
scala> def shortenPosMatchOops (tag: String) = tag match {
|   case "NN" | "NNS" | "NNP" | "NNPS" => "NN"
|   case "JJ" | "JJR" | "JJS" => "JJ"
|   case "NN" => "oops"
|   case _ => tag
| }
<console>:10: error: unreachable code
case "NN" => "oops"

Это очевидный пример, но с более сложными вариантами сопоставления он может уберечь вас от ошибок!

Мы не можем использовать метод startWith так же, как и в случае if-else shorttenPosMatch3 . Тем не менее, мы можем очень хорошо использовать регулярные выражения с операторами match, о которых мы поговорим в следующем уроке.

В действительности операторы match лучше всего проявляют то, что они могут соответствовать гораздо большему, чем просто значения простых переменных, таких как Strings и Ints. Одно из совпадений — проверка типов входных данных для функции, которая может принимать супертип многих типов. Напомним, что Any — это супертип всех типов; если у нас есть следующая функция, которая принимает аргумент с любым типом, мы можем использовать сопоставление, чтобы проверить, что это за тип аргумента, и соответственно изменить поведение.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
scala> def multitypeMatch (x: Any) = x match {
|    case i: Int => "an Int: " + i*i
|    case d: Double => "a Double: " + d/2
|    case b: Boolean => "a Boolean: " + !b
|    case s: String => "a String: " + s.length
|    case (p1: String, p2: Int) => "a Tuple[String, Int]: " + p2*p2 + p1.length
|    case (p1: Any, p2: Any) => "a Tuple[Any, Any]: (" + p1 + "," + p2 + ")"
|    case _ => "some other type " + x
| }
multitypeMatch: (x: Any)java.lang.String
  
scala> multitypeMatch(true)
res4: java.lang.String = a Boolean: false
  
scala> multitypeMatch(3)
res5: java.lang.String = an Int: 9
  
scala> multitypeMatch((1,3))
res6: java.lang.String = a Tuple[Any, Any]: (1,3)
  
scala> multitypeMatch(("hi",3))
res7: java.lang.String = a Tuple[String, Int]: 92

Так, например, если это Int, мы можем делать такие вещи, как умножение, если это логическое значение, мы можем его отрицать (с!) И так далее. В операторе case мы предоставляем новую переменную с типом соответствия, а затем после стрелки => мы можем использовать эту переменную безопасным для типа образом. Позже мы увидим, как создавать классы (и, в частности, классы случаев), где этот вид функции на основе сопоставления используется регулярно.

Между тем, вот пример простой функции сложения, которая позволяет вводить String или Int для указания своих аргументов. Например, поведение, которое мы желаем, таково:

01
02
03
04
05
06
07
08
09
10
11
scala> add(1,3)
res4: Int = 4
  
scala> add("one",3)
res5: Int = 4
  
scala> add(1,"three")
res6: Int = 4
  
scala> add("one","three")
res7: Int = 4

Давайте предположим, что мы обрабатываем только прописанные версии от 1 до 5, и что любая строка, которую мы не можем обработать (например, «шестерка» и «aardvark»), считается равной 0. Тогда следующие две функции, использующие совпадения, обрабатывают это.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
def convertToInt (x: String) = x match {
  case "one" => 1
  case "two" => 2
  case "three" => 3
  case "four" => 4
  case "five" => 5
  case _ => 0
}
  
def add (x: Any, y: Any) = (x,y) match {
  case (x: Int, y: Int) => x + y
  case (x: String, y: Int) => convertToInt(x) + y
  case (x: Int, y: String) => x + convertToInt(y)
  case (x: String, y: String) => convertToInt(x) + convertToInt(y)
  case _ => 0
}

Как и блоки if-else, совпадения могут возвращать любой тип, который вам нравится, включая кортежи, списки и многое другое.

Блоки соответствия используются во многих других полезных контекстах, о которых мы поговорим позже. В то же время, стоит также отметить, что сопоставление фактически используется при присвоении переменных. Мы уже видели это с Tuples, но это можно сделать с помощью списков и других типов.

01
02
03
04
05
06
07
08
09
10
11
scala> val (x,y) = (1,2)
x: Int = 1
y: Int = 2
  
scala> val colors = List("blue","red","yellow")
colors: List[java.lang.String] = List(blue, red, yellow)
  
scala> val List(color1, color2, color3) = colors
color1: java.lang.String = blue
color2: java.lang.String = red
color3: java.lang.String = yellow

Это особенно полезно в случае Args Array, который поступает из командной строки при создании скрипта с Scala. Например, рассмотрим программу, которая запускается следующим образом.

1
2
$ scala nextYear.scala John 35
Next year John will be 36 years old.

Вот как мы можем это сделать. (Сохраните следующие две строки как nextYear.scala и попробуйте.)

1
2
val Array(name, age) = args
println("Next year " + name + " will be " + (age.toInt + 1) + " years old.")

Обратите внимание, что мы должны были сделать age.toInt . Это потому, что сам возраст является String, а не Int.
Условное выполнение с блоками if-else и совпадающими блоками является мощной частью встраивания сложных программ в ваши программы, которые вы будете часто видеть и использовать!

Ссылка: Первые шаги в Scala для начинающих программистов, часть 3 от нашего партнера JCG Джейсона Болдриджа в блоге Bcomposes .

Статьи по Теме :