19.03.2024

Lang swift: Swift.org — Welcome to Swift.org

7 причин изучить Swift | GeekBrains

И почему вам стоит начать немедленно.

https://d2xzmw6cctk25h.cloudfront.net/post/1401/og_cover_image/fe014dfcd164d1c9c60b8d3a2243ed0c

Программирование может быть настоящей мукой, особенно если начинать с какого-нибудь архаичного языка. Новичкам лучше взяться за нечто более современное, такое как Python, Ruby или Swift. Последний появился в общем доступе лишь в 2014 году, поэтому многие программисты скептически относятся к его изучению. Но если вы ищете свой путь, оставьте скептицизм в стороне. Вот несколько отличных причин, которые смогут вас убедить.

Mac и iOS

Только за 2016 год AppStore собрал около 20 миллиардов долларов для разработчиков с приложений на платформах Mac и iOS. Поскольку Apple берёт с прибыли 30% в AppStore, рынок можно оценить в 28,5 миллиардов долларов.

Огромные деньги! И ты можешь оторвать свою долю от этого пирога.

Язык Swift используется для создания приложений под Mac и iOS, выступая в качестве возможной замены Objective-C. Последний хоть и активно используется, но морально устарел и не имеет будущего  Изучив Swift, вы сможете создавать приложения сразу для обеих платформ и очень хорошо на этом заработать.

Swift отлично подходит для быстрого развития

Когда команда Apple разрабатывала замену Objective-C, у них было два основных требования:

  • Он должен быть прост в изучении.
  • Должен способствовать ускорению цикла разработки приложений.

В итоге, Swift имеет все атрибуты современного языка программирования и определённо превосходит Objective-C по всем фронтам. Основные особенности:

  • Нет неопределенных или неинициализированных переменных.
  • Нет ошибок с размерностями массивов.
  • Нет ошибок переполнения.
  • Явная обработка значений nil (null).
  • Автоматическое управление памятью.

Таким образом, вы тратите больше времени на реализацию идей и меньше — на беспокойство по поводу возможных ошибок, сбоев и конфликтов вашего кода. Кроме того, язык поборол синтаксическую многословность в Objective-C, что упростило запись и чтение. Результат – в разы меньше времени на написание аналогичного кода в Swift.

Swift производителен

Несмотря на то, что Swift язык высокого уровня, ориентированный на скорое изучение, он по-настоящему быстр. По словам Apple, Swift до 2.6 раз быстрее, чем Objective-C и почти в 8,4 раза быстрее, чем Python 2.7. Конечная цель – сделать язык быстрее, чем C++.

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

  • дженерики;
  • замыкания;
  • кортежи;
  • множественные возвраты;
  • итераторы;
  • встроенные шаблоны ФП.

И ещё много другого.

Swift безопасен

Введение многих из перечисленных возможностей, а также усовершенствование синтаксиса делает Swift безопаснее, нежели Objective-C.

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

Swift бесплатный и открытый

Через год после появления Swift Apple сделал его  языком с открытым исходным кодом. Хотя это не уникальное явление для современного мира, для «яблочной» компании подобная щедрость – редкость. Как правило, Apple проталкивает проприетарные технологии, чтобы выделить собственную уникальность. Но шаг со Swift стал оправданным и плодотворным.

Как и в случае с любым другим языком с открытым исходным кодом, Swift – полностью в руках сообщества. Пользователи могут предлагать пути исправления ошибок и улучшать функции, помогают переносить приложения за пределы Mac и iOS. В конце концов, пользователи – главная движущая сила языка

Стремительный рост и высокий спрос

Согласно отчету GitHub Octoverse 2017, Swift является 13-м по популярности среди языков в проектах с открытым исходным кодом.

Ресурс TNW в 2016 году сообщил, что спрос на сотрудников Swift вырос на 600 процентов. К концу года Upwork сообщила, что Swift стал вторым самым быстрорастущим навыком на внештатном рынке труда. И в опросе Stack Overflow 2017 Swift стал четвертым самым любимым языком среди активных разработчиков.

Glassdoor сообщает о среднем базовом окладе для iOS Developer в размере 107 тыс. долларов.

Разработка приложений сегодня – одна из самых «горячих» профессий на бирже. Выбрав Swift в качестве фундамента карьеры, вы точно не пожалеете.

Swift – будущее Apple

У Apple нет причин в ближайшее десятилетие заменять Swift другим языком. Добавьте сюда 4-летний прогресс, как с точки зрения развития, так и популярности, неизменно растущие продажи «яблочных» устройств и расширение линейки. Основываясь на этих фактах, можно с уверенностью говорить, что потребность в Swift-разработчиках будет расти.

Если вы хотите работать с Apple, хотите быть частью их сумасшедших финансовых отчётов – вам нужен Swift. Самое время начать обучение. Вы знаете с чего начать.

Как я изучил Swift и выпустил своё приложение — Личный опыт на vc.ru

Хочу поделиться опытом изучения языка программирования Swift с нуля.

Дисклеймер: я не претендую на звание супермегакрутого кодера, просьба токсичных сеньоров обходить статью стороной, спасибо.

В заголовке я написал что «изучил» Swift, это, конечно же, не так. Процентов на 3–5%, думаю, да, но даже этого мне хватило, чтобы написать и выпустить своё приложение, которое даже приносит какие-то деньги.

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

Мне всегда было интересно научиться писать приложения для iPhone самому, чтобы сделать именно так, как я хочу, и понимать, насколько сложно и долго это делается. Это очень важно, если ты работаешь в этой сфере. И в целом навык программирования полезен для мозга, логики и прочего.

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

Когда ты делаешь сам, ты доделываешь каждый элемент до того состояния, пока ты сам не будешь кайфовать, и неважно, сколько это займёт времени.

Сейчас есть множество крутейших курсов для изучения чего угодно (в том числе Swift). Например, за смешную сумму в $11 на сайте Udemy можно найти всё, что хочешь, правда, нужно знать (хотя бы понимать) английский. Обычно у топовых курсов есть субтитры, тоже на английском.

Также есть неплохие курсы и на русском, платные и бесплатные. Есть переводы стэнфордских курсов. Если что, могу в комментариях скинуть ссылки на всё, что мне сейчас кажется крутым, о чём я жалею, что не нашёл их изначально.

Я же решил найти себе программиста уровня мидла или чуть выше, чтобы с ним заниматься по Skype, конечно, за деньги. Плюсы этого подхода в том, что обучение проходит в разы быстрее (по крайней мере для меня). Главное, что в любой момент, когда тебе что-то интересно или непонятно, ты мог спросить учителя об этом и получить ответ.

Моей целью не было получить работу где-то, я просто хотел делать приложения для себя, потому что нужные мне приложения я часто не находил в App Store, вернее, приложения, которые мне бы нравились. Всегда хотелось что-то сделать удобнее или переделать, а лишний мусор выкинуть. Кстати, для приложения vc.ru тоже есть пара комментариев, подскажите, куда их лучше адресовать.

Своего первого учителя искать долго не пришлось, им стал программист, который работал над моим очередным приложением. Как я сейчас понимаю, уровень его

Базовые операторы — SwiftBook

Оператор — это специальный символ или выражение для проверки, изменения или сложения величин. Например, оператор сложения (+) суммирует два числа let i = 1 + 2, а логический оператор И && объединяет два логических значения if enteredDoorCode && passedRetinaScan.

Язык Swift поддерживает большинство стандартных операторов C, а также ряд возможностей для устранения типичных ошибок в коде. Оператор присваивания (=) не возвращает значение, что позволяет избежать путаницы с оператором проверки на равенство (==). Арифметические операторы (+, -, *, /, % и т. д.) могут обнаруживать и предотвращать переполнение типа, чтобы числовой переменной нельзя было присвоить слишком большое или слишком маленькое значение. Контроль переполнения типа включается в Swift специальными операторами, которые описаны в разделе Операторы переполнения.

В отличие от C язык Swift позволяет делить с остатком (%) числа с плавающей точкой. Также в Swift имеются два сокращенных оператора интервала (a..<b и a…b), которых нет в C.

В этой главе описываются стандартные операторы Swift. Более сложные операторы Swift рассмотрены в главе Продвинутые операторы, где описано, как объявить пользовательские операторы и реализовать стандартные операторы для пользовательских типов.

Операторы делятся на унарные, бинарные и тернарные:

  • Унарные операторы применяются к одной величине (например, -a). Унарные префиксные операторы ставятся непосредственно перед величиной (например, !b), а унарные постфиксные операторы — сразу за ней (например, c!).
  • Бинарные операторы применяются к двум величинам (например, 2 + 3) и являются инфиксными, так как ставятся между этими величинами.
  • Тернарные операторы применяются к трем величинам. Как и в языке C, в Swift есть только один такой оператор, а именно — тернарный условный оператор (a ? b : c).

Величины, к которым применяются операторы, называются операндами. В выражении 1 + 2 символ + является бинарным оператором, а его операндами служат 1 и 2.

Оператор присваивания (a = b) инициализирует или изменяет значение переменной a на значение b:

let b = 10
var a = 5
a = b
// теперь a равно 10

Если левая часть выражения является кортежем с несколькими значениями, его элементам можно присвоить сразу несколько констант или переменных:

let (x, y) = (1, 2)
// x равно 1, а y равно 2

В отличие от C и Objective-C оператор присваивания в Swift не может возвращать значение. К примеру, следующее выражение недопустимо:

if x = y {
 // это неверно, так как x = y не возвращает никакого значения
}

Эта особенность не позволяет разработчику спутать оператор присваивания (=) с оператором проверки на равенство (==). Благодаря тому, что выражения типа if x = y некорректны, подобные ошибки при программировании на Swift не произойдут.

Язык Swift поддерживает четыре стандартных арифметических оператора для всех числовых типов:

  • сложение (+)
  • вычитание (-)
  • умножение (*)
  • деление (/)
1 + 2 // равно 3
5 - 3 // равно 2
2 * 3 // равно 6
10.0 / 2.5 // равно 4.0

В отличие от C и Objective-C арифметические операторы Swift по умолчанию не допускают переполнения типа. Контроль переполнения типа включается в Swift специальными операторами (например, a &+ b). Подробнее см. в главе Операторы переполнения.

Оператор сложения служит также для конкатенации, или же склейки, строковых значений (тип String):

"hello, " + "world"  // равно "hello, world"

Оператор целочисленного деления

Оператор целочисленного деления (a % b) показывает, какое количество b помещается внутри a, и возвращает остаток деления a на b.

Заметка

