
dd1f868bd3f8144a45d9742c246495fb.ppt
- Количество слайдов: 50
Функциональное программирование F#. Списки Лекция 2 Мар. ГТУ, 2012 1
Ø Декларация типов Ø Списки Ø Функции высшего порядка Ø Пример загрузка данных из файла Мар. ГТУ, 2012 2
Декларация типов Мар. ГТУ, 2012 3
Декларация типов Ø Объявление типа позволяет определить новые типы соответствующие обычным структурам данных используемым в программах ü тип–произведение для кортежей или записей ü тип–сумма для union ü рекурсивный тип ü параметризованный тип ü функциональный тип type nom = typedef ; ; Мар. ГТУ, 2012 4
Декларация типов Ø Объявление типов по умолчанию рекурсивно type nom 1 = typedef 1 and nom 2 = typedef 2 : and nomn = typedefn ; ; Ø Параметризация переменной типа type 'a nom = typedef ; ; type ('a 1. . . 'an) nom = typedef ; ; Мар. ГТУ, 2012 5
Записи Ø Запись — это кортеж, в котором каждому полю присваивается имя. Запись всегда соответствует объявлению нового типа. Для определения записи необходимо указать ее имя, а так же имя и тип для каждого поля записи. type nom = { nom 1 : t 1; . . . ; nomn : tn } ; ; let c = {re=2. ; im=3. } Мар. ГТУ, 2012 6
Пример let add_complex c 1 c 2 = {re=c 1. re+. c 2. re; im=c 1. im+. c 2. im}; ; let mult_complex c 1 c 2 = match (c 1, c 2) with ({re=x 1; im=y 1}, {re=x 2; im=y 2}) -> {re=x 1*. x 2 -. y 1*. y 2; im=x 1*. y 2+. x 2*. y 1} ; ; Ø Преимущество записей по сравнению с кортежами ü более информативное описание, благодаря именам полей — это в частности позволяет облегчить образцы фильтра; ü идентичное обращение по имени, порядок значения не имеет — главное указать имя. Ø Подробнее смотрите пункт 1. 2. 3 «Записи» в документации языка Ocaml Мар. ГТУ, 2012 7
Сумма Ø тип сумма соответствует объединению множеств (union) type nom =. . . | Nomi. . . | Nomj of tj. . . | Nomk of tk *. . . * tl. . . ; ; Ø Имя конструктора это специальный идентификатор. Ø Замечание ü Имя конструктора должна всегда начинаться с заглавной буквы. ü ключевое слово of указывает тип аргумента Мар. ГТУ, 2012 8
Рекурсивный тип Ø В отличии от объявления let, type всегда рекурсивный (необходим – для списков, деревьев и так далее) type int_or_char_list = Nil | Int_cons of int * int_or_char_list | Char_cons of char * int_or_char_list ; ; Мар. ГТУ, 2012 9
Видимость описания Ø На имена конструкторов распространяются те же правила, что и на глобальные объявления. Переопределение скрывает своего предшественника. Скрытые значения всегда существуют, они никуда не делись. Ø Дома рассмотреть параметризованные, функциональные типы, видимость описания Ø (пункты 1. 2. 6 – 1. 2. 8) Мар. ГТУ, 2012 10
Списки Мар. ГТУ, 2012 11
Примеры 1: : 2: : 3: : [] или [1; 2; 3] “hello”: : ”there”: : [] или [“hello”; ”there”] Ø : : – это бинарный оператор, синоним cons (constructor) Ø Все операции описаны в модуле List, поэтому при их использовании надо добавить в программу open List, либо указывать List. cons и т. д. Мар. ГТУ, 2012 12
Работа с головой и хвостом Ø Для отделения головы и хвоста обычно используют pattern matching let h: : t = [1; 2; 3]; ; match list with [] -> … | h: : t -> …; ; Ø Возможно также использовать функции hd (head для версии 2. 0) и tl (tail для версии 2. 0), а также cons для создания Мар. ГТУ, 2012 13
Рекурсивная обработка рекурсивной структуры Ø Структура данных рекурсивна, потому что она определяется через саму себя Ø Соответственно, для такой структуры хорошо подходит рекурсивная обработка с pattern matching let rec length l = match l with [] -> 0 | _: : t -> 1+length(t) ; ; Мар. ГТУ, 2012 let rec length = function [] -> 0 | _: : t -> 1+length(t); ; 14
Сумма элементов списка let rec sum L = match L with [x] -> x | x: : t -> x+sum t; ; let rec sum = function [x] -> x | x: : t -> x+sum t; ; Ø sum [1; 2; 3] → 1+sum [2; 3] → 1+2+sum [3] → 1+2+3 → 6 Ø В модуле List определены более общие функции ü List. sum. By: (T→U) →T list →U Ø let sum = List. sum. By (fun x -> x) [4; 3; 6; 8]; ; Мар. ГТУ, 2012 15
Определение длины списка Ø Встроенная функция модуля List let rec length l = match l with [] -> 0 | _: : t -> 1+length(t) ; ; let rec length = function [] -> 0 | _: : t -> 1+length(t); ; Ø length [1; 2; 3] → 1+length [2; 3] → 1+1+length [3] → 1+1+1+length [] → 1+1+1+0 → 3 Мар. ГТУ, 2012 16
Принадлежность элемента списку Ø Встроенная функция модуля List let rec mem x = function [] -> false | z: : t -> z=x or mem x t; ; Ø mem 2 [1; 2; 3] → (1=2) or mem 2 [2; 3] → (2=2) or mem 2 [3] → true Ø mem 4 [1; 2] → (4=1) or mem 4 [2] → (4=2) or mem 4 [] → false Мар. ГТУ, 2012 17
Конкатенация списков let rec append M L = match M with [] -> L | h: : t -> h: : (append t L); ; Ø append [1; 2] [3; 4] → 1: : append [2] [3; 4] → 1: : 2: : append [] [3; 4] → 1: : 2: : [3; 4] = [1; 2; 3; 4] Ø Определена как оператор @ Ø [1; 2] @ [3; 4] → [1; 2; 3; 4] Мар. ГТУ, 2012 18
Удаление элемента по значению let rec remove x = function [] -> [] | h: : t when h=x -> remove x t | h: : t -> h: : remove x t; ; Ø remove 3 [2; 3; 4; 3] → if 3=2 then remove 3 [3; 4; 3] else 2: : remove 3 [3; 4; 3] → 2: : remove 3 [4; 3] → 2: : 4: : remove 3 [3] → 2: : 4: : [] = [2; 4] Ø Определите функцию для удаления только первого вхождения элемента; для генерации списка всех возможных удалений одного вхождения элемента] Мар. ГТУ, 2012 19
Другие полезные функции Ø nth – взятие n-го элемента списка Ø concat – объединение списка списков в один список Мар. ГТУ, 2012 20
Функции высшего порядка Мар. ГТУ, 2012 21
Итерация let rec iter f = function [] -> () | h: : t -> (f h); iter f t; ; Ø Функции с суффиксами -i, -2 и -i 2 доступны для многих библиотечных функций ü -i - функции-обработчику передается номер элемента ü -2 – функция работает с двумя параллельными списками Ø List. iteri (fun n x -> printf "%d. %sn" (n+1) x) ["One"; "Two"; "Three"]; ; Ø List. iter 2 (fun n x -> printf "%d. %sn" n x) [1; 2; 3] ["One"; "Two"; "Three"]; ; Мар. ГТУ, 2012 22
Нахождение элемента Ø find_index; find_indexi ü если элемент не находится, то происходит исключение Ø tryfind_index; tryfind_indexi type person = string*int; ; let plist = [("Vasya", 123); ("Petya", 234)]; ; let find_name no = List. tryfind (fun (name, num) -> num=no) plist; ; match (find_name 123) with None -> "No person found" | Some((name, num)) -> "The person is "+name; ; Мар. ГТУ, 2012 23
Применение функции к элементам списка let rec map f = function [] -> [] | h: : t -> (f h): : map f t; ; Ø map (fun x->2*x) [1; 2; 3] = [2; 4; 6] Ø mapi (fun i x -> i*x) [1; 2; 3] = [0; 2; 6] Ø map (fun (s: string)->s. Length) ["One"; "Two"; "Three"] = [3; 3; 5] Ø ["One"; "Two"; "Three"] |> map (fun s->s. Length) Мар. ГТУ, 2012 24
Фильтрация списка let rec filter p = function [] -> [] | x: : t when p(x) -> x: : filter p t | x: : t -> filter p t; ; let sp 1 = [7; 4; 3; 6; 8] let sp 2 = List. filter(fun (nm) -> nm <= 4) sp 1 printf "sp 2 = %A nn" sp 2; Результат: [4; 3] Мар. ГТУ, 2012 25
Сворачивание списка Ø let sum = fold (fun ac x -> ac+x) 0; ; Ø let prod = fold (fun ac x -> ac*x) 1; ; Ø let sum. By. Int f = fold (fun ac x ->ac*(f x)) 0; ; Ø let sum = reduce (fun u v -> u+v); ; Мар. ГТУ, 2012 26
Логические функции Ø let exists p = fold_left (fun a x -> a or x) false Ø let for_all p = fold_left (fun a x -> a and x) true Мар. ГТУ, 2012 27
Другие полезные функции Ø zip / unzip (zip 3/unzip 3) – объединить два (три) списка в список пар (троек) и наоборот Ø assoc a L = snd (find (fun (u, v) -> u=a) L) Ø choose Ø sort / stable_sort – сортировка Ø partition – разбиение на 2 списка в зависимости от предиката Ø scan_left, scan_right – поэлементная обработка списка (map) с аккумулятором Мар. ГТУ, 2012 28
Пример использования библиотечных функций Ø permute L – генерация списка всех перестановок списка L Ø permute [1; 2; 3] = [[1; 2; 3]; [1; 3; 2]; …; [3; 2; 1]] Ø Декомпозиция задачи: ü Рекурсия: как свести задачу к меньшей размерности? ü Отделяем голову, получаем все перестановки хвоста, затем ставим голову на все возможные позиции в этих перестановках ü Задача сводится к функции insertions, генерирующей список возможных «вставлений» элемента в список Мар. ГТУ, 2012 29
Генерация вставления Ø insertions 3 [1; 2] = [[3; 1; 2]; [1; 3; 2]; [1; 2; 3]] Ø Для пустого списка – одно возможное решение [[x]] Ø Для списка h: : t ü Вставляем всеми способами в t ü К этим спискам приписываем h в начало ü Добавляем решение – вставка в начало let rec insertions x = function [] -> [[x]] | h: : t -> (x: : h: : t): : (map (fun z -> h: : z) (insertions x t)); ; Мар. ГТУ, 2012 30
Перестановки Ø Для пустого списка – [[]] ü На произвольном шаге ü Получаем список перестановок хвоста ü Каждую из перестановок надо раскрыть в список вставок, получившиеся списки объединить ü Для этого подходит функция map_concat let rec permute L = match L with [] -> [[]] | h: : t -> map_concat (fun z -> insertions h z) (permute t); ; Мар. ГТУ, 2012 31
Еще один способ вычисления факториала Ø let fact n = length (permute [1. . n]); ; Мар. ГТУ, 2012 32
Списковое представление Ø type image = char list; ; Ø let mklist (s: string) = [ for c in s. To. Char. Array() -> c ]; ; let sample = [ mklist ". . . "; mklist ". . . ##. . . "; mklist ". . . . . ##. . . . "; mklist ". . #. ##. . . "; mklist ". . . ##. . "; mklist ". . ##. "; mklist ". . . "]; ; Мар. ГТУ, 2012 33
Генерация элементов списка (list comprehension) Ø List. init 9 (fun x -> 2. 0**float(x))= [1; 2; 4; 8; 16; 32; 64; 128; 256] Ø [ for x in 0. . 8 -> 2. 0**float(x)] Ø [ 1. . 10 ] Ø [ 1. . 2. . 10 ] = [1; 3; 5; 7; 9] Ø [ for x in 1. . 10 when x%2=0 for y in 1. . 10 when y%2=0 -> (x, y) ]; ; Ø List comprehension можно использовать вместо map: ü [for x in L -> x*2] ü map (fun x->x*2) L Мар. ГТУ, 2012 34
Хвостовая рекурсия. Приведение к ней Мар. ГТУ, 2012 35
Хвостовая рекурсия Ø Во многих случаях (особенно при обработке списков) рекурсивные алгоритмы могут быть сведены к итерационным Ø Такая рекурсия называется хвостовой ü Линейная ü Рекурсивный вызов – в конце тела функции Øт. е. вызов совершается сразу после выполнения тела функции ü Не выделяется промежуточная память Мар. ГТУ, 2012 36
length let rec length = function [] -> 0 | _: : t -> 1+length(t); ; Ø После рекурсивного вызова выполняется операция сложения Ø При каждом погружении выделяется память на промежуточные вычисления и на адрес возврата Ø В случае итерационного алгоритма выделение памяти бы не потребовалось Мар. ГТУ, 2012 37
Приведение к хвостовой рекурсии Ø Вводим промежуточную функцию len : int → T list → int Ø При каждом откусывании головы числовой параметр (разностный счетчик) увеличивается, затем делается рекурсивный вызов Ø В конце значение счетчика возвращается как результат let length L = let rec len a = function [] -> a | _: : t -> len (a+1) t in len 0 L; ; Мар. ГТУ, 2012 38
Хвостовая рекурсия let rec len a = function [] -> a | _: : t -> len (a+1) t Ø Сложение выполняется перед рекурсивным вызовом Ø Результат возвращается напрямую в вызывающую функцию => не требуется выделения памяти Мар. ГТУ, 2012 39
Реверсирование списка let rec rev = function [] -> [] | h: : t -> (rev t)@[h]; ; Ø Не хвостовая рекурсия Ø Сложность: порядка n*n, поскольку append имеет линейную сложность Мар. ГТУ, 2012 40
Приведение к хвостовой рекурсии Ø Вводим аналог счетчика – списковый Ø Начиная с [ ], каждое откусывание головы присоединяется к этому списку let rev L = let rec rv s = function [] -> s | h: : t -> rv (h: : s) t in rv [] L; ; Мар. ГТУ, 2012 41
Порядковое представление списков Ø Возможно предложить другое, более «функциональное» определения списка как последовательности чисел list : int -> A type 'a nlist = int->'a option ; ; let nhd l = l 0; ; let ntl l = fun x -> if x>=0 then l (x+1) else None; ; let nempty l = (l 0) = None; ; let mempty = fun _ -> None; ; let ncons a l = fun x -> (if x=0 then Some(a) else l(x-1)); ; Мар. ГТУ, 2012 42
Например let rec to_list l = if nempty l then [] else (nhd l): : to_list (ntl l) let rec from_list = function [] -> mempty | h: : t -> ncons h (from_list t) let rec map f l = match nhd l with None -> mempty | Some(x) -> ncons (f x) (map f (ntl l)); ; Ø Порядковое представление неэффективно (представление функции в памяти), но любопытно с теоретической точки зрения Ø Может использоваться для представления матриц (в особенности разреженных) Мар. ГТУ, 2012 43
Матрицы. Массивы Мар. ГТУ, 2012 44
Представление матриц Ø Списком списков type ‘a matrix = ‘a list; ; Ø Порядковое представление type ‘a rmatrix = int->’a option; ; Ø Удобно для разреженных матриц, при этом надо отдельно хранить размерность: type ‘a rmatrix = int*(int->’a option); ; Мар. ГТУ, 2012 45
Массивы Ø F# – частично-императивный язык, содержит mutable-типы данных Ø Тип массив во многом напоминает список, но является mutable: ü Операции не возвращают новый массив, а модифицируют исходный Ø 2 вида многомерных массивов: ü Jagged (непрямоугольный) и regular ü Представление матриц и векторов Мар. ГТУ, 2012 46
Пример загрузка данных из файла Мар. ГТУ, 2012 47
Пример загрузка данных из файла let img 2 = new Bitmap( @"d: image. bmp"); let img 3 = new Bitmap(400, 400) let n = 400; for i=0 to n-1 do for j=0 to n-1 do let color = img 2. Get. Pixel(i, j); if color. Is. Empty = false && color. A > (byte) 20 && color. R = (byte) 0 && color. G = (byte) 0 && color. B = (byte) 0 then img 3. Set. Pixel(i, j, Color. Blue); else img 3. Set. Pixel(i, j, Color. Floral. White); img 3. Save(@"d: image 2. bmp") Мар. ГТУ, 2012 48
Итог Ø Рекурсивные структуры данных – часто используемый удобный механизм хранения данных в функциональном программировании Ø Списки – гармоничная часть языка ü Прозрачный синтаксис (специальный синтаксис конструкторов) ü Поддержка со стороны библиотеки Ø Множество абстрактных функций для обработки списков позволяет, комбинируя их, легко реализовывать различные алгоритмы обработки Мар. ГТУ, 2012 49
Спасибо за внимание! Вопросы? Мар. ГТУ, 2012 50