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