Оператор целочисленного деления (%) в некоторых языках называется оператором деления по модулю. Однако учитывая его действие над отрицательными числами в Swift, этот оператор, строго говоря, выполняет деление с остатком, а не по модулю.

Оператор целочисленного деления работает следующим образом. Для вычисления выражения 9 % 4 сначала определяется, сколько четверок содержится в девятке:

В одной девятке содержатся две четверки, а остатком будет 1 (выделено оранжевым цветом).

На языке Swift это записывается так:

9 % 4    // равно 1

Чтобы получить результат деления a % b, оператор % вычисляет следующее выражение и возвращает остаток:

a = (b × множитель) + остаток

где множитель показывает, сколько раз целых b содержится в a.

Подставляя в это выражение 9 и 4, получим:

9 = (4 × 2) + 1

Точно так же рассчитывается остаток, когда a отрицательно:

-9 % 4   // равно -1

Подставляя в наше выражение -9 и 4, получим:

-9 = (4 × -2) + -1

причем остаток будет равен -1.

Если b отрицательно, его знак отбрасывается. Это означает, что выражения a % b и a % -b всегда будут давать одинаковый результат.

Оператор унарного минуса

Для изменения знака числового значения служит префиксный минус -, который называется оператором унарного минуса:

let three = 3
let minusThree = -three // minusThree равно -3
let plusThree = -minusThree // plusThree равно 3, т. е. "минус минус три"

Оператор унарного минуса (-) ставится непосредственно перед значением, без пробела.

Оператор унарного плюса

Оператор унарного плюса (+) просто возвращает исходное значение без каких-либо изменений:

let minusSix = -6
let alsoMinusSix = +minusSix // alsoMinusSix равно -6

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

Как и в языке C, в Swift имеются составные операторы присваивания, совмещающие простое присваивание (=) с другой операцией. Одним из примеров может служить оператор присваивания со сложением (+=):

var a = 1
a += 2
// теперь a равно 3

Выражение a += 2 является краткой формой записи a = a + 2. Таким образом, один и тот же оператор выполняет одновременно операцию сложения и присваивания.

Заметка

Составные операторы присваивания не возвращают значение. К примеру, нельзя написать так: let b = a += 2.

Для получения полного списка операторов присваивания, предусмотренных стандартной библиотекой Swift, см. Operator Declarations.

Язык Swift поддерживает все стандартные операторы сравнения из C:

  • равно (a == b)
  • не равно (a != b)
  • больше (a > b)
  • меньше (a < b)
  • больше или равно (a >= b)
  • меньше или равно (a <= b)
Заметка

В языке Swift есть также два оператора проверки на идентичность/тождественность (=== и !==), определяющие, ссылаются ли два указателя на один и тот же экземпляр объекта. Дополнительную информацию см. в главе Классы и структуры.

Каждый оператор сравнения возвращает значение типа Bool, указывающее, является ли выражение истинным:

1 == 1 // истина, так как 1 равно 1
2 != 1 // истина, так как 2 не равно 1
2 > 1 // истина, так как 2 больше чем 1
1 < 2 // истина, так как 1 меньше 2
1 >= 1 // истина, так как 1 больше либо равно 1
2 <= 1 // ложь, так как 2 не меньше либо равно 1

Операторы сравнения часто используются в условных выражениях, включая инструкцию if:

let name = "world"
if name == "world" {
 print("hello, world")
} else {
 print("Мне жаль, \(name), но я тебя не узнаю")
}
// напечатает "hello, world", так как name очевидно равно "world"

Подробнее об инструкции if см. в главе «Управление потоком».

Вы так же можете сравнивать кортежи, которые имеют одно и то же количество значений, которые, в свою очередь, должны быть сравниваемыми, что означает, что кортеж типа (Int, String) может быть сравнен с кортежем такого же типа.
Кортежи сравниваются слева направо, по одному значению за раз до тех пор, пока операция сравнения не найдет отличия между значениями. Если все значения кортежей попарно равны, то и кортежи так же считаются равными. Например:

(1, "zebra") < (2, "apple")   // true, потому что 1 меньше 2, "zebra" и "apple" не сравниваются
(3, "apple") < (3, "bird")    // true , потому что 3 равно 3, а "apple" меньше чем "bird"
(4, "dog") == (4, "dog")      // true , потому что 4 равно 4 и "dog" равен "dog"

В примере выше, в первой строке вы можете видеть сравнение слева на право. Так как 1 меньше 2, то (1, “zebra”) меньше (2, “apple”), несмотря на остальные значения кортежа, потому что это неравенство было определено первыми членами. Не важно, что “zebra” не меньше, чем “apple”, потому что сравнение уже определено первыми элементами кортежей. Однако, когда первые элементы кортежей одинаковые, то сравниваются вторые элементы и так далее.

Кортежи могут сравниваться, только в том случае, если оператор сравнения можно применить ко всем членам кортежей соответственно. Например, как показано в коде ниже, вы можете сравнить два кортежа типа (String, Int) потому что и String, и Int могут сравниться оператором <. И наоборот, кортеж типа (String, Bool) сравниваться не может, так как к значениям типа Bool операторы сравнения не применяются.

("blue", -1) < ("purple", 1) // OK, расценивается как true
("blue", false) < ("purple", true) // Ошибка так как < не может применяться к значениям типа Bool 
Заметка

Стандартная библиотека Swift включает в себя операторы сравнения кортежей, которые имеют менее семи значений. Если вам нужны операторы, которые могут сравнивать кортежи с более, чем шестью элементами, то вам нужно реализовать их самостоятельно.

Тернарный условный оператор — это специальный оператор из трех частей, имеющий следующий синтаксис: выражение ? действие1 : действие2. Он выполняет одно из двух действий в зависимости от того, является ли выражение true или false. Если выражение равно true, оператор выполняет действие1 и возвращает его результат; в противном случае оператор выполняет действие2 и возвращает его результат.

Тернарный условный оператор является краткой записью следующего кода:

if выражение {
 действие1
} else {
 действие2
}

Ниже приведен пример расчета высоты строки в таблице. Если у строки есть заголовок, то она должна быть выше своего содержимого на 50 точек, а если заголовка нет, то на 20 точек:

let contentHeight = 40
let hasHeader = true
let rowHeight = contentHeight + (hasHeader ? 50 : 20)
// rowHeight равно 90

В развернутом виде этот код можно записать так:

let contentHeight = 40
let hasHeader = true
var rowHeight = contentHeight
if hasHeader {
    rowHeight = rowHeight + 50
} else {
    rowHeight = rowHeight + 20
}
// rowHeight равно 90

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

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

Оператор объединения по nil (a ?? b) извлекает опционал a, если он содержит значение, или возвращает значение по умолчанию b, если a равно nil. Выражение a может быть только опционалом. Выражение b должно быть такого же типа, что и значение внутри a.

Оператор объединения по nil является краткой записью следующего кода:

a != nil ? a! : b

В вышеприведенном коде тернарный условный оператор и принудительное извлечение (a!) используются для обращения к значению внутри a, если a не равно nil, или для возвращения b в противном случае. Оператор объединения по nil — это более элегантный, короткий и понятный способ одновременно проверить условие и извлечь значение.

Заметка

Если a не равно nil, выражение b не анализируется. Такой подход называется краткой проверкой условия (short-circuit evaluation).

В следующем примере оператор объединения по nil выбирает между стандартным значением цвета и пользовательским:

let defaultColorName = "red"
var userDefinedColorName: String? // по умолчанию равно nil
        
var colorNameToUse = userDefinedColorName ?? defaultColorName
// userDefinedColorName равен nil, поэтому colorNameToUse получит значение по умолчанию — "red"

Переменная userDefinedColorName объявлена как строковый (String) опционал и по умолчанию равна nil. Так как userDefinedColorName является опционалом, ее значение можно анализировать посредством оператора объединения по nil. В вышеприведенном примере этот оператор задает начальное значение для строковой (String) переменной colorNameToUse. Так как userDefinedColorName равно nil, выражение userDefinedColorName ?? defaultColorName возвратит значение defaultColorName, т. е. «red».

Если переменной userDefinedColorName присвоить отличное от nil значение и снова передать ее в оператор объединения по nil, вместо значения по умолчанию будет использовано значение внутри userDefinedColorName:

userDefinedColorName = "green"
colorNameToUse = userDefinedColorName ?? defaultColorName
// userDefinedColorName не равно nil, поэтому colorNameToUse получит значение "green"

В языке Swift есть два оператора диапазона, которые в короткой форме задают диапазон значений.

Оператор замкнутого диапазона

Оператор замкнутого диапазона (a…b) задает диапазон от a до b, включая сами a и b. При этом значение a не должно превышать b.

Оператор замкнутого диапазона удобно использовать при последовательном переборе значений из некоторого диапазона, как, например, в цикле for-in:

for index in 1...5 {
 print("\(index) умножить на 5 будет \(index * 5)")
}
// 1 умножить на 5 будет 5
// 2 умножить на 5 будет 10
// 3 умножить на 5 будет 15
// 4 умножить на 5 будет 20
// 5 умножить на 5 будет 25

Подробнее о циклах for-in см. в главе Управление потоком.

Оператор полузамкнутого диапазона

Оператор полузамкнутого диапазона (a..<b) задает диапазон от a до b, исключая значение b. Такой диапазон называется полузамкнутым, потому что он включает первое значение, но исключает последнее. Так же, как и для оператора замкнутого диапазона, значение a не должно превышать b. Если значение a равно значению b, то итоговый диапазон будет пустым.

Операторы полузамкнутого диапазона особенно удобны при работе с массивами и другими последовательностями, пронумерованными с нуля, когда нужно перебрать элементы от первого до последнего:

let names = ["Anna", "Alex", "Brian", "Jack"]
let count = names.count
for i in 0..<count {
 print("Person \(i + 1) будет \(names[i])")
}
// Person 1 будет Anna
// Person 2 будет Alex
// Person 3 будет Brian
// Person 4 будет Jack

Хотя в массиве четыре элемента, диапазон 0..<count доходит только до 3 (т. е. до номера последнего элемента в массиве), так как это оператор полузамкнутого диапазона. Подробнее о массивах см. в главе Массивы.

Односторонние диапазоны

Операторы замкнутого диапазона имеют себе альтернативу — диапазон, который продолжается насколько возможно, но только в одну сторону, например, диапазон, который включает все элементы массива, начиная от 2 и до последнего индекса. В этих случаях вы можете пропустить значение с одной стороны оператора диапазона. Этот тип диапазона называется односторонним, потому что оператор имеет значение только с одной стороны. Например:

for name in names[2...] {
    print(name)
}
// Brian
// Jack
 
