Массивы в языке F# представляет коллекцию с фиксированным колличеством элементов одного типа, которые можно изменять.
Для определения массива применяются квадратные скобки и вертикальные черточки [||]:
let people = [||]
Здеь определен пустой массив нулевой длины, который называется people
.
При создании массива мы можем инициализировать его элементами. Элементы помещаются между символами [| и |]. Если элементы размещаются на одной строке, то они отделяется друг от друга точкой с запятой:
let people = [|"Tom"; "Sam"; "Bob"|]
В данном случае массив people
состоит из пяти элементов типа string
. Важно, что все элементы представляют один и тот же тип.
Если элементы размещены по отдельности на разных строках, тогда не требуется разделять их точкой с запятой:
let people = [| "Tom" "Sam" "Bob" |]
Стоит отметить, что для вывода массива F# предоставляет специальный спецификатор %A:
let people = [|"Tom"; "Sam"; "Bob"|] printfn "%A" people
По умолчанию тип элементов массива выводится из значений, которыми инициализируется массив. Но также можно указать тип массива явным образом:
let people: string array = [|"Tom"; "Sam"; "Bob"|]
Выражение типа string array
указывает, что значение представляет массив (array) и этот массив содержит значения типа string.
Можно определить элементы массива с помощью оператора массива ..
:
let numbers = [| 1..5 |] // [|1; 2; 3; 4; 5|]
Можно сгенерировать элементы с помощью цикла:
let squares = [| for i in 1..5 -> i * i |] // [|1; 4; 9; 16; 25|]
Также для создания массива можно применять ряд специальных функций. Array.create создает массив указанного размера и присваивает всем элементам определенное значение:
let numbers = Array.create 5 1 // 5 чисел, каждое из которых равно 1 printfn "%A" numbers // [|1; 1; 1; 1; 1|]
Функция Array.init создает массив заданного размера с помощью функции генерации элементов:
let numbers = Array.init 5 (fun i -> i * i) // 5 квадратов чисел от 1 до 5 printfn "%A" numbers // [|0; 1; 4; 9; 16|]
Array.zeroCreate создает массив, в котором все элементы инициализируются значением по умолчанию для типа массива:
let numbers: int array = Array.zeroCreate 5 // 5 чисел равных 0 printfn "%A" numbers // [|0; 0; 0; 0; 0|]
Стоит отметить, что в этом случае нам надо явным образом указать тип элементов массива - int array
при определении значения.
Каждый элемент в массиве имеет индекс - порядковый номер, который отсчитывается с нуля (то есть первый элемент имеет индекс 0, второй элемент- индекс 1 и так далее). Для обращения к элементу по индексу применяются квадратные скобки, в которые передается индекс элемента:
let people = [|"Tom"; "Sam"; "Bob"|] let person = people[1] // получаем элемент с индексом 1 - "Sam" printfn "%s" person
В отличие от других типов коллекций мы можем изменить значение элемента массива:
let people = [|"Tom"; "Sam"; "Bob"|] printfn "%s" people[1] // Sam people[1] <- "Alex" // изменяем значение по индексу 1 printfn "%s" people[1] // Alex
Оператор последовательности .. позволяет обратиться к определенной части массива:
let people = [|"Tom"; "Sam"; "Bob"; "Alice"; "Mike"|] let people1 = people[1..3] // [|"Sam"; "Bob"; "Alice"|] printfn "%A" people1 let people2 = people[..2] // [|"Tom"; "Sam"; "Bob"|] printfn "%A" people2 let people3 = people[2..] // [|"Bob"; "Alice"; "Mike"|] printfn "%A" people3
Массивы имеют свойство Length, которое хранит количество элементов:
Применение:
let people = [|"Tom"; "Sam"; "Bob"|] printfn "Количество элементов: %d" (people.Length) // 3
Модуль Array предоставляет ряд функций для выполнения различных операций над массивами. Рассмотрим основные из них.
Для перебора элементов массива можно использовать цикл for..in:
let people = [|"Tom"; "Sam"; "Bob"|] for person in people do printfn "%s" person
Также для перебора массива можно использовать функцию Array.iter. Эта функция принимает два параметра. Первый параметр - функция, которая выполняется для каждого элемента массива. Второй параметр - сам перебираемый массив:
let people = [|"Tom"; "Bob"; "Alice"; "Mike"; "Sam"|] Array.iter (fun p -> printf "%s " p) people
Здесь функция fun p -> printf "%s " p
получает каждый элемент массива people через параметр и выводит его на консоль
Функция Array.copy создает новый массив, в который копируются элементы из существующего массива. Причем копия массива является поверхностной копией. Это означает, что если тип элемента является ссылочным типом, копируется только ссылка, а не базовый объект:
let people = [|"Tom"; "Sam"; "Bob"|] let students = Array.copy people
Array.sub генерирует новый массив из подмассива. Для этого в функцию передается подмассив, начальный индекс в нем и количество элементов, которые надо выбрать из подмассива с указанного индекса:
let people = [|"Tom"; "Sam"; "Bob"; "Alice"; "Mike"|] let people1= Array.sub people 1 3 // [|"Sam"; "Bob"; "Alice"|]
Функция Array.concat объединяет последовательность массивов:
let employees = [|"Tom"; "Sam"; "Bob"|] let students = [|"Alice"; "Mike"|] let people= Array.concat [employees; students; [|"Alex"; "Kate"|]] printfn "%A" people // [|"Tom"; "Sam"; "Bob"; "Alice"; "Mike"; "Alex"; "Kate"|]
Функция Array.rev возвращает новый массив, где элементы исходного массива расположены в обратном порядке:
let people = [|"Tom"; "Sam"; "Bob"|] let reversed= Array.rev people printfn "%A" reversed // [|"Bob"; "Sam"; "Tom"|]
С помощью функции Array.filter можно отфильтровать элементы массива в сответствии с некоторым условием. Функция возвращает новый массив, которая содержит только те элементы, которые соответствуют этому условию.
Первый параметр функции - функция условия, а второй параметр - фильтруемый массив
let people = [|"Tom"; "Alice"; "Sam"; "Kate"; "Bob"|] let result = Array.filter (fun (p:string) -> p.Length = 3) people printfn "%A" people // [|"Tom"; "Sam"; "Bob"|]
В данном случае получаем в новый массив из исходного массива все строки, длина которых равна 3 символам.
По умолчанию создаваемые в F# массивы одномерны, то есть их можно представить как ряд элементов. Но также F# поддерживает двухмерные массивы. Для их создания применяется оператор array2D:
let table = array2D [ [ 0; 1; 2]; [3; 0; 4]; [5; 6; 0] ]
Оператору array2D передается последовательность списков или массивов. Фигурально такой массив можно изобразить как таблицу. Так, в данном случае массив table содержит три вложенных списка, которые можно отождествлять со строками таблицы:
0 | 1 | 2 |
3 | 0 | 4 |
5 | 6 | 0 |
Также можно использовать функцию Array2D.init для инициализации двухмерных массивов. В качестве параметра она принимает количество строк, количество столбцов и функцию генерации значений:
let table = Array2D.init 2 3 (fun i j -> 1) printfn "%A" table
Здесь создается двухмерный массив, в котором 2 строки и 3 столбца. Функция генерации значений принимает текущий номер строки (i) и текущий номер столбца (j) и возвращает значение для ячейки на пересечении этой строки и столбца. В примере выше просто возвращает 1, поэтому генерируемый массив будет выглядеть так:
[[1; 1; 1] [1; 1; 1]]
Используем номера строк и столбцов при генерации значения. Например, будем возвращать сумму номера строки и номера столбца:
let table = Array2D.init 2 3 (fun i j -> i+j)
В итоге будет сгенерирован следующий массив:
[[0; 1; 2] [1; 2; 3]]
Для создания двумерного массива с элементами, которым присвоено начальное значение, также применяется функцию Array2D.create. Она принимает количество строк, столбцов и начальное значение для всех элементов:
let table = Array2D.create 2 3 8
Здесь создается массив из 2 строк и 3 столбцов, где все элементы равны 8:
[[8; 8; 8] [8; 8; 8]]
Для обращения к элементам двухмерных массивов используются два индекса - номер строки и столбца:
let table = array2D [ [ 0; 1; 2]; [3; 0; 4]; [5; 6; 0] ] // получаем элемент двухмерного массива из 2-й строки, 3-го столбца printfn "%d" (table[1, 2]) // 4 // присваиваем новое значение table[1, 2] >- 22 printfn "%d" (table[1, 2]) // 22
В данном случае с помощью записи table[1, 2]
обращаемся к столбцу с индексом 2 в строке с индексом 1. Поскольку индексация начинается с нуля, тогда получится,
что мы обращаемся к элементу, который находится на пересечении 2-й строки и 3-му столбца.
F# также предоставляет аналогичные функции для создания трехмерных и четырехмерных массивов с помощью типов Array3D и Array4D, которые имеют 3 и 4 размерности. Например, классическим примером трехмерного массива является набор точек в пространстве, которые имеют три координаты X, Y и Z. Определим и выведем на консоль трехмерный массив:
let points = Array3D.init 1 2 3 (fun i j k -> i+j+k) for i = 0 to (Array3D.length1 points) - 1 do for j = 0 to (Array3D.length2 points)-1 do printfn "X=%d Y=%d Z=%d" (points[i, j, 0]) (points[i, j, 1]) (points[i, j, 2])
Для определения трехмерного массива применяется функция Array3D.init
, которая принимает три размерности. Для генерации значений просто складываем текущее значение размерностей.
В итоге мы получим структуру наподобие
[ [ [0; 1; 2] [1; 2; 3] ] ]
Чтобы получить значения отдельных размерностей применяются функции Array3D.length1
/Array3D.length2
/Array3D.length3
,
где число после "length" - это номер размерности. Используя эти функции, мы можем пройтись по многомерному массиву.
А чтобы обратиться к значению трехмерного массива, надо использовать три индекса - для каждой размерности, например, points[i, j, 1]
Ряд функций позволяют проверить соответствия элементов массива некоторому условию:
Array.exists: проверяет, есть ли в массиве элементы, удовлетворяют некоторому условию. Если такие имеются,
тогда возвращает true
, иначе возвращается false
.
Первый параметр функции - функция условия, а второй параметр - массив для поиска
let people = [|"Tom"; "Sam"; "Bob"|] let result1 = Array.exists (fun p -> p = "Sam") people printfn $"result1 = {result1}" // True let result2 = Array.exists (fun p -> p = "Alex") people printfn $"result2 = {result2}" // False
В первом случае применяется условие fun p -> p = "Sam"
, которое проверяет значение элемента, что элемент равен строке "Sam". Во втором случае проверяем на равенство строке
"Alex". И если хотя бы один элемент с таким значением присутствует в массиве, то возвращается true.
Array.forall: также проверяет, есть ли в массиве элементы, удовлетворяют некоторому условию, только возвращает true
, если все элементы
соответствуют этому условию
let people = [|"Tom"; "Sam"; "Bob"|] let result1 = Array.forall (fun p -> p = "Sam") people printfn $"result1 = {result1}" // False let result2 = Array.forall (fun (p:string) -> p.Length=3) people printfn $"result2 = {result2}" // True
Во втором случае проверяем, все ли элементы имеют длину в 3 символа. Поскольку обращаемся к свойству объекта string, то явным образом типизируем параметр условия типом string.
Ряд функций выполняют поиск элемента в массиве. Функция Array.tryFind возвращает из массива первый элемент, который соответствует некоторому условию. Поскольку такого элемента может и не быть в массиве,
то возвращается в реальности не сам элемент, а его обертка в виде объекта Option
. С помощью свойства Value можно получить значение.
let people = [|"Tom"; "Sam"; "Bob"|] let printOption option = match option with | Some value -> printfn $"{value}" | None -> printfn "Не найдено" let result1 = Array.tryFind (fun (p:string) -> p.StartsWith("T")) people printOption result1 // Tom let result2 = Array.tryFind (fun (p:string) -> p.EndsWith("e")) people printOption result2 // Не найдено
В первом случае ищем в массиве элемент, который начинается на букву "T" и для этого применяем метод StartsWith()
. Во втором случае ищем элемент,
который заканчивается на букву "e" и для этого используем метод EndsWith()
. Оба метода определены у типа string.
Для вывода результата определяем функцию printOption, которая получает объект Option и с помощью конструкции match сопоставляет его с двумя паттернами - Some и None. Если элемент найден, тогда объект option успешно сопоставляется с Some, а его значение помещается в переменную value:
match option with | Some value -> printfn $"{value}"
Для массивов также доступна функция Array.find, которая также возвращает первый элемент, который соответствует по критерию. Но в отличие от
Array.tryFind()
возвращается непосредственно сам элемент:
let people = [|"Tom"; "Sam"; "Bob"|] let result1 = Array.find (fun (p:string) -> p.StartsWith("T")) people printfn $"{result1}" // Tom
Однако если в массиве нет элементов, которые соответствуют критерию, то генерируется ошибка. Поэтому функция Array.tryFind()
может представлять более предпочтительный способ поиска.
Array.tryFindIndex: возвращает из массива индекс первого элемента, который соответствует некоторому условию.
Работает аналогично функции tryFind()
- поскольку такого элемента может и не быть в массиве,
то возвращается не сам индекс элемента, а его обертка в виде объекта Option
. С помощью свойства Value можно получить значение.
let people = [|"Tom"; "Sam"; "Bob"|] let printOption option = match option with | Some value -> printfn $"Index: {value}" | None -> printfn "Не найдено" let result1 = Array.tryFindIndex (fun (p:string) -> p.StartsWith("T")) people printOption result1 // Index: 0 let result2 = Array.tryFindIndex (fun (p:string) -> p.EndsWith("e")) people printOption result2 // Не найдено
Стоит не забывать, что индексация элементов начинается с нуля.
Ряд функций выполняют сортировку элементов спика. Например, функция Array.sort сортирует элементы по возрастанию, а Array.sortDescending - по убыванию:
let people = [|"Tom"; "Sam"; "Bob"; "Alice"; "Mike"|] let sortedPeople = Array.sort people // сортировка по возрастанию for p in sortedPeople do printf $"{p} " // Alice Bob Mike Sam Tom printfn "" let sortedPeopleDesc = Array.sortDescending people // сортировка по убыванию for p in sortedPeopleDesc do printf $"{p} " // Tom Sam Mike Bob Alice
Стоит отметить, что сортируемые значения должны реализовать интерфейс System.IComparable, в частности, его функцию System.IComparable.CompareTo()
,
которая применяется при сравнении. Эта функция сравнивает текущий элемент с параметром и возвращает число. Если текущий элемент должен стоять перед значением из параметра,
то возвращается отрицательное значение. Если текущий элемент должен находиться после элемента из параметра, то должно возвращаться положительное значение. Например:
type Person(name:string, age:int) = member this.Name = name member this.Age = age interface System.IComparable with member this.CompareTo obj = let person = obj :?> Person // преобразуем к типу Person this.Age - person.Age // сравниваем по возрасту let people = [| Person("Tom", 39); Person("Sam", 28); Person("Bob", 43); Person("Alice", 34); Person("Mike", 31)|] let sortedPeople = Array.sort people for p in sortedPeople do printf $"{p.Name}({p.Age}) " // Sam(28) Mike(31) Alice(34) Tom(39) Bob(43)
Здесь реализуем функцию CompareTo
для класса Person. В эту функцию передается объект, который сравнивается с текущим. В данном случае выполняем сравнение по возрасту - свойству Age.
Причем метод получает значение типа Object, поэтому нам надо его преобразовать к типу Person, чтобы произвести сравение.
let person = obj :?> Person // преобразуем к типу Person
Затем формируем результат метода:
this.Age - person.Age // сравниваем по возрасту
То есть если возраст текущего объекта больше, чем возраст объекта, который передается через параметр, то возвращается положительное число, и поэтому текущий объект будет стоять после объекта-параметра.
Функция Array.sortBy() принимает функцию, которая возвращает значение-критерий сортировки. Особенно полезна эта функция при сравнении комплексных объектов. Например:
type Person(name:string, age:int) = member this.Name = name member this.Age = age let people = [| Person("Tom", 39); Person("Sam", 28); Person("Bob", 43); Person("Alice", 34); Person("Mike", 31)|] // сортировка по имени let sortedByName = Array.sortBy (fun (p:Person) -> p.Name) people for p in sortedByName do printf $"{p.Name} " // Alice Bob Mike Sam Tom printfn "" // сортировка по возрасту let sortedByAge = Array.sortBy (fun (p:Person) -> p.Age) people for p in sortedByAge do printf $"{p.Name}({p.Age}) " // Sam(28) Mike(31) Alice(34) Tom(39) Bob(43)
Здесь сортируется массив объектов Person, которые состоят из двух свойств - Name и Age. В первом случае сортируем по имени - свойству Name, поэтому для определения критерия сортировки используем функцию:
fun (p:Person) -> p.Name
В эту функцию передается объект массива, и у него выбирается значение свойства Name.
Во втором случае аналогично сортируем по имени.
Еще одна функция - Array.sortWith() в качестве первого параметра принимает функцию сравнения и в качестве второго - сортируемый массив:
type Person(name:string, age:int) = member this.Name = name member this.Age = age let people = [| Person("Tom", 39); Person("Sam", 28); Person("Bob", 43); Person("Alice", 34); Person("Mike", 31)|] let comparePeople (person1:Person) (person2:Person) = if person1.Name > person2.Name then 1 elif person1.Name < person2.Name then -1 else 0 let sortedPeople = Array.sortWith comparePeople people for p in sortedPeople do printf $"{p.Name} " // Alice Bob Mike Sam Tom
Здесь применяется функция comparePeople, которая принимает два объекта Person и сравнивает их по имени.
Для массива доступен ряд функций, которые выполняют агрегатные операции над массивом:
sum: вычисляет сумму элементов
min: вычисляет минимальное значение
max: вычисляет максимальное значение
average: вычисляет среднее значение (элементы должны представлять тип float)
Применение:
let numbers = [|1f; 2f; 3f; 4f; 5f|] printfn "Sum: %.1f" (Array.sum numbers) // Sum: 15.0 printfn "Min: %.1f" (Array.min numbers) // Min: 1.0 printfn "Max: %.1f" (Array.max numbers) // Max: 5.0 printfn "Average: %.1f" (Array.average numbers) // Average: 3.0
Двойники этих функций с суффиксом By
в качестве первого параметра принимают функцию, которая определяет критерий для вычисления значения:
sumBy
minBy
maxBy
averageBy
countBy (вычисляет количество элементов, которые соответствуют некоторому критерию)
Например:
let people = [| ("Tom", 39); ("Bob", 43); ("Sam", 28)|] let projection = fun (name, age) -> age let sum = Array.sumBy projection people printfn $"Сумма возрастов: {sum}" let min = Array.minBy projection people printfn $"С мин. возрастом: {min}" let max = Array.maxBy projection people printfn $"С макс. возрастом: {max}" let average = Array.averageBy (fun (name, age) -> float age) people printfn "Средний возраст: %.1f" average let result = Array.countBy (fun (name, age) -> age > 30) people printfn $"Больше 30 лет: {result}"
Здесь имеется массив people, где каждый элемент представляет кортеж из двух значений. Будем условно считать, что каждый такой кортеж представляет пользователя, где первый элемент - имя, а второй - возраст. В качестве функции-проекции, которая определяет критерий подсчета, определена функция projection:
let projection = fun (name, age) -> age
Она получает в качестве параметра - элемент массива - кортеж из двух элементов и возвращает второе значение из кортежа. То есть вычисления будем производить относительно возраста. Далее применяем эту функцию в функциях
Array.sumBy/Array.minBy/Array.maxBy. Функция Array.sumBy
возращает сумму (в данном случае сумму возрастов), Array.minBy - элемент массива с минимальным значением,
а Array.maxBy - элемент с максимальным значением.
Array.averageBy возвращает среднее значение, но это значение вычисляется на наборе чисел float, поэтому преобразуем возраст в значение float:
let average = Array.averageBy (fun (name, age) -> float age) people
Array.countBy
возвращает две группы элементов - массив из двух кортежей, где один кортеж содержит количество элементов, которые соответствуют
условию, а другой кортеж - сколько не соответствуют условию (в качестве условия ищем количество элементов, у которых возраст больше 30):
let result = Array.countBy (fun (name, age) -> age > 30) people
Консольный вывод:
Сумма возрастов: 110 С мин. возрастом: (Sam, 28) С макс. возрастом: (Bob, 43) Средний возраст: 36.7 Больше 30 лет: [|(True, 2); (False, 1)|]
Ряд функций позволяют получить подмассив. Функции Array.take и Array.truncate создают новый массив, который содержит определенное количество элементов исходного массива. Первый параметр функции - количество элементов, а второй параметр - массив. Извлечение элементов идет с начала массива
let people = [|"Tom"; "Alice"; "Bob"; "Mike"; "Sam"|] let items = Array.take 3 people for item in items do printf $"{item} \t" // Tom Alice Bob printfn "" let items2 = Array.truncate 3 people for item in items2 do printf $"{item} \t" // Tom Alice Bob
Здесь извлекаем три первых элемента. Обе функции работают похожим образом за тем исключением, что функция Array.take генерирует
исключение System.InvalidOperationException
, если количество извлекаемых элементов больше количество элементов массива.
Функция Array.skip пропускает определенное количество элементов с начала массива.
let people = [|"Tom"; "Alice"; "Bob"; "Mike"; "Sam"|] let items = Array.skip 2 people // пропускаем первых 2 элемента for item in items do printf $"{item} \t" // Bob Mike Sam
Функция Array.skipWhile пропускает определенное количество элементов с начала массива, которые соответствуют условию:
let people = [|"Tom"; "Bob"; "Alice"; "Mike"; "Sam"|] let items = Array.skipWhile (fun (p:string) -> p.Length < 4) people for item in items do printf $"{item} \t" // Alice Mike Sam
Здесь пропускаем сначала строки, длина которых меньше 4 символов. Строки пропускаются только сначала, пока не дойдем до строки, которая не соответствует условию.
Функция Array.takeWhile оставляет определенное количество элементов с начала массива, которые соответствуют условию:
let people = [|"Tom"; "Bob"; "Alice"; "Mike"; "Sam"|] let items = Array.takeWhile (fun (p:string) -> p.Length < 4) people for item in items do printf $"{item} \t" // Tom Bob
Здесь добавляем в новый массив строки, длина которых меньше 4 символов. Строки добавляются только сначала, пока не дойдем до строки, которая не соответствует условию.
Функция Array.map позволяет преобразовать массив. Первый параметр - функция преобразования, которая применяется к каждому элементу, а второй параметр - исходный массив. Результат функции Array.map - новый массив из преобразованных элементов:
let people = [|("Tom", 39); ("Bob", 43); ("Alice", 34); ("Mike", 31); ("Sam", 28)|] let items = Array.map (fun (name, age) -> name) people for item in items do printf $"{item} \t"
В данном случае функция преобразования fun (name, age) -> name
получает каждый элемент массива в виде кортежа из двух значений - name и age и
возвращает значение name. Соответственно из массива кортежей, где каждый элемент хранит строку и число, получим массив из строк.