Статьи

Scala Tutorial — кортежи, списки, методы для списков и строк

Предисловие

Это второе из запланированной серии учебных пособий по программированию в Scala для начинающих программистов, со специальной ссылкой на мой курс «Осень 2011 года по вычислительной лингвистике». Вы можете увидеть другие учебники здесь в этом блоге; они также перечислены на странице ссылок курса .

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

Кортеж

В предыдущем уроке мы видели, как одно значение может быть присвоено переменной, а затем использовано в различных контекстах. Кортеж является обобщением этого: коллекция из двух, трех, четырех и более значений. Каждое значение может иметь свой собственный тип.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
scala> val twoInts = (3,9)
twoInts: (Int, Int) = (3,9)
  
scala> val twoStrings = ("hello", "world")
twoStrings: (java.lang.String, java.lang.String) = (hello,world)
  
scala> val threeDoubles = (3.14, 11.29, 1.5)
threeDoubles: (Double, Double, Double) = (3.14,11.29,1.5)
  
scala> val intAndString = (7, "lucky number")
intAndString: (Int, java.lang.String) = (7,lucky number)
  
scala> val mixedUp = (1, "hello", 1.16)
mixedUp: (Int, java.lang.String, Double) = (1,hello,1.16)

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

1
2
3
4
5
6
7
8
scala> val (first, second) = twoInts
first: Int = 3
second: Int = 9
  
scala> val (numTimes, thingToSay, price) = mixedUp
numTimes: Int = 1
thingToSay: java.lang.String = hello
price: Double = 1.16

Scala снимает значения и присваивает их каждой отдельной переменной. Это становится очень полезным в контексте функций, которые возвращают кортежи. Например, рассмотрим функцию, которая предоставляет левый и правый края диапазона, когда вы задаете ему среднюю точку диапазона и размер интервала с каждой стороны от средней точки.

1
2
scala> def rangeAround(midpoint: Int, size: Int) = (midpoint - size, midpoint + size)
rangeAround: (midpoint: Int, size: Int)(Int, Int)

Так как rangeAround возвращает Tuple (в частности, пару), мы можем вызвать его и установить переменные для левой и правой сторон непосредственно из вызова функции.

1
2
3
scala> val (left, right) = rangeAround(21, 3)
left: Int = 18
right: Int = 24

Другой способ получить доступ к значениям в кортеже — через индексацию, используя « _n », где n — это индекс элемента, который вы хотите.

1
2
3
4
5
6
scala> print(mixedUp._1)
1
scala> print(mixedUp._2)
hello
scala> print(mixedUp._3)
1.16

Синтаксис этого немного странный, но вы к этому привыкнете.

Кортежи — удивительно полезная функция в языке программирования. По мере продвижения вы увидите несколько примеров их полезности.

Списки

Списки — это коллекции заказанных товаров, которые будут знакомы всем, кто совершал покупки. Очевидно, что кортежи связаны со списками, но они менее универсальны в том смысле, что их необходимо создавать в одном выражении, они имеют ограниченную длину (около 20 или около того) и не поддерживают операции, выполняющие вычисления над всеми их элементами. ,

В Scala мы можем создавать списки Strings, Ints и Doubles (и многое другое).

1
2
3
4
5
6
7
8
scala> val groceryList = List("apples", "milk", "butter")
groceryList: List[java.lang.String] = List(apples, milk, butter)
  
scala> val odds = List(1,3,5,7,9)
odds: List[Int] = List(1, 3, 5, 7, 9)
  
scala> val multinomial = List(.2, .4, .15, .25)
multinomial: List[Double] = List(0.2, 0.4, 0.15, 0.25)

Мы видим, что Scala отвечает, что список был создан вместе со скобками вокруг типа элементов, которые он содержит. Итак, List [Int] читается как «Список целых» и так далее. Это означает, что List является параметризованной структурой данных: это контейнер, который содержит элементы определенных типов. Мы увидим, как знание этого позволяет нам делать разные вещи со списками, параметризованными разными типами.

Мы также можем создавать списки со смесью типов.

1
2
3
4
5
scala> val intsAndDoubles = List(1, 1.5, 2, 2.5)
intsAndDoubles: List[Double] = List(1.0, 1.5, 2.0, 2.5)
  
scala> val today = List("August", 23, 2011)
today: List[Any] = List(August, 23, 2011)

Типы иногда автоматически конвертируются, например, конвертирование Ints в Double для intsAndDoubles , но часто нет очевидного обобщаемого типа. Например, сегодня это List [Any] , что означает, что это List of Anys — и Any — это самый общий тип в Scala, супертип всех типов. Это как сказать: «Да, у меня есть список… ну, вы знаете… вещи».

Списки могут также содержать списки (и списки списков, и списки списков списков …).

1
2
scala> val embedded = List(List(1,2,3), List(10,30,50), List(200,400), List(1000))
embedded: List[List[Int]] = List(List(1, 2, 3), List(10, 30, 50), List(200, 400), List(1000))

Тип внедренного спискаList [List [Int]] , который можно прочитать как «Список списков целых».