for name in names[...2] {
    print(name)
}
// Anna
// Alex
// Brian

Оператор полузамкнутого диапазона так же имеет одностороннюю форму, которая записывается только с одним конечным значением. Точно так же как и в случае, когда вы включаете значение в обе стороны, конечное значение не является частью самого диапазона. Например:

for name in names[..<2] {
    print(name)
}
// Anna
// Alex

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

let range = ...5
range.contains(7)   // false
range.contains(4)   // true
range.contains(-1)  // true

Логические операторы изменяют или комбинируют логические значения типа Boolean (булево) — true и false. Язык Swift, как и другие C-подобные языки, поддерживает три стандартных логических оператора:

  • логическое НЕ (!a)
  • логическое И (a && b)
  • логическое ИЛИ (a || b)

Оператор логического НЕ

Оператор логического НЕ (!a) инвертирует булево значение — true меняется на false, а false становится true.

Оператор логического НЕ является префиксным и ставится непосредственно перед значением, без пробела. Как видно из следующего примера, его можно воспринимать как «не a»:

let allowedEntry = false
if !allowedEntry {
 print("ACCESS DENIED")
}
// Выведет "ACCESS DENIED"

Конструкция if !allowedEntry означает «если не allowedEntry». Идущая за ней строка будет выполнена, только если «не allowedEntry» является истиной, т. е. если allowedEntry равно false.

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

Оператор логического И

Оператор логического И (a && b) дает на выходе true тогда и только тогда, когда оба его операнда также равны true.

Если хотя бы один из них равен false, результатом всего выражения тоже будет false. На самом деле, если первое значение равно false, то второе даже не будет анализироваться, так как оно все равно не изменит общий результат на true. Такой подход называется краткой проверкой условия (short-circuit evaluation).

В следующем примере проверяются два значения типа Bool, и если они оба равны true, программа разрешает доступ:

let enteredDoorCode = true
let passedRetinaScan = false
if enteredDoorCode && passedRetinaScan {
 print("Welcome!")
} else {
 print("ACCESS DENIED")
}
// Выведет "ACCESS DENIED"

Оператор логического ИЛИ

Оператор логического ИЛИ (a || b) является инфиксным и записывается в виде двух вертикальных палочек без пробела. С его помощью можно создавать логические выражения, которые будут давать true, если хотя бы один из операндов равен true.

Как и описанный выше оператор логического И, оператор логического ИЛИ использует краткую проверку условия. Если левая часть выражения с логическим ИЛИ равна true, то правая не анализируется, так как ее значение не повлияет на общий результат.

В приведенном ниже примере первое значение типа Bool (hasDoorKey) равно false, а второе (knowsOverridePassword) равно true. Поскольку одно из значений равно true, результат всего выражения тоже становится true и доступ разрешается:

let hasDoorKey = false
let knowsOverridePassword = true
if hasDoorKey || knowsOverridePassword {
 print("Welcome!")
} else {
 print("ACCESS DENIED")
}
// Выведет "Welcome!"

Комбинирование логических операторов

Можно также составлять и более сложные выражения из нескольких логических операторов:

if enteredDoorCode && passedRetinaScan || hasDoorKey || knowsOverridePassword {
 print("Welcome!")
} else {
 print("ACCESS DENIED")
}
// Выведет "Welcome!"

В этом примере с помощью нескольких операторов && и || составляется более длинное и сложное выражение. Однако операторы && и || по-прежнему применяются только к двум величинам, поэтому все выражение можно разбить на три простых условия. Алгоритм работы будет следующим:

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

Исходя из значений enteredDoorCode, passedRetinaScan и hasDoorKey первые два подусловия дают false. Однако был введен код экстренного доступа, поэтому все составное выражение по-прежнему равно true.

Заметка

Логические операторы Swift && и || являются лево-ассоциированными, что означает, что составные выражения с логическими операторами оценивают в первую очередь выражения слева направо.

Явное указание круглых скобок

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

if (enteredDoorCode && passedRetinaScan) || hasDoorKey || knowsOverridePassword {
 print("Welcome!")
} else {
 print("ACCESS DENIED")
}
// Выведет "Welcome!"

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

Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.

Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.

Функциональное программирование в Swift — SwiftBook

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

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

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

Пришло время повеселиться… в функциональном программировании!

Простая фильтрация массива

Вы начнете с очень простого: с математики. Ваша первая задача состоит в том, чтобы создать простой Swift скрипт, находящий все четные числа от 1 до 10 (включительно). Довольно простая задача и отличное введение в функциональное программирование!

Фильтрация старым способом

Создайте новый файл в плейграунде Swift и сохранить его. Замените содержимое созданного файла следующим:


var evens = [Int]()
for i in 1...10 {
  if i % 2 == 0 {
    evens.append(i)
  }
}
print(evens)

Это приведет к желаемому результату:


[2, 4, 6, 8, 10]

(Если вы не видите консоль, то вам нужно, чтобы показался Assistant Editor, вы можете его вызвать через View/Assistant Editor/Show Assistant Editor.)

Этот маленький скрипт очень прост, ключевые моменты алгоритма заключаются в следующем:

  1. Вы создаете пустой (и изменяемый) массив.
  2. Цикл for перебирает числа от 1 до 10 (помните, «» включительно!).
  3. Если условие (о том, что число должно быть четным) удовлетворяется, то вы добавляете его в массив.

Приведенный выше код является императивным по своей сути. Инструкции указывают компьютеру, как располагать четные числа через четкие инструкции, использующие основные структуры контроля, в данном случае if и for-in.

Код работает просто отлично, но есть важное «но» — тестирование на то, является ли число четным, закопано внутри цикла. Существует также глубокая связь: желаемое действие добавления числа в массив находится внутри условия. Если вы хотите напечатать каждое четное число где-то еще в вашем приложении, то кроме копи-пейста лучшего варианта у вас и не будет.

Функциональная фильтрация

Добавьте следующее в конец вашего плейграунда:


func isEven(number: Int) -> Bool {
  return number % 2 == 0
}
evens = Array(1...10).filter(isEven)
print(evens)

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


[2, 4, 6, 8, 10]

Давайте рассмотрим подробнее функциональную версию. Она состоит из двух частей:

  1. Раздел Array(1…10) представляет собой простой и удобный способ создания массива и содержит числа от 1 до 10. Оператор диапазона 1…10 создает диапазон, который вы передаете инициализатору массива.
  2. Выражение filter — это то место, где происходит магия функционального программирования. Этот метод, раскрытый через Array, создает и возвращает новый массив, содержащий только элементы, для которых данная функция возвращает значение true. В этом примере isEven подается на filter.

Вы передаете функцию isEven в качестве параметра для фильтрации, но запомните, что функции теперь стали называться замыканиями. Попробуйте добавить следующую, более краткую версию в плейграунд:


evens = Array(1...10).filter { (number) in number % 2 == 0 }
print(evens)

Убедитесь, что результаты всех трех вариантов одинаковые. Приведенный выше пример показывает, что компилятор выводит тип параметра числа и возвращает типы замыкания из контекста использования.

Если вы хотите, чтобы ваш код был еще более кратким, то попробуйте следующее:


evens = Array(1...10).filter { $0 % 2 == 0 }
print(evens)

Код выше использует сокращенное обозначение аргумента, неявные возвращения …, вывод типа … И это работает!

Заметка

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

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

  1. Higher-order functions (Функции высшего порядка): Это функции, которые вы передаете в качестве аргументов другим функциям. В этом простом примере, фильтр требует передачи функции высшего порядка.
  2. First-class functions (Функции первого класса): Вы можете рассматривать функции, как любую другую переменную. Вы можете присвоить их переменным и передать их в качестве аргументов другим функциям.
  3. Closures (Замыкания): Это фактические анонимные функции, которые вы создаете на месте.

Вы могли заметить, что в Objective-C тоже есть демонстрация некоторых из этих функций за счет использования блоков. Swift, однако, идет дальше, чем Objective-C в развитии функционального программирования, соединяя более лаконичный синтаксис и встроенные функциональные возможности, такие как filter.

Магия фильтров

Массивы в Swift имеют ряд функциональных методов, таких как map, join и reduce. Что, собственно, происходит за кулисами этих методов?

Пришло время прикоснуться к магии фильтров и добавить собственную реализацию.

В этом же плейграунде, добавьте следующую функцию:


func myFilter<T>(source: [T], predicate:(T) -> Bool) -> [T] {
  var result = [T]()
  for i in source {
    if predicate(i) {
      result.append(i)
    }
  }
  return result
}

Выше вы видите общую (generic) функцию, которая принимает в качестве своих входных параметров исходный массив типа  T, и предекат, или функцию, которая принимает экземпляр типа T и возвращает Bool.

Реализация MyFilter выглядит практически также, как императивная версия, которую вы добавили в начале. Основное отличие заключается в том, что у вас есть условие, которое проверяется как функция, а не просто жестко hard-code (определять в коде конкретные значения переменных вместо того, чтобы получать их из внешних источников).

Поработаем с добавленной реализацией фильтра, добавив следующий код:


evens = myFilter(Array(1...10)) { $0 % 2 == 0 }
print(evens)

Опять результат такой же!

Задачка!

Вышеуказанная функция фильтра является глобальной, а вы сможете сделать ее методом массива?

Подсказка №1

Вы можете добавить myFilter в массив с помощью расширения класса.

Подсказка №2

Вы можете расширить Array, но не Array<T>. Это означает, что, когда вы перебираете элементы массива через себя, вы должны выполнить приведение.

Функция Reduce

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

Создайте новый плейграунд и ждите новое задание!

Своя функция Reduce

Ваша задача в этом разделе лишь немного сложнее. Возьмите четные числа от 1 до 10, и вычислите их сумму. Для этого вам понадобится функция reduce, которая принимает несколько входных значений и генерирует одно на выходе.

Я уверен, что вы с успехом справитесь и сами, но в любом случае ответ ниже! Добавьте следующую строку в ваш плейграунд:


var evens = [Int]()
for i in 1...10 {
  if i % 2 == 0 {
    evens.append(i)
  }
}
 
var evenSum = 0
for i in evens {
  evenSum += i
}
 
print(evenSum)

В Assistant Editor будет следующий результат:


30

Императивный код, указанный выше, написан в том же ключе, что и код в предыдущем примере, с добавлением дополнительного цикла for-in.

Давайте посмотрим, как выглядит функциональный эквивалент!

Функциональная Reduce

Добавьте следующую строку в ваш плейграунд:


evenSum = Array(1...10)
    .filter { (number) in number % 2 == 0 }
    .reduce(0) { (total, number) in total + number }
 
print(evenSum)

