Перейти к содержимому

Практика 4. Практика к лекции 4

Цель: освоить функции Swift (метки аргументов, значения по умолчанию, вариативные параметры, inout), научиться работать с функциями как объектами первого класса, разобраться в замыканиях (closures) и их сокращённом синтаксисе, а также применить map, filter, reduce для функциональной обработки данных.

Рекомендации по выполнению:

  • Каждое задание оформляйте в отдельном файле (например, task1.swift).
  • Запускайте через swift имя_файла.swift на Linux.
  • Обращайте внимание на разницу между меткой аргумента (argument label) и именем параметра (parameter name).
  • Старайтесь использовать trailing closure syntax там, где это уместно.
  • Сравнивайте с аналогичными конструкциями Python (lambda, map, filter) — это поможет понять подход Swift.

A. Разминка — базовые функции

  1. Возведение в степень. Напишите функцию power(base:exponent:), которая возводит целое число в целую степень. Значение exponent по умолчанию — 2:
func power(base: Int, exponent: Int = 2) -> Int {
var result = 1
for _ in 0..<exponent {
result *= base
}
return result
}
print(power(base: 3)) // 9
print(power(base: 2, exponent: 10)) // 1024
print(power(base: 5, exponent: 3)) // 125

Дополнительно: обработайте случай exponent == 0 (результат всегда 1) и отрицательные степени (верните 0 для целочисленного варианта, объясните почему в комментарии).

  1. Метки аргументов. Напишите функцию для приветствия с использованием различных меток:
func greet(_ person: String, from hometown: String) -> String {
return "Привет, \(person)! Рады видеть гостя из \(hometown)."
}
print(greet("Иван", from: "Москва"))
// Привет, Иван! Рады видеть гостя из Москва.

Напишите ещё две функции:

  • move(from start: String, to end: String) -> String — возвращает "Перемещение из <start> в <end>".
  • repeat(_ text: String, times count: Int) -> String — повторяет строку count раз через пробел.

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


B. Вариативные параметры и inout

  1. Объединение строк. Напишите функцию joinStrings, принимающую вариативный параметр строк и разделитель (по умолчанию ", "):
func joinStrings(_ strings: String..., separator: String = ", ") -> String {
return strings.joined(separator: separator)
}
print(joinStrings("Swift", "Python", "Go"))
// Swift, Python, Go
print(joinStrings("a", "b", "c", separator: " - "))
// a - b - c
print(joinStrings("Один"))
// Один

Напишите аналогичную функцию sum(_ numbers: Int...) -> Int и протестируйте: sum(1, 2, 3, 4, 5)15.

  1. inout-параметры. Напишите функции, использующие inout:
// Обмен значениями
func swapValues(_ a: inout Int, _ b: inout Int) {
let temp = a
a = b
b = temp
}
var x = 10, y = 20
print("До: x=\(x), y=\(y)")
swapValues(&x, &y)
print("После: x=\(x), y=\(y)")

Дополнительно напишите:

  • clamp(_ value: inout Int, min: Int, max: Int) — ограничивает значение диапазоном.
  • normalize(_ array: inout [Double]) — нормализует массив (делит каждый элемент на максимальный).

Пример ожидаемого поведения:

var val = 150
clamp(&val, min: 0, max: 100)
print(val) // 100
var data = [2.0, 4.0, 6.0, 8.0, 10.0]
normalize(&data)
print(data) // [0.2, 0.4, 0.6, 0.8, 1.0]

C. Функции как объекты первого класса

  1. Функции высшего порядка. Напишите функцию applyOperation, принимающую два числа и функцию-операцию:
func add(_ a: Int, _ b: Int) -> Int { return a + b }
func multiply(_ a: Int, _ b: Int) -> Int { return a * b }
func applyOperation(_ a: Int, _ b: Int, operation: (Int, Int) -> Int) -> Int {
return operation(a, b)
}
print(applyOperation(5, 3, operation: add)) // 8
print(applyOperation(5, 3, operation: multiply)) // 15

Создайте массив операций let operations: [(Int, Int) -> Int] = [add, multiply, ...] и выведите результат каждой операции для чисел 12 и 4.

  1. applyToEach. Напишите функцию, принимающую inout-массив и замыкание, применяющее преобразование к каждому элементу:
func applyToEach(_ array: inout [Int], transform: (Int) -> Int) {
for i in 0..<array.count {
array[i] = transform(array[i])
}
}
var nums = [1, 2, 3, 4, 5]
applyToEach(&nums) { $0 * $0 }
print(nums) // [1, 4, 9, 16, 25]
applyToEach(&nums) { $0 + 10 }
print(nums) // [11, 14, 19, 26, 35]

D. Замыкания и сокращённый синтаксис

  1. Фабрика замыканий (makeMultiplier). Напишите функцию, возвращающую замыкание:
func makeMultiplier(factor: Int) -> (Int) -> Int {
return { number in
return number * factor
}
}
let double = makeMultiplier(factor: 2)
let triple = makeMultiplier(factor: 3)
print(double(5)) // 10
print(triple(7)) // 21
print(triple(10)) // 30

Дополнительно создайте:

  • makeAdder(value:) — возвращает замыкание, прибавляющее value.
  • makeFormatter(prefix:suffix:) — возвращает замыкание (String) -> String, оборачивающее строку.
