Статьи

Отрывки Scala 3: списки вместе с картой, плоской картой, сжатием и уменьшением

Вы не можете говорить о Scala, не вдаваясь в детали функций Map, flatMap, zip и Reduce. С помощью этих функций очень легко обрабатывать содержимое списков и работать с объектом Option. Обратите внимание, что на этом сайте вы можете найти еще несколько фрагментов:

  1. Отрывки Scala 1: складные
  2. Фрагменты Scala 2: список символов магии
  3. Отрывки Scala 3: списки вместе с картой, плоской картой, сжатием и уменьшением

Давайте начнем с опции карты. С опцией карты мы применяем функцию к каждому элементу списка и возвращаем это как новый список.

Мы можем использовать это для умножения каждого значения в списке:

1
2
3
4
5
scala> list1
res3: List[Int] = List(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
  
scala> list1.map(x=>x*x)
res4: List[Int] = List(0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100)

Некоторые функции, которые вы применяете к списку, могут привести к появлению элементов Option. Возьмем, к примеру, следующую функцию:

1
2
3
4
5
scala> val evenify = (x:Int) => if (x % 2 == 0) Some(x) else None
evenify: Int => Option[Int] = <function1>
  
scala> list1.map(evenify)
res6: List[Option[Int]] = List(Some(0), None, Some(2), None, Some(4), None, Some(6), None, Some(8), None, Some(10))

Проблема в этом случае в том, что мы часто не заинтересованы в результатах None в нашем списке. Но как мы можем легко получить их? Для этого мы можем использовать flatMap. С flatMap мы можем обрабатывать списки последовательностей. Мы применяем предоставленную функцию к каждому элементу каждой последовательности в списке и возвращаем список, который содержит элементы каждой последовательности исходного списка. Пример гораздо проще понять:

01
02
03
04
05
06
07
08
09
10
11
scala> val list3 = 10 to 20 toList
list3: List[Int] = List(10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20)
  
scala> val list2 = 1 to 10 toList
list2: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
  
scala> val list4 = List(list2, list3)
list4: List[List[Int]] = List(List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), List(10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20))
  
scala> list4.flatMap(x=>x.map(y=>y*2))
res2: List[Int] = List(2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40)

Как видите, у нас есть два списка. В этом списке мы вызываем функцию flatMap. FlatMap обрабатывает каждую из двух записей по отдельности. В каждом из отдельных списков мы вызываем функцию map для дублирования каждой записи. Окончательный результат представляет собой один список, который содержит все записи, сведенные в один список.

Теперь давайте вернемся к функции evenify, которую мы видели ранее, и к списку элементов Option, которые у нас были.

01
02
03
04
05
06
07
08
09
10
11
scala> val list1 = 1 to 10 toList
list1: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
  
scala> list1.map(evenify)
res3: List[Option[Int]] = List(None, Some(2), None, Some(4), None, Some(6), None, Some(8), None, Some(10))
  
scala> val list2 = list1.map(evenify)
list2: List[Option[Int]] = List(None, Some(2), None, Some(4), None, Some(6), None, Some(8), None, Some(10))
  
scala> list2.flatMap(x => x)
res6: List[Int] = List(2, 4, 6, 8, 10)

Легко, верно. И, конечно же, мы можем написать это в одну строку.

1
2
scala> list1.flatMap(x=>evenify(x))
res14: List[Int] = List(2, 4, 6, 8, 10)

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

01
02
03
04
05
06
07
08
09
10
11
scala> val list = "Hello.World".toCharArray
list: Array[Char] = Array(H, e, l, l, o, ., W, o, r, l, d)
  
scala> val list1 = 1 to 20 toList
list1: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20)
  
scala> list.zip(list1)
res30: Array[(Char, Int)] = Array((H,1), (e,2), (l,3), (l,4), (o,5), (.,6), (W,7), (o,8), (r,9), (l,10), (d,11))
  
scala> list1.zip(list)
res31: List[(Int, Char)] = List((1,H), (2,e), (3,l), (4,l), (5,o), (6,.), (7,W), (8,o), (9,r), (10,l), (11,d))

Как только один список достигает конца, функция zip останавливается. У нас также есть функция zipAll, которая также обрабатывает оставшиеся элементы большего списка:

1
2
scala> list.zipAll(list1,'a','1')
res33: Array[(Char, AnyVal)] = Array((H,1), (e,2), (l,3), (l,4), (o,5), (.,6), (W,7), (o,8), (r,9), (l,10), (d,11), (a,12), (a,13), (a,14), (a,15), (a,16), (a,17), (a,18), (a,19), (a,20))

Если список с символами исчерпан, мы поместим букву «а», если список целых чисел исчерпан, мы поместим 1. У нас есть одна последняя функция zip для исследования, и это zipWithIndex. Еще раз, имя в значительной степени подводит итог того, что произойдет. Элемент индекса будет добавлен:

1
2
scala> list.zipWithIndex
res36: Array[(Char, Int)] = Array((H,0), (e,1), (l,2), (l,3), (o,4), (.,5), (W,6), (o,7), (r,8), (l,9), (d,10))

Итак, к последней из функций для изучения: уменьшить. С помощью Reduce мы обрабатываем все элементы в списке и возвращаем одно значение. С помощью ReduLeft и reduRight мы можем задать направление, в котором обрабатываются значения (при уменьшении это не гарантируется):

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
scala> list1
res51: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20)
  
scala> val sum = (x:Int, y:Int) => {println(x,y) ; x + y}
sum: (Int, Int) => Int = <function2>
  
scala> list1.reduce(sum)
(1,2)
(3,3)
(6,4)
(10,5)
(15,6)
(21,7)
(28,8)
(36,9)
(45,10)
(55,11)
(66,12)
(78,13)
(91,14)
(105,15)
(120,16)
(136,17)
(153,18)
(171,19)
(190,20)
res52: Int = 210
  
scala> list1.reduceLeft(sum)
(1,2)
(3,3)
(6,4)
(10,5)
(15,6)
(21,7)
(28,8)
(36,9)
(45,10)
(55,11)
(66,12)
(78,13)
(91,14)
(105,15)
(120,16)
(136,17)
(153,18)
(171,19)
(190,20)
res53: Int = 210
  
scala> list1.reduceRight(sum)
(19,20)
(18,39)
(17,57)
(16,74)
(15,90)
(14,105)
(13,119)
(12,132)
(11,144)
(10,155)
(9,165)
(8,174)
(7,182)
(6,189)
(5,195)
(4,200)
(3,204)
(2,207)
(1,209)
res54: Int = 210

Помимо этих функций у нас также есть ReduOption (а также в вариантах reduLeftOption и reduRightOption). Эти функции будут возвращать Option вместо значения. Это может быть использовано для безопасной обработки пустых списков, что приведет к отсутствию.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
scala> list1.reduceRightOption(sum)
(19,20)
(18,39)
(17,57)
(16,74)
(15,90)
(14,105)
(13,119)
(12,132)
(11,144)
(10,155)
(9,165)
(8,174)
(7,182)
(6,189)
(5,195)
(4,200)
(3,204)
(2,207)
(1,209)
res65: Option[Int] = Some(210)
  
scala> val list3 = List()
list3: List[Nothing] = List()
  
scala> list3.reduceRightOption(sum)
res67: Option[Int] = None

Достаточно для этого фрагмента и для изучения API списков / коллекций на данный момент. В следующем фрагменте мы рассмотрим некоторые вещи из Scalaz, поскольку, хотя библиотека имеет дурную репутацию сложной, она предоставляет некоторые действительно приятные функции.