Вы увидите точно такой же результат:


30

Предыдущий раздел охватывал создание массива и использование filter. Конечным результатом этих двух операций является массив с пятью числами [2, 4, 6, 8, 10]. Новым шагом в коде выше стало использование reduce.

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

Для того чтобы понять, как работает метод reduce, нужно посмотреть на его описание:


func reduce<U>(initial: U, combine: (U, T) -> U) -> U

Первый параметр — это начальное значение типа U. В вашем текущем коде, начальное значение равно 0, и имеет тип Int (следовательно, U это в данном случае Int). Второй аргумент — это функция combine, и она выполняется один раз для каждого элемента массива.

combine принимает два аргумента: первый, типа U, является результатом предыдущего вызова combine, второй — значением элемента массива, с которым он объединен. Результат, возвращаемый reduce,  это значение, возвращаемое последним вызовом combine.

Давайте разберем все шаг за шагом.

В вашем коде, первая итерация reduce приводит к следующему:

Сначала total имеет значение 0, а первый элемент входного массива равен 2. Если мы просуммируем эти значения, то на выходе (result) получится 2.

Вторая итерация показана ниже:

Во второй итерации, входное значение равно значению предыдущей итерации и следующего элемента из входного массива. Объединение их приводит к 2 + 4 = 6.

Продолжаем проделывать тоже самое для всех элементов массива, что приводит к следующим вводам и выводам:

Обозначенное звездочкой число в правом нижнем углу — общий результат.

Это довольно простой пример, на практике же вы можете выполнять любые виды интересных и сильных преобразований с reduce. Ниже приведены несколько простых примеров.

Добавьте следующую строку в ваш плейграунд:


let maxNumber = Array(1...10)
            .reduce(0) { (total, number) in max(total, number) }
print(maxNumber)

Этот код использует reduce, чтобы найти максимальное значение в массиве целых чисел. В этом случае результат весьма очевиден! Помните, что здесь total на самом деле просто максимальный результат max последней итерации reduce.

Если вы пытаетесь изо всех сил понять, как это работает, почему бы не создать еще одну таблицу, где вы вычислите вводы и выводы combine (т.е. замыкания) для каждой итерации?

Примеры, которые вы видели, все уменьшают массивы целых чисел до одиночных целочисленных значений. Конечно, у reduce есть два типа параметров, U и T и они могут быть разными, и, конечно, не должны быть интеджерами. Это означает, что вы можете уменьшить массив одного типа до совершенно другого типа.

Добавьте следующую строку в ваш плейграунд:


let numbers = Array(1...10)
  	.reduce("numbers: ") {(total, number) in total + "\(number) "}
print(numbers)

Это приводит к следующему выводу:


numbers: 1 2 3 4 5 6 7 8 9 10

Этот пример понижает массив целых чисел до строки, указанной выше.

Немного практики и вы будете использовать reduce по-всякому! 

Задача

Можете ли вы использовать reduce для того, чтобы преобразовать массив digits в целое число, если массив ввода такой:


let digits = ["3", "1", "4", "1"]

Ваш понижающий метод должен возвращать Int со значением 3141.

Магия Reduce

В предыдущем разделе, вы разработали свою собственную реализацию filter, что оказалось удивительно просто. Теперь вы увидите, что то же самое можно сделать и для reduce.

Добавьте следующий код в ваш плейграунд:


extension Array {
  func myReduce<T, U>(seed:U, combiner:(U, T) -> U) -> U {
    var current = seed
    for item in self {
      current = combiner(current, item as! T)
    }
    return current
  }
}

Код выше добавляет метод myReduce в Array, который имитирует встроенную функцию Array. Этот метод просто перебирает каждый элемента массива, вызывая на каждом этапе combiner.

Чтобы это проверить, замените один из методов reduce в вашем плейграунде на myReduce.

Вы, наверное, на этом этапе думаете о том, почему вам должно хотеться реализовать filter или reduce? Ответ: “А и не должно хотеться!”

Но тем не менее, вы можете захотеть расширить использование функциональной парадигмы в Swift и реализовать собственные функциональные методы. Видеть и понимать, насколько это легко реализовывать такие действенные методы, как reduce, очень важно и нужно.

Построение индекса

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

В этом разделе вы будете использовать функциональные методы программирования для группировки списка слов в индекс по первой букве слова.

В новом плейграунде добавьте:


import Foundation
 
let words = ["Cat", "Chicken", "fish", "Dog",
                      "Mouse", "Guinea Pig", "monkey"]

Для выполнения задачи этого раздела, вам нужно сгруппировать эти слова по их первым буквам (без учета регистра!).

Добавьте следующее:


typealias Entry = (Character, [String])
 
func buildIndex(words: [String]) -> [Entry] {
  return [Entry]()
}
print(buildIndex(words))

typealias Entry определяет тип кортежа для каждой записи индекса. Использование typealias в этом примере делает код более удобным для чтения, устраняя необходимость повторно указать тип кортежа в полном объеме. Вам нужно добавить код, создающий индекс, в buildIndex.

Строим индекс императивно

Начнем с императивного подхода; обновите buildIndex следующим:


func buildIndex(words: [String]) -> [Entry] {
  var result = [Entry]()
  
  var letters = [Character]()
  for word in words {
    let firstLetter = Character(word.substringToIndex(word.startIndex.advancedBy(1)).uppercaseString)
    
    if !letters.contains(firstLetter) {
      letters.append(firstLetter)
    }
  }
  
  for letter in letters {
    var wordsForLetter = [String]()
    for word in words {
      let firstLetter = Character(word.substringToIndex(word.startIndex.advancedBy(1)).uppercaseString)
      
      if firstLetter == letter {
        wordsForLetter.append(word)
      }
    }
    result.append((letter, wordsForLetter))
  }
  return result
}

У этой функции две части, у каждой части свой собственный цикл for. Первая половина перебирает слова, чтобы построить массив букв; вторая перебирает эти буквы, находя слова, которые начинаются с нужной буквы, чтобы построить обратный массив.

После реализации, вы увидите желаемый результат:


[("C", ["Cat", "Chicken"]), 
 ("F", ["fish"]), 
 ("D", ["Dog"]), 
 ("M", ["Mouse", "monkey"]), 
 ("G", ["Guinea Pig"])]

(Текст выше немного отформатирован для ясности.)

У этой императивной реализации довольно много шагов и вложенных циклов, от этого возникают сложности понимания. Давайте посмотрим, как выглядит функциональный эквивалент.

Создание индекса функциональным путем

Создайте новый файл в плейграунде и добавьте ту же первоначальную структуру:


import Foundation

let words = ["Cat", "Chicken", "fish", "Dog",
             "Mouse", "Guinea Pig", "monkey"]

typealias Entry = (Character, [String])

func buildIndex(words: [String]) -> [Entry] {
  return [Entry]()
}

print(buildIndex(words))

На данном этапе, заявление print будет выводить пустой массив:


[]

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


func buildIndex(words: [String]) -> [Entry] {
  let letters = words.map {
    (word) -> Character in
    Character(word.substringToIndex(word.startIndex.advancedBy(1)).uppercaseString)
    
  }
  print(letters)
  
  return [Entry]()
}

Плейграунд в настоящее время выводит массив букв верхнего регистра, каждой букве соответствует слово в вводном массиве.


[C, C, F, D, M, G, M]

В предыдущих разделах, вы столкнулись с filter и reduce. Приведенный выше код представляет map, другой функциональный метод, являющийся частью API массива.

map создает новый массив с результатами вызовов прилагаемого замыкания для каждого элемента в каждом массиве. Вы можете использовать map для выполнения преобразований. В этом случае map преобразует массив типа [String] в массив типа [Character].

Сейчас массив букв содержит дубликаты, а у нужного вам индекса должно быть только одно вхождение каждой буквы. К сожалению, у типа массива в Swift нет метода дедупликации. Это то, что вам нужно написать самим!

В предыдущих разделах мы видели, что повторно реализовать reduce и filter легко. Добавление метода дедупликации также не должно вызвать сложности.

Добавьте следующую функцию в вашем плейграунде перед buildIndex:


func distinct<T: Equatable>(source: [T]) -> [T] {
  var unique = [T]()
  for item in source {
    if !unique.contains(item) {
      unique.append(item)
    }
  }
  return unique
}

При построении нового массива, содержащего только уникальные элементы, distinct перебирает все элементы массива.

Обновите buildIndex, чтобы привести в работу distinct:


func buildIndex(words: [String]) -> [Entry] {
  let letters = words.map {
    (word) -> Character in
    Character(word.substringToIndex(word.startIndex.advancedBy(1)).uppercaseString)
    
  }
  let distinctLetters = distinct(letters)
  print(distinctLetters)
  
  return [Entry]()
}

Ваша плейграунд теперь будет выводить уникальные буквы:


[C, F, D, M, G]

Теперь, когда у вас есть массив различных букв, следующей задачей в создании индекса, будет преобразование каждой буквы в экземпляр Entry. Не звучит как преобразование? Еще одна работа для map!

Обновите buildIndex следующим образом:


func buildIndex(words: [String]) -> [Entry] {
  let letters = words.map {
    (word) -> Character in
    Character(word.substringToIndex(word.startIndex.advancedBy(1)).uppercaseString)
    
  }
  let distinctLetters = distinct(letters)
  return distinctLetters.map {
    (letter) -> Entry in
    return (letter, [])
  }
}

Второй вызов map принимает массив символов и выводит массив экземпляров Entry:


[(C, []), 

 (F, []), 

 (D, []), 

 (M, []), 

 (G, [])]

(Опять же, текст выше для ясности отформатирован.)

Вы на финишной прямой. Заключительная задача состоит в том, чтобы заполнить каждый экземпляр ввода словами, которые начинаются с данной буквы. Обновите функцию для добавления вложенного filter, следующим образом:


func buildIndex(words: [String]) -> [Entry] {
  let letters = words.map {
    (word) -> Character in
    Character(word.substringToIndex(word.startIndex.advancedBy(1)).uppercaseString)
    
  }
  
  let distinctLetters = distinct(letters)
  return distinctLetters.map {
    (letter) -> Entry in
    
    return (letter, words.filter {
        (word) -> Bool in
      Character(word.substringToIndex(word.startIndex.advancedBy(1)).uppercaseString) == letter
  
      })
  }
}

Это даст вам желаемый результат:


[(C, [Cat, Chicken]),

 (F, [fish]),

 (D, [Dog]),

 (M, [Mouse, monkey]),

 (G, [Guinea Pig])]

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

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