Список методов

Хорошо, теперь, когда у нас есть несколько списков, что мы можем с ними сделать? На самом деле, много. Одним из основных свойств списка является его длина, которую вы можете получить, используя « .length » после переменной, которая ссылается на список.

1
2
3
4
5
6
7
8
scala> groceryList.length
res19: Int = 3
  
scala> odds.length
res20: Int = 5
  
scala> embedded.length
res21: Int = 4

Обратите внимание, что длина внедренного составляет 4, что является количеством списков, которые он содержит (не количество элементов в этих списках).

Обозначение variable.method указывает, что вы вызываете функцию, специфичную для типа этой переменной для значения в этой переменной. Хорошо, это был полный рот. Scala является объектно-ориентированным языком, что означает, что каждое значение имеет набор действий, которые идут с ним. Какие действия доступны, зависит от его типа. Итак, выше, мы вызывали метод длины, который доступен спискам для каждого из значений списка, приведенных выше. Вы не поняли этого в предыдущем уроке, но использовали методы при добавлении Ints или объединенных строк — просто Scala позволяет нам обходиться без «.» и паретезы в некоторых случаях. Если мы не уроним их, вот как это выглядит.

1
2
3
4
5
scala> 2.+(3)
res25: Double = 5.0
  
scala> "Portis".+("head")
res26: java.lang.String = Portishead

То, что происходит, заключается в том, что у Ints есть метод с именем « + », а у Strings есть другой метод с именем « + ». Их можно было бы назвать «Билл» и «Боб», но это будет сложнее запомнить, среди прочего. У Ints есть другие методы, такие как « », « * » и « / », которых нет в строках. (Примечание: сейчас я возвращаюсь к пропущенному «.» И паретезам.)

1
2
3
4
5
6
scala> 5-3
res27: Int = 2
  
scala> "walked" - "ed"
<console>:8: error: value - is not a member of java.lang.String
"walked" - "ed"

Scala жалуется, что мы пытались использовать метод « » в строке, поскольку в строке нет такого метода. С другой стороны, у Ints нет метода с именем length , в то время как у Strings есть.

1
2
3
4
5
6
7
scala> 5.length
<console>:8: error: value length is not a member of Int
5.length
^
  
scala> "walked".length
res31: Int = 6

В случае Strings длина возвращает количество символов, а в списках — количество элементов. Метод длины строки можно было бы назвать «numberOfCharacters», но «длину» легче запомнить, и он позволяет нам обрабатывать строки как другие последовательности и думать о них аналогично.

Вернемся к спискам и что мы можем с ними сделать. «Добавление» двух списков является их объединением и обозначается « ++ ».

1
2
3
4
5
scala> val evens = List(2,4,6,8)
evens: List[Int] = List(2, 4, 6, 8)
  
scala> val nums = odds ++ evens
nums: List[Int] = List(1, 3, 5, 7, 9, 2, 4, 6, 8)

Мы можем добавить один элемент в начало списка с помощью « :: ».

1
2
scala> val zeroToNine = 0 :: nums
zeroToNine: List[Int] = List(0, 1, 3, 5, 7, 9, 2, 4, 6, 8)

И отсортируйте список с отсортированным , и переверните его с обратным , и сделайте оба в последовательности.

