Статьи

Первые шаги в Scala для начинающих программистов, часть 2

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

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

Кортеж

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

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)

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

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 снимает значения и присваивает их каждой отдельной переменной. Это становится очень полезным в контексте функций, которые возвращают кортежи. Например, рассмотрим функцию, которая предоставляет левый и правый края диапазона, когда вы задаете ему среднюю точку диапазона и размер интервала с каждой стороны от средней точки.

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

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

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

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

scala> print(mixedUp._1)
1
scala> print(mixedUp._2)
hello
scala> print(mixedUp._3)
1.16

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

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

Списки

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

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

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 является параметризованной структурой данных: это контейнер, который содержит элементы определенных типов. Мы увидим, как знание этого позволяет нам делать разные вещи со списками, параметризованными разными типами.

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

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, супертип всех типов. Это как сказать: «Да, у меня есть список… ну, вы знаете… вещи».

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

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 » после переменной, которая ссылается на список.

scala> groceryList.length
res19: Int = 3

scala> odds.length
res20: Int = 5

scala> embedded.length
res21: Int = 4

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

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

scala> (2).+(3)
res25: Int = 5

scala> "Portis".+("head")
res26: java.lang.String = Portishead

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

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 есть.

scala> 5.length
<console>:8: error: value length is not a member of Int
5.length
^

scala> "walked".length
res31: Int = 6

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

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

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)

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

scala> val zeroToNine = 0 :: nums
zeroToNine: List[Int] = List(0, 1, 3, 5, 7, 9, 2, 4, 6, 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 является неизменной, но это не так — она ​​является фиксированной и не может быть переназначена. Следующие примеры включают все неизменяемые списки, но фиксированные переменный является валом , а переназначаемые переменным является варом .

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 для второго элемента и так далее.

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 для доступа к элементу в списке.

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

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

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?» Он называется трассировкой стека и дает подробное описание того, где возникли проблемы в части кода. Для начинающих программистов это, вероятно, выглядит ошеломляющим и пугающим — вы можете пока спокойно смотреть на это, но вскоре понадобится возможность использовать трассировку стека для выявления проблем в вашем коде и их устранения.

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

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 .

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"

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

scala> odds.sum
res59: Int = 25

scala> multinomial.sum
res60: Double = 1.0

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

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 делает именно то, что нам нужно.

scala> groceryList.mkString("\n")
res22: String =
apples
milk
butter

scala> odds.mkString(",")
res23: String = 1,3,5,7,9

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

scala> groceryList.contains("milk")
res4: Boolean = true

scala> groceryList.contains("coffee")
res5: Boolean = false

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

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

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

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

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

 

От http://bcomposes.wordpress.com/2011/08/24/first-steps-in-scala-for-beginning-programmers-part-2/