Если бы это был код на Objective-C, у вас было бы несколько вариантов: Вы могли бы создать метод, который бы выполнял эту функцию  или, возможно, вы могли бы добавить этот метод непосредственно к NSString через категорию класса. Тем не менее, если вам нужно выполнить эту задачу в рамках buildIndex, то в служебном методе не достаточно смысловой ясности и использование категории класса будет излишним.

К счастью, Swift предлагает отличный вариант!

Обновите buildIndex следующим:


func buildIndex(words: [String]) -> [Entry] {
  func firstLetter(str: String) -> Character {
    return Character(str.substringToIndex(str.startIndex.advancedBy(1)).uppercaseString)
  }
  
  let letters = words.map {
    (word) -> Character in
    Character(word.substringToIndex(word.startIndex.advancedBy(1)).uppercaseString)
    
  }
  
  
  return distinct(words.map(firstLetter))
    .map {
      (letter) -> Entry in
      
      return (letter, words.filter {
        (word) -> Bool in
        Character(word.substringToIndex(word.startIndex.advancedBy(1)).uppercaseString) == letter
        })
  }
}

Вы увидите точно такой же результат, как видели и раньше.

Приведенный выше код добавляет функцию firstLetter, вложенную в buildIndex, и в результате, является полностью локальной по отношению к внешней функции. Такой вариант получает преимущество функций первого класса в Swift, которые вы можете использовать, как переменные, что позволяет их присваивать и определять их область использования.

Новый код удаляет дублирование, но вы можете сделать еще больше, чтобы очистить buildIndex.

Первый шаг map, направленный на построение массива букв, принимает замыкание, тип которого (String) -> Character. Вы можете заметить, что этот тип точно такой же, как у функции firstLetter, которую вы только что добавили, что означает, что вы можете передать ее непосредственно на map.

Используя эти знания, вы можете переписать функцию следующим образом:


func buildIndex(words: [String]) -> [Entry] {
  func firstLetter(str: String) -> Character {
    return Character(str.substringToIndex(str.startIndex.advancedBy(1)).uppercaseString)
  }
  
  
  return distinct(words.map(firstLetter))
    .map {
      (letter) -> Entry in
      
      return (letter, words.filter {
        (word) -> Bool in
        Character(word.substringToIndex(word.startIndex.advancedBy(1)).uppercaseString) == letter
        })
  }
}

Конечный результат краткий, но весьма выразительный.

Возможно, вы заметили интересный побочный эффект функциональных методов, которые вы до сих пор использовали. В то время как ваши императивные решения полагались на переменные (с использованием ключевого слова var), то все функциональные эквиваленты вы определяли как константы (через let).

Вы должны стремиться к неизменности. Неизменные типы легче тестировать и использовать параллелизм. Функциональное программирование и неизменные типы, как правило, идут рука об руку. В результате, ваш код будет более кратким, а также менее подвержен ошибкам. Он будет круто выглядеть и произведет впечатление на ваших друзей!

Заметка

Сейчас buildIndex возвращает неотсортированный индекс; порядок экземпляров Entry зависит от порядка слов в вводном массиве. Ваша задача состоит в том, чтобы отсортировать индекс в алфавитном порядке. Для примера массив строк, то это дало бы следующий вывод:


[(C, [Cat, Chicken]),
 (D, [Dog]),
 (F, [fish]),
 (G, [Guinea Pig]),
 (M, [Mouse, monkey])]
Подсказка:

Тип массива в Swift имеет метод sort, но этот метод изменяет массив, а не возвращает новый, отсортированный экземпляр. Для работы он требует изменяемый массив. В общем, безопаснее иметь дело с неизменяемыми данными, поэтому я бы не советовал этот метод! В качестве альтернативы, использовать метод сортировки, который возвращает второй отсортированный массив.

Что дальше?

Дальше, вы можете продолжить изучать наши туториалы по мере их появления, а также, параллельно читать перевод официальной книги по языку программирования Swift. И, для более подробного изучения языка, вы можете пройти наши курсы!

Урок подготовил: Акулов Иван

Источник урока: Источник

Базовые операторы — SwiftBook

Оператор — это специальное символ или выражение для проверки, изменения или сложения величин. Например, оператор сложения (+) суммирует два числа let i = 1 + 2, а логический оператор И && объединяет два логических значения, если введеноDoorCode && passRetinaScan.

Language Swift поддерживает стандартных стандартных операторов C, а также ряд возможностей для устранения типичных ошибок в коде. Оператор присваивания (=) не возвращает значение, что позволяет избежать путаницы с оператором проверки на равенство (==).Арифметические операторы (+, -, *, /,% и т. Д.) Могут обнаруживать и предотвращать переполнение типа, чтобы числовой переменной нельзя было присвоить слишком большое или слишком маленькое значение. Контроль переполнения типа включается в Swift специальными операторами, которые предлагают в разделе Операторы переполнения.