1
2
3
4
5
6
7
8
scala> zeroToNine.sorted
res42: List[Int] = List(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
  
scala> zeroToNine.reverse
res43: List[Int] = List(8, 6, 4, 2, 9, 7, 5, 3, 1, 0)
  
scala> zeroToNine.sorted.reverse
res44: List[Int] = List(9, 8, 7, 6, 5, 4, 3, 2, 1, 0)

Последняя строка говорит: «возьмите zeroToNine , получите из него новый отсортированный список, а затем переверните этот список». Обратите внимание, что вызов этих функций никогда не изменяет сам zeroToNine ! Это потому, что List является неизменным : вы не можете изменить его, поэтому все эти операции возвращают новые списки. Это свойство списков дает много преимуществ, к которым мы вернемся позже.

Примечание: неизменность отличается от различия val / var . Обычно считается, что переменная val является неизменной, но это не так — она ​​является фиксированной и не может быть переназначена. Все следующие примеры включают неизменяемые списки, но фиксированная переменная — это значение val, а переназначаемая переменная — это переменная.

01
02
03
04
05
06
07
08
09
10
11
12
13
scala> val fixed = List(1,2)
fixed: List[Int] = List(1, 2)
  
scala> fixed = List(3,4)
<console>:8: error: reassignment to val
fixed = List(3,4)
^
  
scala> var reassignable = List(5,6)
reassignable: List[Int] = List(5, 6)
  
scala> reassignable = List(7,8)
reassignable: List[Int] = List(7, 8)

Одна из вещей, которую часто хочется сделать со списком, — это прямой доступ к его элементам. Это делается с помощью индексации в списке, начиная с 0 для первого элемента, 1 для второго элемента и так далее.

1
2
3
4
5
6
7
8
scala> odds
res48: List[Int] = List(1, 3, 5, 7, 9)
  
scala> odds(0)
res49: Int = 1
  
scala> odds(1)
res50: Int = 3

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

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

01
02
03
04
05
06
07
08
09
10
11
scala> zeroToNine(3)
res63: Int = 5
  
scala> zeroToNine(5-2)
res64: Int = 5
  
scala> val index = 3
index: Int = 3
  
scala> zeroToNine(index)
res65: Int = 5

Если мы запрашиваем индекс, равный или превышающий количество элементов в списке, мы получаем ошибку.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
scala> odds(10)
java.lang.IndexOutOfBoundsException: 10
at scala.collection.LinearSeqOptimized$class.apply(LinearSeqOptimized.scala:51)
at scala.collection.immutable.List.apply(List.scala:45)
at .<init>(<console>:9)
at .<clinit>(<console>)
at .<init>(<console>:11)
at .<clinit>(<console>)
at $export(<console>)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at scala.tools.nsc.interpreter.IMain$ReadEvalPrint.call(IMain.scala:592)
at scala.tools.nsc.interpreter.IMain$Request$$anonfun$10.apply(IMain.scala:828)
at scala.tools.nsc.interpreter.Line$$anonfun$1.apply$mcV$sp(Line.scala:43)
at scala.tools.nsc.io.package$$anon$2.run(package.scala:31)
at java.lang.Thread.run(Thread.java:680)

Глядя на все это, вы можете подумать «WTF?» Он называется трассировкой стека и дает подробное описание того, где возникли проблемы в части кода. Для начинающих программистов это, вероятно, выглядит ошеломляющим и пугающим — вы можете пока спокойно смотреть на это, но вскоре понадобится возможность использовать трассировку стека для выявления проблем в вашем коде и их устранения.

Еще один полезный метод — это слайс , который дает вам подсписок от одного индекса до другого, но не включая его.

1
2
3
4
5
scala> zeroToNine
res55: List[Int] = List(0, 1, 3, 5, 7, 9, 2, 4, 6, 8)
  
scala> zeroToNine.slice(2,6)
res56: List[Int] = List(3, 5, 7, 9)

Итак, срез дал нам список с элементами от индекса 2 (третий элемент) до индекса 5 (шестой элемент).

Кратко вернемся к Strings — с ними работают и другие методы List, кроме length .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
scala> val artist = "DJ Shadow"
artist: java.lang.String = DJ Shadow
  
scala> artist(3)
res0: Char = S
  
scala> artist.slice(3,6)
res1: String = Sha
  
scala> artist.reverse
res2: String = wodahS JD
  
scala> artist.sorted
res3: String = " DJSadhow"

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

1
2
3
4
5
scala> odds.sum
res59: Int = 25
  
scala> multinomial.sum
res60: Double = 1.0

Однако, если список содержит нечисловые значения, сумма недействительна.

1
2
3
4
scala> groceryList.sum
<console>:9: error: could not find implicit value for parameter num: Numeric[java.lang.String]
groceryList.sum
^

То, что происходит, — это какое-то очень классное и полезное автоматическое поведение Scala, включающее в себя последствия. Мы вернемся к этому позже, но сейчас вы можете с радостью использовать сумму в списках Ints и Doubles.

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

1
2
3
4
5
6
7
8
scala> groceryList.mkString("\n")
res22: String =
apples
milk
butter
  
scala> odds.mkString(",")
res23: String = 1,3,5,7,9

Хотите знать, если список содержит определенный элемент? Использование содержится в списке.

1
2
3
4
5
scala> groceryList.contains("milk")
res4: Boolean = true
  
scala> groceryList.contains("coffee")
res5: Boolean = false

И теперь мы приходим к Booleans , еще одному из наиболее важных базовых типов. Они играют главную роль в условном исполнении, о котором мы расскажем в следующем уроке.

На самом деле для списков доступно гораздо больше методов, которые вы можете увидеть, перейдя к записи List в Scala API . API означает интерфейс прикладного программирования — иными словами, набор спецификаций для того, что вы можете делать с различными компонентами языка программирования Scala. Я сделаю все возможное, чтобы дать вам методы, которые вам нужны на данный момент, но в конечном итоге вам нужно будет просмотреть записи API для типов Scala, чтобы увидеть, какие методы доступны, что они делают и как их использовать. ,

Некоторые из наиболее важных методов в списках, которые мы не рассмотрели, это map , filter , foldLeft и Reduce . Мы еще вернемся к ним позже, но сейчас есть тизер, который должен дать вам интуитивное представление о том, что они делают.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
scala> val odds = List(1,3,5,7,9)
odds: List[Int] = List(1, 3, 5, 7, 9)
  
scala> odds.map(1+)
res6: List[Int] = List(2, 4, 6, 8, 10)
  
scala> odds.filter(4<)
res7: List[Int] = List(5, 7, 9)
  
scala> odds.foldLeft(10)(_ + _)
res8: Int = 35
  
scala> odds.filter(6>).map(_.toString).reduce(_ + "," + _)
res9: java.lang.String = 1,3,5

Теперь мы работаем. 🙂

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

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