Массивы (F#)

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

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

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

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

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

let array1 =
    [|
        1
        2
        3
     |]

Тип элементов массива выводится из используемых литералах и должен быть согласован.

// This is an array of 3 integers.
let array1 = [| 1; 2; 3 |]
// This is an array of a tuple of 3 integers.
let array2 = [| 1, 2, 3 |]

Следующий код вызывает ошибку, так как 3.0 является плавающей и 1 и 2 целыми числами.

// Causes an error. The 3.0 (float) cannot be converted to integer implicitly.
// let array3 = [| 1; 2; 3.0 |]

Следующий код также вызывает ошибку, так как 1,2 является кортежем и 3 является целым числом.

// Causes an error too. The 3 (integer) cannot be converted to tuple implicitly.
// let array4 = [| 1, 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# — это тип System.Arrayплатформа .NET Framework. Поэтому массивы F# поддерживают все функциональные возможности, доступные в System.Array.

Модуль Array поддерживает операции с одномерными массивами. Модули и Array4D содержат функции, поддерживающие операции с массивами Array2DArray3Dдвух, трех и четырех измерений соответственно. Вы можете создать массивы ранга, превышающие четыре, с помощью 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.findIndexArray.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.tryFindIndex как Array.findIndex и тип параметра, является возвращаемым значением. Если элемент не найден, параметр имеет значение 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.maxByArray.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.reduceArray.reduceBackArray.scanArray.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.sortInPlaceArray.sortInPlaceByи Array.sortInPlaceWith изменение существующего массива вместо возврата нового.

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

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

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

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

См. также