В отличие от C язык Swift позволяет делить с остатком (%) числа с плавающей точкой. Также в Swift имеются два сокращенных оператора интервала (a ..

В этой главе описываются стандартные операторы Swift. Более сложные операторы Swift представлены в разделе Продвинутые операторы, где описаны, как объявить пользовательские операторы и реализовать стандартные операторы для пользовательских типов.

Операторы делятся на унарные, бинарные и тернарные:

  • Унарные операторы применяются к одной величине (например, -a). Унарные префиксные операторы ставятся непосредственно перед величиной (например,! B), а унарные постфиксные — сразу за ней (например, c!).
  • Бинарные операторы к двум величинам (например, 2 + 3) являются инфиксными , так как ставятся между этими величинами.
  • Тернарные операторы применяются к трем величинам. Как и на языке C, в Swift есть только один такой оператор, а именно — тернарный условный оператор (a? B: c).

Величины, к которым операторы, называются операндами . В выражении 1 + 2 символ + является бинарным оператором, а его операндами сад 1 и 2.

Оператор присваивания (a = b) инициализирует или изменяет значение a на значение b:

  пусть b = 10
var a = 5
а = б
// теперь a равно 10  

Если левая часть выражения является кортежем с использованием значений значений, его элементам можно присвоить сразу несколько констант или числовое значение:

  пусть (x, y) = (1, 2)
// x равно 1, а y равно 2  

В отличие от C и Objective-C оператор присваивания в Swift не может возвращать значение.К примеру, следующее выражение недопустимо:

 , если x = y {
// это неверно, так как x = y не возвращает никакого значения
}  

Эта особенность не позволяет разработать оператор присваивания (=) с оператором проверки на равенство (==). Благодаря тому, что выражения типа if x = y некорректны, подобные ошибки при программировании на Swift не произойдут.

Language Swift поддерживает четыре стандартных арифметических оператора для всех числовых типов:

  • сложение (+)
  • вычитание (-)
  • умножение (*)
  • деление (/)
  1 + 2 // равно 3
5 - 3 // равно 2
2 * 3 // равно 6
10.0 / 2.5 // равно 4.0  

В отличие от C и Objective-C арифметические операторы Swift по умолчанию не допускают переполнения типа. Контроль переполнения типа включается в Swift специальными операторами (например, a & + b). Подробнее см. в главе Операторы переполнения.

Оператор сложения служит также для конкатенации, или же склейки, строковых значений (тип String):

  «привет,» + «мир» // равно «привет, мир»  

Оператор целочисленного деления

Оператор целочисленного деления (a% b) показывает, какое количество b помещается внутри a, и возвращает остаток деления a на b.

Заметка

Оператор целочисленного деления (%) на некоторых языках называется оператором деления по модулю . Однако его действие над отрицательным числом в Swift, строго говоря, выполняет действие с остатком, а не по модулю.

Оператор целочисленного деления работает следующим образом. Для вычисления выражения 9% 4 сначала определяется, сколько четверок содержит вятке:

В одной девятке две четверки, а остатком будет 1 (выделено оранжевым цветом).

На языке Swift это записывается так:

 9% 4 // равно 1 

Чтобы получить результат деления% b, оператор% вычисляет следующее выражение и возвращает остаток:

a = (b × множитель) + остаток

где множитель показывает, сколько раз целых b содержится в a.

Подставляя в это выражение 9 и 4, получим:

9 = (4 × 2) + 1

Точно так же рассчитывается остаток, когда отрицательно:

 -9% 4 // равно -1 

Подставляя в наше выражение -9 и 4, получим:

-9 = (4 × -2) + -1

причем остаток будет равен -1.

Если b отрицательно, его знак отбрасывается. Это, что выражения a% b и a% -b всегда будут означать одинаковый результат.

Оператор унарного минуса

Для изменения знака числового значения служат префиксный минус -, который называется оператором унарного минуса :

  пусть три = 3
let minusThree = -three // minusThree равно -3
let plusThree = -minusThree // plusThree равно 3, т. е. "минус минус три"  

Оператор унарного минуса (-) ставится непосредственно перед значением, без пробела.

Оператор унарного плюса

Оператор унарного плюса (+) просто возвращает значение без каких-либо изменений:

  пусть minusSix = -6
let alsoMinusSix = + minusSix // alsoMinusSix равно -6  

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

Как и на языке C, в Swift имеются составные операторы присваивания , совмещающие простое присваивание (=) с другой операцией.Одним из способов может служить оператор присваивания со сложением (+ =):

  var a = 1
а + = 2
// теперь a равно 3  

Выражение a + = 2 является краткой формой записи a = a + 2. Таким образом, один и тот же оператор выполняет функцию сложения и присваивания.

Заметка

Составные операторы присваивания не возвращают значение. К примеру, нельзя написать так: let b = a + = 2.

Для полного списка операторов, установленных стандартной библиотекой Swift, см.Заявления оператора.

Language Swift поддерживает все стандартные операторы сравнения из C:

  • равно (а == б)
  • не равно (а! = Б)
  • больше (a> b)
  • меньше (a
  • больше или равно (а> = б)
  • или меньше равно (a <= b)
Заметка

На языке Swift есть также два оператора проверки идентичности / тождественности (=== и! ==), определяющие, указаны ли два указателя на один и тот же экземпляр объекта.Дополнительную информацию см. в главе Классы и структуры.

Каждый оператор возвращает значение типа Bool, указывающее, является ли выражение истинным:

  1 == 1 // истина, так как 1 равно 1
2! = 1 // истина, так как 2 не равно 1
2> 1 // истина, так как 2 больше чем 1
1 <2 // истина, так как 1 меньше 2
1> = 1 // истина, так как 1 больше либо равно 1
2 <= 1 // ложь, так как 2 не меньше либо равно 1  

Операторы сравнения часто используются в условных выражениях, включая инструкцию if:

  let name = "world"
if name == "world" {
print ("привет, мир")
} else {
print ("Мне жаль, \ (имя), но я тебя не узнаю")
}
// напечатает "hello, world", так как name очевидно равно "world"  

Подробнее об инструкции если см.в главе "Управление потоком".

Вы так же сравниваете кортежи, которые имеют одно и то же количество значений, которые, в свою очередь, должны быть сравниваемыми, что означает, что кортеж типа (Int, String) может быть сравнен с кортежем такого же типа.
Кортежи сравниваются слева направо, по одному значению за раз до тех пор, пока операция не найдет отличия между значениями. Если все значения кортежей попарно равны, то и кортежи так же считаются равными. Например:

  (1, "zebra") <(2, "apple") // true, потому что 1 меньше 2, "zebra" и "apple" не сравниваются
(3, "яблоко") <(3, "птица") // истина, потому что 3 равно 3, а "яблоко" меньше чем "птица"
(4, "dog") == (4, "dog") // верно, потому что 4 равно 4 и "dog равно" dog " 

В примере выше в первой строке вы можете видеть сравнение слева на право.Так как 1 меньше 2, то (1, «зебра») меньше (2, «яблоко»), несмотря на остальные значения кортежа, потому что это неравенство было определено первыми членами. Не важно, что «зебра» не меньше, чем «яблоко», потому что сравнение уже определено первыми элементами кортежей. Однако, когда первые элементы кортежей одинаковые, то сравниваются вторые элементы и так далее.

Кортежи могут сравниваться, только в том случае, если оператор сравнения можно применить ко всем кортежей соответственно.Например, как показано в коде ниже, вы можете сравнить два кортежа типа (String, Int), потому что и String, и Int могут сравниться оператором <. И наоборот, кортеж типа (String, Bool) сравниваться не может, так как к значениям типа Bool операторы сравнения не применяются.

  ("синий", -1) <("фиолетовый", 1) // ОК, расценивается как true
("синий", ложь) <("фиолетовый", правда) // Ошибка так как <не может правил к значениям типа Bool  
Заметка

Стандартная библиотека Swift включает в себя операторы сравнения кортежей, которые имеют менее семи значений.Если вам нужны операторы, которые могут сравнивать кортежи с более, чем шестью элементами, то вам нужно реализовать их самостоятельно.

Тернарный условный оператор - это специальный оператор из трех частей, имеющий следующий синтаксис: выражение? действие1: действие2. Он одно действует из двух действий в зависимости от того, является ли выражение истинным или ложным. Если выражение истинно, оператор выполняет действие1 и возвращает его результат; в случае произошедшего действия2 и возвращается его результат.

Тернарный условный оператор является краткой записью следующего кода:

  if выражение {
действие1
} else {
действие2
}  

Ниже приведен пример расчета высоты строки в таблице. Если у строки есть заголовок, то она должна быть выше своего содержимого на 50 точек, а если заголовок нет, то на 20 точек:

  let contentHeight = 40
пусть hasHeader = true
пусть rowHeight = contentHeight + (hasHeader? 50: 20)
// rowHeight равно 90  

В развернутом виде этот код можно записать так:

  let contentHeight = 40
пусть hasHeader = true
var rowHeight = contentHeight
if hasHeader {
rowHeight = rowHeight + 50
} else {
rowHeight = rowHeight + 20
}
// rowHeight равно 90  

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

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

Оператор объединения по nil (a ?? b) извлекает опционал a, если он содержит значение, или возвращает значение по умолчанию b, если a равно nil. Выражение может быть только опционалом. Выражение b должно быть такого же типа, что и значение внутри a.

Оператор объединения по ноль является краткой записью следующего государства:

  a! = Ноль? а! : b  

В вышеприведенном коде тернарный условный оператор и принудительное извлечение (a!) Используются для обращения к значению внутри a, если a не равно nil, или для возвращения b в случае.Оператор объединения по нулевому - это более элегантный, короткий и понятный способ одновременно проверить условие и извлечь значение.

Заметка

Если a не равно nil, выражение b не анализируется. Такой подход называется краткой проверкой условий (оценка короткого замыкания).

В следующем примере оператор объединения по пустому выбирает между стандартным значением цвета и пользовательским:

  пусть defaultColorName = "красный"
var userDefinedColorName: String? // по умолчанию равно nil
        
var colorNameToUse = userDefinedColorName ?? defaultColorName
// userDefinedColorName равен nil, поэтому colorNameToUse получает значение по умолчанию - "красный"  

Переменная userDefinedColorName объявлена ​​как строковый (String) опционал и по умолчанию равна nil.Так как userDefinedColorName является опционалом, его значение можно анализировать посредством объединения по nil. В вышеприведенном примере этот оператор задает начальное значение для строковой (String) альтернативного colorNameToUse. Так как userDefinedColorName равно nil, выражение userDefinedColorName ?? defaultColorName возвратит значение defaultColorName, т. е. "красный".

Если userDefinedColorName присвоить отличное от nil значение и снова передать ее в оператор объединения по nil, вместо значения по умолчанию будет использовано значение внутри userDefinedColorName:

  userDefinedColorName = "зеленый"
colorNameToUse = userDefinedColorName ?? defaultColorName
// userDefinedColorName не равно nil, поэтому colorNameToUse получит значение "green"  

На языке Swift есть два диапазона , которые в короткой форме задают диапазон значений.

Оператор замкнутого диапазона

Оператор замкнутого диапазона (a ... b) задает диапазон от a до b, включая сами a и b. При этом значение a не формат b.

Оператор замкнутого диапазона удобно использовать при последовательном переборе значений из некоторого диапазона, например, в цикле for-in:

  для индекса в 1 ... 5 {
print ("\ (index) умножить на 5 будет \ (index * 5)")
}
// 1 умножить на 5 будет 5
// 2 умножить на 5 будет 10
// 3 умножить на 5 будет 15
// 4 умножить на 5 будет 20
// 5 умножить на 5 будет 25  

Подробнее о циклах для-в см.в Управление потоком.

Оператор полузамкнутого диапазона

Оператор полузамкнутого диапазона (a ..

Операторы полузамкнутого диапазона особенно удобны при работе с массивами и другими последовательностями, пронумерованными с нуля, когда нужно перебрать элементы от первого до последнего:

  let names = ["Анна", "Алекс", "Брайан", "Джек"]
пусть count = имена.считать
for i in 0 ..  

Хотя в массиве элемента, диапазон 0 ..

Односторонние диапазоны

Операторы замкнутого диапазона диапазона себе альтернативу - диапазон, который продолжается возможно, но только в одну сторону, например, диапазон, который включает все элементы, начиная от 2 и до последнего индекса.В этих случаях вы можете пропустить значение с одной стороны своего диапазона. Этот тип диапазона называется односторонним , потому что оператор имеет значение только с одной стороны. Например:

  для имени в именах [2 ...] {
    печать (имя)
}
// Брайан
// Разъем
 
for name in names [... 2] {
    печать (имя)
}
// Анна
// Алекс
// Брайан  

Оператор полузамкнутого диапазона так же имеет одностороннюю форму, которая записывается только с одним конечным значением.Точно так же как и в случае, когда вы включаете значение в обеих сторонах, конечное значение не является частью самого диапазона. Например:

  для имени в именах [.. <2] {
    печать (имя)
}
// Анна
// Алекс  

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

  пусть диапазон = ... 5
range.contains (7) // ложь
range.contains (4) // правда
range.contains (-1) // правда  

Логические операторы изменяют или комбинируют логические значения типа Boolean (булево) - true и false.Язык Swift, как и другие C-языковые языки, поддерживает три стандартных логических оператора:

  • логическое НЕ (! A)
  • логическое И (a && b)
  • логическое ИЛИ (a || b)

Оператор логического НЕ

Оператор логического НЕ (! A) инвертирует булево значение - истина меняется на ложь, а ложь становится истиной.

Оператор логического НЕ является префиксным и ставится непосредственно перед значением, без пробела.Как видно из следующего примера, его можно воспринимать как "не a":

  let allowedEntry = false
if! allowedEntry {
print ("ДОСТУП ЗАПРЕЩЕН")
}
// Выведет "ДОСТУП ЗАПРЕЩЕН"  

Конструкция if! AllowedEntry означает «если не allowedEntry». Идущая за ней строка будет выполнена, только если "not allowedEntry" является истиной, т. е. если allowedEntry равно false.

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

Оператор логического И

Оператор логического И (a && b) дает на выходе true тогда и только тогда, когда оба его операнда также равны true.

Если хотя бы один из них равен false, результатом всего выражения тоже будет false. На самом деле, если первое значение равно ложно, то второе даже не будет анализироваться, так как оно все равно не изменит общий результат на истину. Такой подход называется краткой проверкой условий (оценка короткого замыкания).

В следующем примере проверяются два значения типа Bool, и если они оба равны true, программа разрешает доступ:

  пусть enterDoorCode = true
пусть пройденоRetinaScan = false
если указаноDoorCode && passRetinaScan {
print ("Добро пожаловать!")
} else {
print ("ДОСТУП ЗАПРЕЩЕН")
}
// Выведет "ДОСТУП ЗАПРЕЩЕН"  

Оператор логического ИЛИ

Оператор логического ИЛИ (a || b) является инфиксным и записывается в виде двух вертикальных палочек без пробела.С его помощью можно создать логические выражения, которые будут давать истину, если хотя бы один из операндов равенство истинно.

Как и описанный выше оператор логического И, оператор логического ИЛИ использует краткую условия проверки. Если левая часть выражения с логическим ИЛИ равна истине, то правая не анализируется, так как ее значение не повлияет на общий результат.

В приведенном ниже примере первого значения Bool (hasDoorKey) равно false, а второе (knowOverridePassword) равно true.Одно значение из значений равно верно, результат всего выражения тоже становится правдой и доступ разрешается:

  пусть hasDoorKey = false
пусть знаетOverridePassword = true
если hasDoorKey || knowOverridePassword {
print ("Добро пожаловать!")
} else {
print ("ДОСТУП ЗАПРЕЩЕН")
}
// Выведет "Добро пожаловать!"  

Комбинирование логических операторов

Можно также составлять и более сложные выражения из нескольких логических операторов:

  если введеноDoorCode && прошлоRetinaScan || hasDoorKey || knowOverridePassword {
print ("Добро пожаловать!")
} else {
print ("ДОСТУП ЗАПРЕЩЕН")
}
// Выведет "Добро пожаловать!"  

В этом примере с помощью нескольких операторов && и || составляет более длинное и сложное выражение.Однако операторы && и || по-прежнему применяются только к двум величинам, поэтому все выражение можно разбить на три простых условия. Алгоритм работы будет следующим:

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

Исходя из значений enterDoorCode, передано RetinaScan и hasDoorKey первые два подусловия дают false. Однако был введен код экстренного доступа, поэтому все составное выражение по-прежнему верно.

Заметка

Логические операторы Swift && и || являются лево-ассоциированными, что означает, что оценивают выражение с логическими операторами в первую очередь выражения слева направо.

Явное указание круглых скобок

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

  if (EnterDoorCode && PassRetinaScan) || hasDoorKey || knowOverridePassword {
print ("Добро пожаловать!")
} else {
print ("ДОСТУП ЗАПРЕЩЕН")
}
// Выведет "Добро пожаловать!"  

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

Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl + Enter .

Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl + Enter .

.

Функциональное программирование в Swift - SwiftBook

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

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

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

Пришло время повеселиться… в функциональном программировании!

Простая фильтрация массива

Вы начнете с очень простого: с математики.Ваша первая задача состоит в том, чтобы создать простой скрипт Swift, находящий все четные числа от 1 до 10 (включительно). Довольно простая задача и отличное введение в функциональное программирование!

Фильтрация старым способом

Создайте новый файл в плейграунде Swift и сохраните его. Замените созданного файла следующим образом:

  var evens = [Int] ()
for i in 1 ... 10 {
  если я% 2 == 0 {
    evens.append (i)
  }
}
печать (эвены)  

Это приведет к желаемому результату:

  [2, 4, 6, 8, 10]  

(Если вы не видите консоль, то вам нужно, чтобы показался Assistant Editor, вы можете его вызвать через View / Assistant Editor / Show Assistant Editor .)

Этот маленький скрипт очень прост, ключевые моменты алгоритма заключаются в следующем:

  1. Вы создаете пустой (и изменяемый) массив.
  2. Цикл на перебирает числа от 1 до 10 (помните, « ... » включительно!).
  3. Если условие (о том, что число должно быть четным) удовлетворяется, то вы добавляете его в массив.

Приведенный выше код является императивным по своей сути. Инструкции компьютера указывают, как располагать четкие числа через четкие инструкции, используя основные структуры контроля, в данном случае , если и , для-входа .

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

Функциональная фильтрация

Добавьте следующее в конец вашего плейграунда:

  func isEven (число: Int) -> Bool {
  номер возврата% 2 == 0
}
evens = Массив (1...10) .filter (isEven)
печать (эвены)  

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

  [2, 4, 6, 8, 10]  

Давайте рассмотрим подробнее функциональную версию. Она состоит из двух частей:

  1. Array (1 ... 10) представляет собой простой и удобный способ создания массива и включает число от 1 до 10. Оператор диапазона 1 ... 10 создает диапазон, который вы передаете Раздел инициализатору массива.
  2. Выражение filter - это то место, где происходит магия функционального программирования. Этот метод, раскрытый через Array, создает и возвращает новый массив, создает только возвращает значение true . На этом примере - даже , на фильтр .

Вы передаете функцию isEven в качестве параметра для фильтрации, но запомните, что функции теперь стали называться замыканием.Попробуйте добавить следующую, более краткую версию в плейграунд:

  evens = Массив (1 ... 10) .filter {(число) в количестве% 2 == 0}
печать (эвены)  

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

Если вы хотите, чтобы ваш код был еще более кратким, то введите следующее:

  evens = Массив (1...10) .filter {$ 0% 2 == 0}
печать (эвены)  

Код выше использует сокращенное обозначение аргумента, неявные возвращения…, вывод типа ... И это работает!

Заметка

Использование сокращенного обозначения аргумента является предпочтением. Лично я считаю, что для простых примеров, как приведено выше, сокращенное обозначение - это то, что нужно. Тем не менее, я бы выбрал явные аргументы для чего-то более сложного. Компиляторы не связаны с именами чисел, но с помощью их станет мир гораздо разнообразнее!

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

  1. Функции высшего порядка ( Функции высшего порядка): Это функции, которые вы передаете в качестве аргументов других функций. В этом простом примере, фильтр требует передачи функций высшего порядка.
  2. Функции первого класса ( Функции первого класса): Вы можете рассматривать функции, как любую другую переменную. Вы можете присвоить их переменным и передать их в качестве аргументов другим функциям.
  3. Укупорочные средства ( Замыкания ) : Это фактические анонимные функции, которые вы создаете на месте.

Вы могли заметить, что в Objective-C тоже есть демонстрация некоторых из этих функций за счет использования блоков. Swift, однако, идет дальше, чем Objective-C в развитии функционального программирования, соединяя более лаконичный синтаксис и встроенные функциональные возможности, такие как filter .

Магия фильтров

Массивы в Swift имеют ряд функциональных методов, таких как map , join и reduce .Что, собственно, происходит за кулисами этих методов?

Пришло время прикоснуться к магии фильтров и добавить к магии.

В этом же плейграунде, добавьте новую функцию:

  func myFilter  (источник: [T], предикат: (T) -> Bool) -> [T] {
  var result = [T] ()
  for i в источнике {
    if predicate (i) {
      result.append (я)
    }
  }
  вернуть результат
}  

Выше вы видите общую (общую) функцию, которая принимает в качестве своих входных параметров исходный массив типа T , и предекат, или функцию, которая принимает экземпляр типа T и возвращает Bool .

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

Поработаем с добавленной реализацией фильтра, добавив следующий код:

  evens = myFilter (массив (1 ... 10)) {$ 0% 2 == 0}
печать (эвены)  

Опять результат такой же!

Задачка!

Вышеуказанная функция фильтра является глобальной.

Подсказка №1

Вы можете добавить myFilter в массив с помощью расширения класса.

Подсказка №2

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

Функция уменьшить

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

Создайте новый плейграунд и ждите новое задание!

Своя функция Уменьшить

Ваша задача в этом разделе лишь немного сложнее.Возьмите четные числа от 1 до 10, и вычислите их сумму. Для этого вам понадобится функция reduce , которая принимает несколько входных значений и генерирует одно на выходе.

Я уверен, что вы с успехом справитесь и сами, но в любом случае, если ответ ниже! Добавьте инструкцию в ваш плейграунд:

  var evens = [Int] ()
for i in 1 ... 10 {
  если я% 2 == 0 {
    evens.append (i)
  }
}
 
var evenSum = 0
for i in evens {
  evenSum + = я
}
 
печать (evenSum)  

В помощник редактора будет следующий результат:

  30  

Императивный код, выше, написан в том же ключе, что и код в предыдущем примере, с добавлением дополнительного цикла for-in .

Давайте посмотрим, как выглядит функциональный эквивалент!

Функциональная Снизить

Введите строку в ваш плейграунд:

  evenSum = Массив (1 ... 10)
    .filter {(число) в числе% 2 == 0}
    .reduce (0) {(всего, число) всего + число}
 
печать (evenSum)  

Вы точно увидите такой же результат:

  30  

Предыдущий раздел охватывал создание массива и использование фильтра .Конечным результатом этих двух операций является массив с пятью числами [2, 4, 6, 8, 10] . Новым шагом в коде выше стало использование уменьшить .

уменьшить является универсальным методом массивов и функций однократно для каждого элемента.

Для того, чтобы понять, как работает метод уменьшить , нужно посмотреть на его описание:

  func reduce  (по умолчанию: U, объединить: (U, T) -> U) -> U  

Первый параметр - это начальное значение типа U .В текущем коде, начальное значение равно 0 , имеет тип Int (следовательно, U в данном случае Int ). Второй аргумент - это функция , объединить , и выполнить один раз для каждого элемента массива.

комбайн принимает два аргумента: первый, типа U , является результатом предыдущего вызова комбайн , второй - значение элемента массива, с которым он объединен. Результат, возвращаемый reduce , это значение, возвращаемое последним вызовом comb .

Давайте разберем все шаг за шагом.

В вашем коде, первая итерация уменьшить приводит к следующему:

Сначала итого имеет значение 0 , первый элемент входного массива равен 2 . Если мы просуммируем эти значения, то на выходе ( результат ) получится 2 .

Вторая итерация показания ниже:

Во второй итерации, входное значение равно значению предыдущей итерации и следующего элемента из входного массива.Объединение их приводит к 2 + 4 = 6 .

Продолжаем проделывать тоже самое для всех элементов, что приводит к следующим вводам и выводам:

Обозначенное звездочкой число в правом нижнем углу - общий результат.

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

Введите строку в ваш плейграунд:

  пусть maxNumber = Array (1...10)
            .reduce (0) {(всего, число) в макс (итого, число)}
печать (maxNumber)  

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

. Если вы пытаетесь изо всех сил понять, как это работает, почему бы не создать еще одну таблицу, где вы вычислите ввод и выводы , объедините (т.е. замыкания) для каждой итерации?

Примеры, которые вы видели, все уменьшают массивы целых чисел до одиночных целочисленных значений. Конечно, у уменьшить есть два типа параметров, U и T и они могут быть разными, и, конечно, не должны быть интеджерами. Это означает, что вы можете типа уменьшить массив одного типа до другого.

Введите строку в ваш плейграунд:

  пусть числа = Массив (1...10)
  .reduce ("numbers:") {(всего, число) итого + "\ (число)"}
печать (числа)  

Это приводит к следующему выводу:

  номера: 1 2 3 4 5 6 7 8 9 10  

Этот пример понижает массив целых чисел до строки, приведенной выше.

Немного практики и вы будете использовать уменьшить по-всякому!

Задача

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

  пусть цифры = ["3", "1", "4", "1"]  

Ваш понижающий метод должен возвращать Int со значением 3141 .

Магия Уменьшить

В предыдущем разделе, вы разработали свою собственную часть фильтра , что оказалось удивительно просто. Сократите .

Добавьте следующий код в ваш плейграунд:

  extension Array {
  func myReduce  (начальное число: U, объединитель: (U, T) -> U) -> U {
    var current = seed
    для элемента в себе {
      current = combiner (текущий, элемент как! T)
    }
    возврат тока
  }
}  

Код выше отметки метод myReduce в Array , который имитирует встроенную функцию Array .Этот метод просто перебирает каждый элемент массива, вызывающий на каждом этапе объединитель .

Чтобы это проверить, заменить один из методов уменьшить в вашем плейграунде на myReduce .

Вы, наверное, на этом этапе думаете о том, почему вам должно хотеться реализовать фильтр или уменьшить ? Ответ: «А и не должно хотеться!»

Но можете тем не менее, вы захотеть расширить использование функциональной парагдигмы в Swift и реализовать собственные функциональные методы.Видеть и понимать, насколько это легко реализовывать такие действенные методы, как уменьшить, очень важно и нужно.

Построение индекса

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

В этом разделе вы будете использовать функциональные методы программирования для группировки списка слов в индекс по первой букве слова.

В новом плейграунде добавить:

  Фонд импорта
 
let words = ["Кот", "Курица", "рыба", "Собака",
                      «Мышь», «Морская свинка», «обезьяна»]  

Для выполнения этого раздела вам нужно сгруппировать эти слова по их первым буквам (без учета регистра!).

Добавьте следующее:

  typealias Entry = (Символ, [Строка])
 
func buildIndex (words: [String]) -> [Entry] {
  return [Entry] ()
}
print (buildIndex (слова))  

typealias Запись определяет тип кортежа для каждой записи индекса. Использование typealias в этом примере делает код после этого для чтения, устраняя необходимость установить тип кортежа в полном объеме. Вам нужно добавить код, создающий индекс, в buildIndex .

Строим индекс императивно

Начнем с императивного подхода; обновите buildIndex следующим:

  func buildIndex (words: [String]) -> [Entry] {
  var result = [Entry] ()
  
  var letter = [Символ] ()
  for word in words {
    пусть firstLetter = символ (word.substringToIndex (word.startIndex.advancedBy (1)). uppercaseString)
    
    if! letter.contains (firstLetter) {
      letter.append (первое письмо)
    }
  }
  
  для письма в письмах {
    var wordsForLetter = [String] ()
    for word in words {
      пусть firstLetter = Символ (слово.substringToIndex (word.startIndex.advancedBy (1)). uppercaseString)
      
      if firstLetter == letter {
        wordsForLetter.append (слово)
      }
    }
    result.append ((буква, wordsForLetter))
  }
  вернуть результат
}  

У этой функции две части, у каждой части свой собственный цикл for. Первая половина перебирает слова, чтобы построить массив букв; вторая перебирает эти буквы, находя слова, которые начинаются с нужной буквы, чтобы построить обратный массив.

После реализации, вы увидите желаемый результат:

  [(«С», [«Кот», «Цыпленок»]),
 ("F", ["рыба"]),
 ("Д", ["Собака"]),
 («М», [«Мышь», «обезьяна»]),
 ("G", ["Морская свинка"])]  

(Текст выше немного отформатирован для ясности.)

У этой реализации реализации довольно много шагов и вложенных циклов, от этого сложности понимания. Давайте посмотрим, как выглядит функциональный эквивалент.

Создание функциональным путем

Создайте новый файл в плейграунде и добавьте ту же первоначальную структуру:

  Фонд импорта

let words = ["Кот", "Курица", "рыба", "Собака",
             «Мышь», «Морская свинка», «обезьяна»]

typealias Entry = (Символ, [Строка])

func buildIndex (words: [String]) -> [Entry] {
  return [Entry] ()
}

print (buildIndex (слова))  

На данном этапе, заявление print будет выводить пустой массив:

  []  

Первым шагом на пути индекса будет преобразование слов в массив, использование только буквы.Обновите buildIndex следующим:

  func buildIndex (words: [String]) -> [Entry] {
  let letter = words.map {
    (слово) -> Символ в
    Символ (word.substringToIndex (word.startIndex.advancedBy (1)). UppercaseString)
    
  }
  печать (буквы)
  
  return [Entry] ()
}  

Плейграунд в настоящее время выводит массив букв верхнего регистра, каждая букве соответствует слову в вводном массиве.

  [C, C, F, D, M, G, M]  

В предыдущих разделах вы столкнулись с , фильтром и уменьшите .Приведенный выше код представляет map , другой функциональный метод, являющийся частью массива API.

map новый массив с помощью прилагаемого замыкания для каждого элемента в каждом массиве. Вы можете использовать map для выполнения преобразований. В этом случае map преобразует массив типа [String] в массив типа [Character] .

Сейчас массив букв содержит дубликаты, а у нужного вам индекса должно быть только одно вхождение каждой буквы.К сожалению, у типа в Swift нет метода дедупликации. Это то, что вам нужно написать самим!

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

Добавьте свою функцию в вашем плейграунде перед buildIndex :

  func independent  (источник: [T]) -> [T] {
  var unique = [T] ()
  для элемента в источнике {
    если! уникальный.contains (item) {
      unique.append (элемент)
    }
  }
  вернуть уникальный
}  

При построении нового массива, содержащего только уникальные элементы, отдельных перебирает все элементы массива.

Обновите buildIndex , чтобы привести в работу independent :

  func buildIndex (words: [String]) -> [Entry] {
  let letter = words.map {
    (слово) -> Символ в
    Персонаж (слово.substringToIndex (word.startIndex.advancedBy (1)). uppercaseString)
    
  }
  пусть отличныеLetters = разные (буквы)
  печать (разные буквы)
  
  return [Entry] ()
}  

Ваша плейграунд теперь будет выводить уникальные буквы:

  [C, F, D, M, G]  

Теперь, когда у вас есть массив различных букв, следующая задача в индексе будет преобразование каждой буквы в экземпляре Запись . Не звучит как преобразование? Еще одна работа для карта !

Обновите buildIndex следующим образом:

  func buildIndex (words: [String]) -> [Entry] {
  пусть буквы = слова.карта {
    (слово) -> Символ в
    Символ (word.substringToIndex (word.startIndex.advancedBy (1)). UppercaseString)
    
  }
  пусть отличныеLetters = разные (буквы)
  return independentLetters.map {
    (буква) -> Вход в
    return (письмо, [])
  }
}  

Второй вызов map принимает массив символов и выводит массив экземпляров Запись :

  [(C, []),

(F, []),

(D, []),

(M, []),

(G, [])]  

(Опять же, текст выше для ясности отформатирован.)

Вы на финишной прямой. Заключительная задача состоит в том, чтобы заполнить каждый экземпляр ввода словами, которые начинаются с данной буквы. Обновите функцию для добавления вложенного filter , следующим образом:

  func buildIndex (words: [String]) -> [Entry] {
  let letter = words.map {
    (слово) -> Символ в
    Символ (word.substringToIndex (word.startIndex.advancedBy (1)). UppercaseString)
    
  }
  
  пусть отличныеLetters = разные (буквы)
  вернуть отдельные письма.карта {
    (буква) -> Вход в
    
    return (letter, words.filter {
        (word) -> Bool в
      Символ (word.substringToIndex (word.startIndex.advancedBy (1)). UppercaseString) == буква
  
      })
  }
}  

Это даст вам желаемый результат:

  [(C, [Кот, Курица]),

(F, [рыба]),

(Д, [Собака]),

(М, [Мышь, обезьяна]),

(G, [Морская свинка])]  

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

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

Если бы это был код на Objective-C, у вас было бы несколько вариантов: Вы могли бы создать метод, который бы выполнял эту функцию или, возможно, вы могли бы добавить этот метод непосредственно к NSString через категорию класса.Тем не менее, если вам нужно выполнить эту задачу в рамках buildIndex , то в служебном методе не достаточно смысловой ясности и использования категории класса будет излишним.

К счастью, Swift предлагает отличный вариант!

Обновите buildIndex следующим:

  func buildIndex (words: [String]) -> [Entry] {
  func firstLetter (str: String) -> Character {
    вернуть символ (str.substringToIndex (str.startIndex.advancedBy (1)). uppercaseString)
  }
  
  let letter = words.map {
    (слово) -> Символ в
    Символ (word.substringToIndex (word.startIndex.advancedBy (1)). UppercaseString)
    
  }
  
  
  вернуть отличный (words.map (firstLetter))
    .карта {
      (буква) -> Вход в
      
      return (letter, words.filter {
        (word) -> Bool в
        Символ (word.substringToIndex (word.startIndex.advancedBy (1)).uppercaseString) == буква
        })
  }
}  

Вы точно увидите такой же результат, как видели и раньше.

Приведенный выше код функции функции firstLetter , вложенная в buildIndex , в результате, является полностью по отношению к внешней функциям. Такой вариант преимущества использования функций первого класса в Swift, который позволяет использовать их, как переменные, что позволяет их присваивать и определять их область.

Новый код удаляет дублирование, но вы можете сделать еще больше, чтобы очистить buildIndex .

Первый шаг map , направленный на построение множества букв, принимает замыкание, тип которого (String) -> Character . Вы можете заметить, что этот тип точно такой же, как у функции firstLetter , что означает, что вы можете передать ее непосредственно на карту .

Используя эти знания, вы переписать функцию следующим образом:

  func buildIndex (words: [String]) -> [Entry] {
  func firstLetter (str: String) -> Character {
    return Character (str.substringToIndex (str.startIndex.advancedBy (1)). uppercaseString)
  }
  
  
  вернуть отличный (words.map (firstLetter))
    .карта {
      (буква) -> Вход в
      
      return (letter, words.filter {
        (word) -> Bool в
        Символ (word.substringToIndex (word.startIndex.advancedBy (1)). UppercaseString) == буква
        })
  }
}  

Конечный результат краткий, но весьма выразительный.

Возможно, вы заметили интересный побочный эффект функциональных методов, которые вы до сих пор использовали.В то время как ваши императивные решения полагались на переменные (с использованием ключевого слова , var ), все функциональные эквиваленты вы определяли как константы (через let ).

Вы должны стремиться к неизменности. Неизменные типы легче тестировать и использовать параллелизм. Функциональное программирование и предполагает, как правило, идут рука об руку. В результате ваш код будет более кратким, а также менее подвержен ошибкам. Он будет круто выглядеть и произведет впечатление на ваших друзей!

Заметка

Сейчас buildIndex возвращает неотсортированный индекс; порядок экземпляров Ввод зависит от порядка слов в вводном массиве.Ваша задача состоит в том, чтобы отсортировать индекс в алфавитном порядке. Для массива данных строк, то это дало бы следующий вывод:

  [(C, [Кот, Курица]),
 (Д, [Собака]),
 (F, [рыба]),
 (G, [Морская свинка]),
 (М, [Мышь, обезьяна])]  
Подсказка:

Тип массива в Swift имеет метод sort , но метод изменяет массив, а не возвращает этот новый, отсортированный экземпляр. Для работы он требует изменяемый массив. В общем, безопаснее иметь дело с неизменяемыми данными, поэтому я бы не советовал этот метод! В качестве альтернативы, использовать метод сортировки, который возвращает второй отсортированный массив.

Что дальше?

Дальше, вы можете продолжить изучать наши туториалы по мере их появления, а также продолжайте читать официальные книги по языку программирования Swift. И, для более подробного изучения языка, вы можете пройти наши курсы!

Урок подготовил: Акулов Иван

Источник урока: Источник

.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *