Массивы

Последнее обновление: 14.12.2023

Массивы в языке 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 содержит три вложенных списка, которые можно отождествлять со строками таблицы:

012
304
560

Также можно использовать функцию 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               // сравниваем по возрасту

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

sortBy

Функция 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.

Во втором случае аналогично сортируем по имени.

sortWith

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

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850