let addTen = makeAdder(value: 10)
print(addTen(5)) // 15
let bracket = makeFormatter(prefix: "[", suffix: "]")
print(bracket("hello")) // [hello]
  1. Этапы сокращения замыкания. Для массива [5, 2, 8, 1, 9, 3] отсортируйте по убыванию, постепенно сокращая синтаксис замыкания. Запишите все 5 этапов:
let numbers = [5, 2, 8, 1, 9, 3]
// Этап 1: полная форма
let sorted1 = numbers.sorted(by: { (a: Int, b: Int) -> Bool in
return a > b
})
// Этап 2: вывод типов
let sorted2 = numbers.sorted(by: { a, b in
return a > b
})
// Этап 3: неявный return
let sorted3 = numbers.sorted(by: { a, b in a > b })
// Этап 4: сокращённые имена аргументов
let sorted4 = numbers.sorted(by: { $0 > $1 })
// Этап 5: trailing closure
let sorted5 = numbers.sorted { $0 > $1 }
// Убедитесь, что все варианты дают одинаковый результат
print(sorted1) // [9, 8, 5, 3, 2, 1]
print(sorted2)
print(sorted3)
print(sorted4)
print(sorted5)

E. Функциональные цепочки — map, filter, reduce

  1. Обработка массива. Используя map, filter, reduce с trailing closure, обработайте массив чисел 1…20:
    • Отберите числа, делящиеся на 3.
    • Возведите их в квадрат.
    • Найдите сумму.
let result = (1...20)
.filter { $0 % 3 == 0 }
.map { $0 * $0 }
.reduce(0, +)
print(result)
// Числа: 3, 6, 9, 12, 15, 18
// Квадраты: 9, 36, 81, 144, 225, 324
// Сумма: 819

Дополнительные цепочки (реализуйте каждую):

  • Массив строк ["hello", "world", "swift", "is", "great"] → отобрать слова длиннее 4 букв → привести к верхнему регистру → объединить через " ".
  • Массив [1, -2, 3, -4, 5, -6] → оставить положительные → удвоить → найти произведение.
  1. Обработка студентов. Создайте массив кортежей студентов и обработайте его функциональными цепочками:
typealias Student = (name: String, grade: Double)
let students: [Student] = [
("Анна", 4.8),
("Борис", 3.2),
("Вера", 4.5),
("Глеб", 2.9),
("Дарья", 3.8),
("Егор", 4.1),
("Жанна", 4.9),
("Захар", 3.5)
]

Используя filter, map, sorted, reduce, выведите:

  • Список отличников (балл >= 4.5), отсортированный по убыванию балла.
  • Средний балл всех студентов.
  • Имена всех студентов одной строкой через запятую.
  • Количество студентов с баллом ниже 3.5.

Ожидаемый вывод:

Отличники: Жанна (4.9), Анна (4.8), Вера (4.5)
Средний балл: 3.96
Все студенты: Анна, Борис, Вера, Глеб, Дарья, Егор, Жанна, Захар
Студентов с баллом < 3.5: 2

F. Мини-проект — «Конвейер обработки данных»

  1. Создайте программу pipeline.swift, реализующую конвейер (pipeline) обработки текстовых данных с использованием функций и замыканий:
typealias Transform = (String) -> String

Реализуйте следующие функции-трансформации:

  • trimWhitespace — удаляет пробелы по краям.
  • lowercase — приводит к нижнему регистру.
  • removeDigits — удаляет все цифры из строки.
  • replaceSpaces(with:) — возвращает замыкание, заменяющее пробелы на указанный символ.

Напишите функцию pipeline, принимающую строку и массив трансформаций:

func pipeline(_ input: String, transforms: [Transform]) -> String {
var result = input
for transform in transforms {
result = transform(result)
}
return result
}

Протестируйте:

let input = " Hello World 123 "
let result = pipeline(input, transforms: [
trimWhitespace,
lowercase,
removeDigits,
replaceSpaces(with: "-")
])
print(result) // hello-world-

Добавьте:

  • Функцию makePipeline(_ transforms: Transform...) -> Transform, возвращающую одно замыкание, объединяющее все трансформации.
  • Протестируйте: let clean = makePipeline(trimWhitespace, lowercase)clean(" HeLLo ")"hello".

G. Критерии оценивания

  • Корректность компиляции и работы всех программ на Linux: 0–4 балла
  • Правильное использование функций (метки, значения по умолчанию, вариативные, inout): 0–4 балла
  • Корректная работа с замыканиями и функциональными цепочками (map/filter/reduce): 0–4 балла
  • Оформление кода: комментарии, именование, читаемость, отсутствие дублирования: 0–4 балла

Максимум: 16 баллов. Бонус до +2 за задания из раздела «Дополнительно».


H. Дополнительно (по желанию)

  • Напишите функцию memoize<T: Hashable, U>(_ function: @escaping (T) -> U) -> (T) -> U, кэширующую результаты вызовов (мемоизация). Протестируйте на функции вычисления чисел Фибоначчи.
  • Реализуйте аналог reduce вручную: функцию myReduce, принимающую массив, начальное значение и замыкание-аккумулятор.
  • Сравните производительность: создайте массив из 1 000 000 элементов и сравните время выполнения цепочки filter → map → reduce с эквивалентным ручным циклом for-in.