Массивы (F#)

Массивы — это изменяемые коллекции последовательных элементов данных фиксированного размера с отсчитываемыми от нуля коллекциями одного типа.

Создание массивов

Массивы можно создавать несколькими способами. Можно создать небольшой массив, перечислив последовательные значения между [| и |] и , разделенные точкой с запятой, как показано в следующих примерах.

let array1 = [| 1; 2; 3 |]

Вы также можете поместить каждый элемент в отдельную строку. В этом случае разделитель точки с запятой является необязательным.

let array1 =
    [|
        1
        2
        3
     |]

Тип элементов массива определяется на основе используемых литерал и должен быть согласованным. Следующий код вызывает ошибку, так как 1.0 является числом с плавающей точкой, а 2 и 3 — целыми числами.

// Causes an error.
// let array2 = [| 1.0; 2; 3 |]

Выражения последовательности также можно использовать для создания массивов. Ниже приведен пример, который создает массив квадратов целых чисел от 1 до 10.

let array3 = [| for i in 1 .. 10 -> i * i |]

Чтобы создать массив, в котором все элементы инициализируются равным нулю, используйте .Array.zeroCreate

let arrayOfTenZeroes : int array = Array.zeroCreate 10

Элементы Access

Доступ к элементам массива можно получить с помощью скобок ([ и ]). Исходный синтаксис точек (.[index]) по-прежнему поддерживается, но больше не рекомендуется в F# 6.0.

array1[0]

Индексы массива начинаются с 0.

Вы также можете получить доступ к элементам массива с помощью нотации среза, которая позволяет указать поддиа запись массива. Ниже приведены примеры нотации среза.

// Accesses elements from 0 to 2.

array1[0..2]

// Accesses elements from the beginning of the array to 2.

array1[..2]

// Accesses elements from 2 to the end of the array.

array1[2..]

При использовании нотации среза создается новая копия массива.

Типы массивов и модули

Тип всех массивов F# является типом платформа .NET Framework System.Array. Таким образом, массивы F# поддерживают все функциональные возможности, доступные в System.Array.

МодульArray поддерживает операции с одномерными массивами. Модули Array2D, Array3Dи Array4D содержат функции, поддерживающие операции с массивами двух, трех и четырех измерений соответственно. Массивы рангов больше четырех можно создать с помощью System.Array.

Простые функции

Array.get получает элемент . Array.length задает длину массива. Array.set задает для элемента указанное значение. В следующем примере кода показано использование этих функций.

let array1 = Array.create 10 ""
for i in 0 .. array1.Length - 1 do
    Array.set array1 i (i.ToString())
for i in 0 .. array1.Length - 1 do
    printf "%s " (Array.get array1 i)

Выходные данные выглядят следующим образом.

0 1 2 3 4 5 6 7 8 9

Функции, создающие массивы

Несколько функций создают массивы без использования существующего массива. Array.empty создает новый массив, который не содержит никаких элементов. Array.create создает массив указанного размера и задает для всех элементов предоставленные значения. Array.init создает массив с учетом измерения и функции для создания элементов. Array.zeroCreate создает массив, в котором все элементы инициализируются нулевым значением для типа массива. Эти функции демонстрируются в следующем коде.

let myEmptyArray = Array.empty
printfn "Length of empty array: %d" myEmptyArray.Length



printfn "Array of floats set to 5.0: %A" (Array.create 10 5.0)


printfn "Array of squares: %A" (Array.init 10 (fun index -> index * index))

let (myZeroArray : float array) = Array.zeroCreate 10

Выходные данные выглядят следующим образом.

Length of empty array: 0
Area of floats set to 5.0: [|5.0; 5.0; 5.0; 5.0; 5.0; 5.0; 5.0; 5.0; 5.0; 5.0|]
Array of squares: [|0; 1; 4; 9; 16; 25; 36; 49; 64; 81|]

Array.copy создает новый массив, содержащий элементы, скопированные из существующего массива. Обратите внимание, что копия является неглубокой копией, что означает, что если тип элемента является ссылочным типом, копируется только ссылка, а не базовый объект. Это показано в следующем примере кода.

open System.Text

let firstArray : StringBuilder array = Array.init 3 (fun index -> new StringBuilder(""))
let secondArray = Array.copy firstArray
// Reset an element of the first array to a new value.
firstArray[0] <- new StringBuilder("Test1")
// Change an element of the first array.
firstArray[1].Insert(0, "Test2") |> ignore
printfn "%A" firstArray
printfn "%A" secondArray

Результат предыдущего кода выглядит следующим образом:

[|Test1; Test2; |]
[|; Test2; |]

Строка Test1 отображается только в первом массиве, так как операция создания нового элемента перезаписывает ссылку в , firstArray но не влияет на исходную ссылку на пустую строку, которая все еще присутствует в secondArray. Строка Test2 отображается в обоих массивах, так как Insert операция с типом System.Text.StringBuilder влияет на базовый System.Text.StringBuilder объект, на который ссылается оба массива.

Array.sub создает новый массив из поддиапсеза массива. Чтобы указать поддиалог, укажите начальный индекс и длину. В следующем коде показано использование функции Array.sub.

let a1 = [| 0 .. 99 |]
let a2 = Array.sub a1 5 10
printfn "%A" a2

В выходных данных показано, что вложенный массив начинается с элемента 5 и содержит 10 элементов.

[|5; 6; 7; 8; 9; 10; 11; 12; 13; 14|]

Array.append создает новый массив, объединяя два существующих массива.

В следующем коде показан параметр Array.append.

printfn "%A" (Array.append [| 1; 2; 3|] [| 4; 5; 6|])

Выходные данные предыдущего кода выводятся следующим образом.

[|1; 2; 3; 4; 5; 6|]

Array.choose выбирает элементы массива для включения в новый массив. В следующем коде демонстрируется Array.choose. Обратите внимание, что тип элемента массива не обязательно должен соответствовать типу значения, возвращаемого в типе параметра. В этом примере тип элемента имеет int тип , а параметр является результатом полиномиальной функции elem*elem - 1в виде числа с плавающей запятой.

printfn "%A" (Array.choose (fun elem -> if elem % 2 = 0 then
                                            Some(float (elem*elem - 1))
                                        else
                                            None) [| 1 .. 10 |])

Выходные данные предыдущего кода выводятся следующим образом.

[|3.0; 15.0; 35.0; 63.0; 99.0|]

Array.collect Выполняет указанную функцию для каждого элемента массива существующего массива, а затем собирает элементы, созданные функцией, и объединяет их в новый массив. В следующем коде демонстрируется Array.collect.

printfn "%A" (Array.collect (fun elem -> [| 0 .. elem |]) [| 1; 5; 10|])

Выходные данные предыдущего кода выводятся следующим образом.

[|0; 1; 0; 1; 2; 3; 4; 5; 0; 1; 2; 3; 4; 5; 6; 7; 8; 9; 10|]

Array.concat принимает последовательность массивов и объединяет их в один массив. В следующем коде демонстрируется Array.concat.

Array.concat [ [|0..3|] ; [|4|] ]
//output [|0; 1; 2; 3; 4|]

Array.concat [| [|0..3|] ; [|4|] |]
//output [|0; 1; 2; 3; 4|]

Array.filter принимает логическую функцию условия и создает новый массив, содержащий только те элементы из входного массива, для которых условие имеет значение true. В следующем коде демонстрируется Array.filter.

printfn "%A" (Array.filter (fun elem -> elem % 2 = 0) [| 1 .. 10|])

Выходные данные предыдущего кода выводятся следующим образом.

[|2; 4; 6; 8; 10|]

Array.rev создает новый массив путем изменения порядка существующего массива. В следующем коде демонстрируется Array.rev.

let stringReverse (s: string) =
    System.String(Array.rev (s.ToCharArray()))

printfn "%A" (stringReverse("!dlrow olleH"))

Выходные данные предыдущего кода выводятся следующим образом.

"Hello world!"

В модуле массива можно легко объединять функции, которые преобразуют массивы с помощью оператора конвейера (|>), как показано в следующем примере.

[| 1 .. 10 |]
|> Array.filter (fun elem -> elem % 2 = 0)
|> Array.choose (fun elem -> if (elem <> 8) then Some(elem*elem) else None)
|> Array.rev
|> printfn "%A"

Выходные данные:

[|100; 36; 16; 4|]

Многомерные массивы

Можно создать многомерный массив, но синтаксис для записи литерала многомерного массива отсутствует. Используйте оператор array2D , чтобы создать массив из последовательности последовательностей элементов массива. Последовательности могут быть массивами или литералами списка. Например, следующий код создает двумерный массив.

let my2DArray = array2D [ [ 1; 0]; [0; 1] ]

Функцию Array2D.init можно также использовать для инициализации массивов двух измерений. Аналогичные функции доступны для массивов трех и четырех измерений. Эти функции принимают функцию, которая используется для создания элементов. Чтобы создать двумерный массив, содержащий элементы, для которых вместо указания функции задано начальное значение, используйте функцию Array2D.create , которая также доступна для массивов до четырех измерений. В следующем примере кода сначала показано, как создать массив массивов, содержащих нужные элементы, а затем использовать для Array2D.init создания требуемого двумерного массива.

let arrayOfArrays = [| [| 1.0; 0.0 |]; [|0.0; 1.0 |] |]
let twoDimensionalArray = Array2D.init 2 2 (fun i j -> arrayOfArrays[i][j])

Для массивов с рангом до 4 поддерживается синтаксис индексирования и срезов массивов. При указании индекса в нескольких измерениях для разделения индексов используются запятые, как показано в следующем примере кода.

twoDimensionalArray[0, 1] <- 1.0

Тип двумерного массива записывается как <type>[,] (например, int[,], double[,]), а тип трехмерного массива записывается как <type>[,,], и т. д. для массивов более высоких размеров.

Для многомерных массивов также доступно только подмножество функций, доступных для одномерных массивов.

Срезы массивов и многомерные массивы

В двумерном массиве (матрице) можно извлечь вложенную матрицу, указав диапазоны и используя подстановочный знак (*) для указания целых строк или столбцов.

// Get rows 1 to N from an NxM matrix (returns a matrix):
matrix[1.., *]

// Get rows 1 to 3 from a matrix (returns a matrix):
matrix[1..3, *]

// Get columns 1 to 3 from a matrix (returns a matrix):
matrix[*, 1..3]

// Get a 3x3 submatrix:
matrix[1..3, 1..3]

Многомерный массив можно разложить на подмножья одного или нижнего измерения. Например, можно получить вектор из матрицы, указав одну строку или столбец.

// Get row 3 from a matrix as a vector:
matrix[3, *]

// Get column 3 from a matrix as a vector:
matrix[*, 3]

Этот синтаксис среза можно использовать для типов, реализующих операторы доступа к элементам и перегруженные GetSlice методы. Например, следующий код создает тип Матрица, который создает оболочку для массива F# 2D, реализует свойство Item для поддержки индексирования массива и реализует три версии GetSlice. Если вы можете использовать этот код в качестве шаблона для типов матриц, можно использовать все операции срезов, которые описаны в этом разделе.

type Matrix<'T>(N: int, M: int) =
    let internalArray = Array2D.zeroCreate<'T> N M

    member this.Item
        with get(a: int, b: int) = internalArray[a, b]
        and set(a: int, b: int) (value:'T) = internalArray[a, b] <- value

    member this.GetSlice(rowStart: int option, rowFinish : int option, colStart: int option, colFinish : int option) =
        let rowStart =
            match rowStart with
            | Some(v) -> v
            | None -> 0
        let rowFinish =
            match rowFinish with
            | Some(v) -> v
            | None -> internalArray.GetLength(0) - 1
        let colStart =
            match colStart with
            | Some(v) -> v
            | None -> 0
        let colFinish =
            match colFinish with
            | Some(v) -> v
            | None -> internalArray.GetLength(1) - 1
        internalArray[rowStart..rowFinish, colStart..colFinish]

    member this.GetSlice(row: int, colStart: int option, colFinish: int option) =
        let colStart =
            match colStart with
            | Some(v) -> v
            | None -> 0
        let colFinish =
            match colFinish with
            | Some(v) -> v
            | None -> internalArray.GetLength(1) - 1
        internalArray[row, colStart..colFinish]

    member this.GetSlice(rowStart: int option, rowFinish: int option, col: int) =
        let rowStart =
            match rowStart with
            | Some(v) -> v
            | None -> 0
        let rowFinish =
            match rowFinish with
            | Some(v) -> v
            | None -> internalArray.GetLength(0) - 1
        internalArray[rowStart..rowFinish, col]

module test =
    let generateTestMatrix x y =
        let matrix = new Matrix<float>(3, 3)
        for i in 0..2 do
            for j in 0..2 do
                matrix[i, j] <- float(i) * x - float(j) * y
        matrix

    let test1 = generateTestMatrix 2.3 1.1
    let submatrix = test1[0..1, 0..1]
    printfn $"{submatrix}"

    let firstRow = test1[0,*]
    let secondRow = test1[1,*]
    let firstCol = test1[*,0]
    printfn $"{firstCol}"

Логические функции в массивах

Функции Array.exists и Array.exists2 тестовые элементы в одном или двух массивах соответственно. Эти функции принимают тестовую функцию и возвращают , true если имеется элемент (или пара элементов для Array.exists2), удовлетворяющий условию.

В следующем коде показано использование Array.exists и Array.exists2. В этих примерах новые функции создаются путем применения только одного из аргументов, в этих случаях — аргумента функции.

let allNegative = Array.exists (fun elem -> abs (elem) = elem) >> not
printfn "%A" (allNegative [| -1; -2; -3 |])
printfn "%A" (allNegative [| -10; -1; 5 |])
printfn "%A" (allNegative [| 0 |])


let haveEqualElement = Array.exists2 (fun elem1 elem2 -> elem1 = elem2)
printfn "%A" (haveEqualElement [| 1; 2; 3 |] [| 3; 2; 1|])

Выходные данные предыдущего кода выводятся следующим образом.

true
false
false
true

Аналогичным образом функция Array.forall проверяет массив, чтобы определить, удовлетворяет ли каждый элемент логическому условию. Вариант Array.forall2 делает то же самое, используя логическую функцию, которая включает элементы двух массивов одинаковой длины. В следующем коде показано использование этих функций.

let allPositive = Array.forall (fun elem -> elem > 0)
printfn "%A" (allPositive [| 0; 1; 2; 3 |])
printfn "%A" (allPositive [| 1; 2; 3 |])


let allEqual = Array.forall2 (fun elem1 elem2 -> elem1 = elem2)
printfn "%A" (allEqual [| 1; 2 |] [| 1; 2 |])
printfn "%A" (allEqual [| 1; 2 |] [| 2; 1 |])

Выходные данные для этих примеров приведены ниже.

false
true
true
false

Поиск массивов

Array.find принимает логическую функцию и возвращает первый элемент, для которого функция возвращает true, или вызывает System.Collections.Generic.KeyNotFoundException , если не найден элемент, удовлетворяющий условию. Array.findIndex имеет значение типа Array.find, за исключением того, что он возвращает индекс элемента, а не самого элемента.

В следующем коде используется Array.find и Array.findIndex для поиска числа, представляющего собой идеальный квадрат и идеальный куб.

let arrayA = [| 2 .. 100 |]
let delta = 1.0e-10
let isPerfectSquare (x:int) =
    let y = sqrt (float x)
    abs(y - round y) < delta
let isPerfectCube (x:int) =
    let y = System.Math.Pow(float x, 1.0/3.0)
    abs(y - round y) < delta
let element = Array.find (fun elem -> isPerfectSquare elem && isPerfectCube elem) arrayA
let index = Array.findIndex (fun elem -> isPerfectSquare elem && isPerfectCube elem) arrayA
printfn "The first element that is both a square and a cube is %d and its index is %d." element index

Выходные данные выглядят следующим образом.

The first element that is both a square and a cube is 64 and its index is 62.

Array.tryFind имеет значение типа Array.find, за исключением того, что его результат является типом параметра, и возвращается None , если элемент не найден. Array.tryFind следует использовать вместо Array.find , если вы не знаете, находится ли соответствующий элемент в массиве. Аналогичным образом , какArray.findIndex, Array.tryFindIndex за исключением того, что тип параметра является возвращаемым значением. Если элемент не найден, параметр имеет значение None.

В следующем коде показано использование функции Array.tryFind. Этот код зависит от предыдущего кода.

let delta = 1.0e-10
let isPerfectSquare (x:int) =
    let y = sqrt (float x)
    abs(y - round y) < delta
let isPerfectCube (x:int) =
    let y = System.Math.Pow(float x, 1.0/3.0)
    abs(y - round y) < delta
let lookForCubeAndSquare array1 =
    let result = Array.tryFind (fun elem -> isPerfectSquare elem && isPerfectCube elem) array1
    match result with
    | Some x -> printfn "Found an element: %d" x
    | None -> printfn "Failed to find a matching element."

lookForCubeAndSquare [| 1 .. 10 |]
lookForCubeAndSquare [| 100 .. 1000 |]
lookForCubeAndSquare [| 2 .. 50 |]

Выходные данные выглядят следующим образом.

Found an element: 1
Found an element: 729
Failed to find a matching element.

Используйте, Array.tryPick когда необходимо преобразовать элемент в дополнение к его поиску. Результатом является первый элемент, для которого функция возвращает преобразованный элемент в качестве значения параметра, или None , если такой элемент не найден.

В следующем коде показано использование Array.tryPick. В этом случае вместо лямбда-выражения определяется несколько локальных вспомогательных функций для упрощения кода.

let findPerfectSquareAndCube array1 =
    let delta = 1.0e-10
    let isPerfectSquare (x:int) =
        let y = sqrt (float x)
        abs(y - round y) < delta
    let isPerfectCube (x:int) =
        let y = System.Math.Pow(float x, 1.0/3.0)
        abs(y - round y) < delta
    // intFunction : (float -> float) -> int -> int
    // Allows the use of a floating point function with integers.
    let intFunction function1 number = int (round (function1 (float number)))
    let cubeRoot x = System.Math.Pow(x, 1.0/3.0)
    // testElement: int -> (int * int * int) option
    // Test an element to see whether it is a perfect square and a perfect
    // cube, and, if so, return the element, square root, and cube root
    // as an option value. Otherwise, return None.
    let testElement elem =
        if isPerfectSquare elem && isPerfectCube elem then
            Some(elem, intFunction sqrt elem, intFunction cubeRoot elem)
        else None
    match Array.tryPick testElement array1 with
    | Some (n, sqrt, cuberoot) -> printfn "Found an element %d with square root %d and cube root %d." n sqrt cuberoot
    | None -> printfn "Did not find an element that is both a perfect square and a perfect cube."

findPerfectSquareAndCube [| 1 .. 10 |]
findPerfectSquareAndCube [| 2 .. 100 |]
findPerfectSquareAndCube [| 100 .. 1000 |]
findPerfectSquareAndCube [| 1000 .. 10000 |]
findPerfectSquareAndCube [| 2 .. 50 |]

Выходные данные выглядят следующим образом.

Found an element 1 with square root 1 and cube root 1.
Found an element 64 with square root 8 and cube root 4.
Found an element 729 with square root 27 and cube root 9.
Found an element 4096 with square root 64 and cube root 16.
Did not find an element that is both a perfect square and a perfect cube.

Выполнение вычислений с массивами

Функция Array.average возвращает среднее значение каждого элемента в массиве. Он ограничен типами элементов, поддерживающими точное деление на целое число, которое включает типы с плавающей запятой, но не целочисленные типы. Функция Array.averageBy возвращает среднее значение результатов вызова функции для каждого элемента. Для массива целочисленного типа можно использовать Array.averageBy функцию и преобразовать каждый элемент в тип с плавающей запятой для вычисления.

Используйте Array.max или Array.min , чтобы получить максимальный или минимальный элемент, если тип элемента поддерживает его. Аналогичным Array.maxBy образом и Array.minBy позволяют выполнять функцию первым, возможно, для преобразования в тип, поддерживающий сравнение.

Array.sum добавляет элементы массива, Array.sumBy вызывает функцию для каждого элемента и добавляет результаты вместе.

Чтобы выполнить функцию для каждого элемента в массиве без сохранения возвращаемых значений, используйте Array.iter. Для функции, включающей два массива одинаковой длины, используйте Array.iter2. Если также необходимо сохранить массив результатов функции, используйте Array.map или Array.map2, которая работает с двумя массивами за раз.

Варианты Array.iteri и Array.iteri2 позволяют использовать индекс элемента в вычислении; то же самое верно для Array.mapi и Array.mapi2.

Функции Array.fold, Array.foldBack, Array.reduce, Array.reduceBack, Array.scanи Array.scanBack выполняют алгоритмы, включающие все элементы массива. Аналогичным образом варианты Array.fold2 и Array.foldBack2 выполняют вычисления для двух массивов.

Эти функции для выполнения вычислений соответствуют функциям с тем же именем в модуле List. Примеры использования см. в разделе Списки.

Изменение массивов

Array.set задает для элемента указанное значение. Array.fill задает для диапазона элементов в массиве указанное значение. В следующем коде приведен пример Array.fill.

let arrayFill1 = [| 1 .. 25 |]
Array.fill arrayFill1 2 20 0
printfn "%A" arrayFill1

Выходные данные выглядят следующим образом.

[|1; 2; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 23; 24; 25|]

Можно использовать для Array.blit копирования подраздела одного массива в другой массив.

Преобразование в другие типы и из других типов

Array.ofList создает массив из списка. Array.ofSeq создает массив из последовательности. Array.toList и Array.toSeq преобразуются в эти другие типы коллекций из типа массива.

Сортировка массивов

Используйте Array.sort для сортировки массива с помощью универсальной функции сравнения. Используйте Array.sortBy , чтобы указать функцию, которая создает значение, называемое ключом, для сортировки с помощью универсальной функции сравнения ключа. Используйте, Array.sortWith если вы хотите предоставить пользовательскую функцию сравнения. Array.sort, Array.sortByи Array.sortWith все возвращают отсортированный массив в виде нового массива. Варианты Array.sortInPlace, Array.sortInPlaceByи Array.sortInPlaceWith изменяют существующий массив, а не возвращают новый.

Массивы и кортежи

Функции Array.zip и Array.unzip преобразуют массивы пар кортежей в кортежи массивов и наоборот. Array.zip3 и Array.unzip3 похожи, за исключением того, что они работают с кортежами из трех элементов или кортежами из трех массивов.

Параллельные вычисления в массивах

Модуль Array.Parallel содержит функции для выполнения параллельных вычислений в массивах. Этот модуль недоступен в приложениях, предназначенных для версий платформа .NET Framework до версии 4.

См. также