Последовательности
Последовательность — это логический ряд элементов всех одного типа. Последовательности особенно полезны, если у вас есть большая упорядоченная коллекция данных, но не обязательно предполагается использовать все элементы. Отдельные элементы последовательности вычисляются только по мере необходимости, поэтому последовательность может обеспечить лучшую производительность, чем список в ситуациях, в которых не все элементы используются. Последовательности представлены типом seq<'T>
, который является псевдонимом для IEnumerable<T>. Поэтому любой тип .NET, реализующий IEnumerable<T> интерфейс, можно использовать в качестве последовательности. Модуль Seq обеспечивает поддержку манипуляций с участием последовательностей.
Выражения последовательности
Выражение последовательности — это выражение, которое оценивает последовательность. Выражения последовательности могут принимать ряд форм. Простейшая форма задает диапазон. Например, seq { 1 .. 5 }
создает последовательность, содержащую пять элементов, включая конечные точки 1 и 5. Можно также указать добавочный (или декремент) между двумя двойными периодами. Например, следующий код создает последовательность нескольких 10.
// Sequence that has an increment.
seq { 0..10..100 }
Выражения последовательности состоят из выражений F#, которые создают значения последовательности. Вы также можете создавать значения программным способом:
seq { for i in 1..10 -> i * i }
В предыдущем примере используется ->
оператор, который позволяет указать выражение, значение которого станет частью последовательности. Можно использовать ->
только в том случае, если каждая часть кода, следующая за ней, возвращает значение.
Кроме того, можно указать do
ключевое слово с необязательнымyield
, следующим образом:
seq {
for i in 1..10 do
yield i * i
}
// The 'yield' is implicit and doesn't need to be specified in most cases.
seq {
for i in 1..10 do
i * i
}
Следующий код создает список пар координат вместе с индексом в массив, представляющий сетку. Обратите внимание, что первое for
выражение требует do
указания.
let (height, width) = (10, 10)
seq {
for row in 0 .. width - 1 do
for col in 0 .. height - 1 -> (row, col, row * width + col)
}
Выражение, используемое if
в последовательности, является фильтром. Например, чтобы создать последовательность только простых чисел, при условии, что у вас есть функция isprime
типа int -> bool
, создайте последовательность следующим образом.
seq {
for n in 1..100 do
if isprime n then
n
}
Как упоминание ранее, требуется здесь, do
потому что else
нет ветви, которая идет с if
. Если вы попытаетесь использовать ->
, вы получите сообщение об ошибке о том, что не все ветви возвращают значение.
Ключевое слово yield!
.
Иногда может потребоваться включить последовательность элементов в другую последовательность. Чтобы включить последовательность в другую последовательность, необходимо использовать yield!
ключевое слово:
// Repeats '1 2 3 4 5' ten times
seq {
for _ in 1..10 do
yield! seq { 1; 2; 3; 4; 5}
}
Другой способ мышления yield!
заключается в том, что он сплощает внутреннюю последовательность, а затем включает это в содержащую последовательность.
При yield!
использовании в выражении все остальные отдельные значения должны использовать yield
ключевое слово:
// Combine repeated values with their values
seq {
for x in 1..10 do
yield x
yield! seq { for i in 1..x -> i}
}
В предыдущем примере будет производиться значение x
в дополнение ко всем значениям от 1
x
каждого из них x
.
Примеры
В первом примере используется выражение последовательности, содержащее итерацию, фильтр и выход для создания массива. Этот код выводит последовательность простых чисел от 1 до 100 в консоль.
// Recursive isprime function.
let isprime n =
let rec check i =
i > n / 2 || (n % i <> 0 && check (i + 1))
check 2
let aSequence =
seq {
for n in 1..100 do
if isprime n then
n
}
for x in aSequence do
printfn "%d" x
В следующем примере создается таблица умножения, состоящая из кортежей трех элементов, каждая из которых состоит из двух факторов и продукта:
let multiplicationTable =
seq {
for i in 1..9 do
for j in 1..9 -> (i, j, i * j)
}
В следующем примере показано использование yield!
отдельных последовательностей в одну окончательную последовательность. В этом случае последовательности для каждого поддеревного дерева в двоичном дереве объединяются в рекурсивной функции для создания окончательной последовательности.
// Yield the values of a binary tree in a sequence.
type Tree<'a> =
| Tree of 'a * Tree<'a> * Tree<'a>
| Leaf of 'a
// inorder : Tree<'a> -> seq<'a>
let rec inorder tree =
seq {
match tree with
| Tree(x, left, right) ->
yield! inorder left
yield x
yield! inorder right
| Leaf x -> yield x
}
let mytree = Tree(6, Tree(2, Leaf(1), Leaf(3)), Leaf(9))
let seq1 = inorder mytree
printfn "%A" seq1
Использование последовательностей
Последовательности поддерживают многие из одних и то же функций, что и списки. Последовательности также поддерживают такие операции, как группирование и подсчет с помощью функций создания ключей. Последовательности также поддерживают более разнообразные функции для извлечения вложенных параметров.
Многие типы данных, такие как списки, массивы, наборы и карты, являются неявными последовательности, так как они являются перечисленными коллекциями. Функция, которая принимает последовательность в качестве аргумента, работает с любым из распространенных типов данных F# в дополнение к любому типу данных .NET, который реализует System.Collections.Generic.IEnumerable<'T>
. Контрастирует с функцией, которая принимает список в качестве аргумента, который может принимать только списки. Тип seq<'T>
— это аббревиатура типа для IEnumerable<'T>
. Это означает, что любой тип, реализующий универсальный System.Collections.Generic.IEnumerable<'T>
тип, включающий массивы, списки, наборы и карты в F#, а также большинство типов коллекций .NET, совместим с seq
типом и может использоваться везде, где ожидается последовательность.
Функции модуля
Модуль Seq в пространстве имен FSharp.Collections содержит функции для работы с последовательности. Эти функции также работают со списками, массивами, картами и наборами, так как все эти типы перечисляются и поэтому могут рассматриваться как последовательности.
Создание последовательностей
Последовательности можно создавать с помощью выражений последовательности, как описано ранее, или с помощью определенных функций.
Вы можете создать пустую последовательность с помощью Seq.empty или создать последовательность только одного указанного элемента с помощью Seq.singleton.
let seqEmpty = Seq.empty
let seqOne = Seq.singleton 10
Seq.init можно использовать для создания последовательности, для которой создаются элементы с помощью предоставленной функции. Вы также предоставляете размер последовательности. Эта функция похожа на List.init, за исключением того, что элементы не создаются, пока не выполняется итерацию по последовательности. Следующий код иллюстрирует использование Seq.init
.
let seqFirst5MultiplesOf10 = Seq.init 5 (fun n -> n * 10)
Seq.iter (fun elem -> printf "%d " elem) seqFirst5MultiplesOf10
Выходные данные:
0 10 20 30 40
С помощью функции Seq.ofArray и Seq.ofList'T<> можно создавать последовательности из массивов и списков. Однако можно также преобразовать массивы и списки в последовательности с помощью оператора приведения. Оба метода показаны в следующем коде.
// Convert an array to a sequence by using a cast.
let seqFromArray1 = [| 1 .. 10 |] :> seq<int>
// Convert an array to a sequence by using Seq.ofArray.
let seqFromArray2 = [| 1 .. 10 |] |> Seq.ofArray
С помощью Seq.cast можно создать последовательность из слабо типизированной коллекции, например определенных в System.Collections
. Такие слабо типизированные коллекции имеют тип System.Object
элемента и перечисляются с помощью не универсального System.Collections.Generic.IEnumerable`1
типа. Следующий код иллюстрирует использование Seq.cast
System.Collections.ArrayList
преобразования в последовательность.
open System
let arr = ResizeArray<int>(10)
for i in 1 .. 10 do
arr.Add(10)
let seqCast = Seq.cast arr
Бесконечные последовательности можно определить с помощью функции Seq.initInfinite . Для такой последовательности вы предоставляете функцию, которая создает каждый элемент из индекса элемента. Бесконечные последовательности возможны из-за отложенной оценки; элементы создаются по мере необходимости путем вызова указанной функции. В следующем примере кода создается бесконечная последовательность чисел с плавающей запятой, в этом случае чередующаяся серия взаимных квадратов последовательных целых чисел.
let seqInfinite =
Seq.initInfinite (fun index ->
let n = float (index + 1)
1.0 / (n * n * (if ((index + 1) % 2 = 0) then 1.0 else -1.0)))
printfn "%A" seqInfinite
Seq.unfold создает последовательность из вычислительной функции, которая принимает состояние и преобразует его для создания каждого последующего элемента в последовательности. Состояние — это просто значение, которое используется для вычисления каждого элемента и может изменяться по мере вычисления каждого элемента. Второй аргумент Seq.unfold
— это начальное значение, используемое для запуска последовательности. Seq.unfold
использует тип параметра для состояния, который позволяет завершить последовательность, возвращая None
значение. В следующем коде показаны два примера последовательностей и seq1
fib
, созданных операцией unfold
. Во-первых, seq1
это просто простая последовательность с числами до 20. Во-вторых, fib
используется unfold
для вычисления последовательности Fibonacci. Так как каждый элемент в последовательности Fibonacci является суммой предыдущих двух чисел Fibonacci, значение состояния — это кортеж, состоящий из предыдущих двух чисел в последовательности. Начальное значение — первые (0,1)
два числа в последовательности.
let seq1 =
0 // Initial state
|> Seq.unfold (fun state ->
if (state > 20) then
None
else
Some(state, state + 1))
printfn "The sequence seq1 contains numbers from 0 to 20."
for x in seq1 do
printf "%d " x
let fib =
(0, 1)
|> Seq.unfold (fun state ->
let cur, next = state
if cur < 0 then // overflow
None
else
let next' = cur + next
let state' = next, next'
Some (cur, state') )
printfn "\nThe sequence fib contains Fibonacci numbers."
for x in fib do printf "%d " x
Вывод выглядит следующим образом.
The sequence seq1 contains numbers from 0 to 20.
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
The sequence fib contains Fibonacci numbers.
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181
Следующий код является примером, который использует многие функции модуля последовательности, описанные здесь, для создания и вычисления значений бесконечных последовательностей. Выполнение кода может занять несколько минут.
// generateInfiniteSequence generates sequences of floating point
// numbers. The sequences generated are computed from the fDenominator
// function, which has the type (int -> float) and computes the
// denominator of each term in the sequence from the index of that
// term. The isAlternating parameter is true if the sequence has
// alternating signs.
let generateInfiniteSequence fDenominator isAlternating =
if (isAlternating) then
Seq.initInfinite (fun index ->
1.0 /(fDenominator index) * (if (index % 2 = 0) then -1.0 else 1.0))
else
Seq.initInfinite (fun index -> 1.0 /(fDenominator index))
// The harmonic alternating series is like the harmonic series
// except that it has alternating signs.
let harmonicAlternatingSeries = generateInfiniteSequence (fun index -> float index) true
// This is the series of reciprocals of the odd numbers.
let oddNumberSeries = generateInfiniteSequence (fun index -> float (2 * index - 1)) true
// This is the series of recipocals of the squares.
let squaresSeries = generateInfiniteSequence (fun index -> float (index * index)) false
// This function sums a sequence, up to the specified number of terms.
let sumSeq length sequence =
(0, 0.0)
|>
Seq.unfold (fun state ->
let subtotal = snd state + Seq.item (fst state + 1) sequence
if (fst state >= length) then
None
else
Some(subtotal, (fst state + 1, subtotal)))
// This function sums an infinite sequence up to a given value
// for the difference (epsilon) between subsequent terms,
// up to a maximum number of terms, whichever is reached first.
let infiniteSum infiniteSeq epsilon maxIteration =
infiniteSeq
|> sumSeq maxIteration
|> Seq.pairwise
|> Seq.takeWhile (fun elem -> abs (snd elem - fst elem) > epsilon)
|> List.ofSeq
|> List.rev
|> List.head
|> snd
// Compute the sums for three sequences that converge, and compare
// the sums to the expected theoretical values.
let result1 = infiniteSum harmonicAlternatingSeries 0.00001 100000
printfn "Result: %f ln2: %f" result1 (log 2.0)
let pi = Math.PI
let result2 = infiniteSum oddNumberSeries 0.00001 10000
printfn "Result: %f pi/4: %f" result2 (pi/4.0)
// Because this is not an alternating series, a much smaller epsilon
// value and more terms are needed to obtain an accurate result.
let result3 = infiniteSum squaresSeries 0.0000001 1000000
printfn "Result: %f pi*pi/6: %f" result3 (pi*pi/6.0)
Поиск и поиск элементов
Последовательности поддерживают функции, доступные со списками: Seq.exists, Seq.exists2, Seq.find, Seq.findIndex, Seq.pick, Seq.tryFind и Seq.tryFindIndex. Версии этих функций, доступные для последовательностей, оценивают последовательность только до элемента, для которого выполняется поиск. Примеры см. в разделе "Списки".
Получение вложенных параметров
Seq.filter и Seq.choose похожи на соответствующие функции, доступные для списков, за исключением того, что фильтрация и выбор не возникают до оценки элементов последовательности.
Seq.truncate создает последовательность из другой последовательности, но ограничивает последовательность указанным числом элементов. Seq.take создает новую последовательность, содержащую только указанное число элементов с начала последовательности. Если в последовательности меньше элементов, чем указано, Seq.take
вызывает исключение System.InvalidOperationException
. Разница между Seq.take
и Seq.truncate
заключается в том, что не возникает ошибки, Seq.truncate
если число элементов меньше указанного числа.
В следующем коде показано поведение и различия между Seq.truncate
и Seq.take
.
let mySeq = seq { for i in 1 .. 10 -> i*i }
let truncatedSeq = Seq.truncate 5 mySeq
let takenSeq = Seq.take 5 mySeq
let truncatedSeq2 = Seq.truncate 20 mySeq
let takenSeq2 = Seq.take 20 mySeq
let printSeq seq1 = Seq.iter (printf "%A ") seq1; printfn ""
// Up to this point, the sequences are not evaluated.
// The following code causes the sequences to be evaluated.
truncatedSeq |> printSeq
truncatedSeq2 |> printSeq
takenSeq |> printSeq
// The following line produces a run-time error (in printSeq):
takenSeq2 |> printSeq
Выходные данные перед ошибкой приведены следующим образом.
1 4 9 16 25
1 4 9 16 25 36 49 64 81 100
1 4 9 16 25
1 4 9 16 25 36 49 64 81 100
Используя Seq.takeTime, можно указать функцию предиката (логическую функцию) и создать последовательность из другой последовательности, состоящей из этих элементов исходной последовательности, для которой является true
предикат, но остановиться перед первым элементом, для которого возвращается false
предикат. Seq.skip возвращает последовательность, которая пропускает указанное число первых элементов другой последовательности и возвращает оставшиеся элементы. Seq.skipTime возвращает последовательность, которая пропускает первые элементы другой последовательности до тех пор, пока предикат возвращает true
, а затем возвращает оставшиеся элементы, начиная с первого элемента, для которого возвращается false
предикат.
В следующем примере кода показано поведение и различия между Seq.takeWhile
, Seq.skip
и Seq.skipWhile
.
// takeWhile
let mySeqLessThan10 = Seq.takeWhile (fun elem -> elem < 10) mySeq
mySeqLessThan10 |> printSeq
// skip
let mySeqSkipFirst5 = Seq.skip 5 mySeq
mySeqSkipFirst5 |> printSeq
// skipWhile
let mySeqSkipWhileLessThan10 = Seq.skipWhile (fun elem -> elem < 10) mySeq
mySeqSkipWhileLessThan10 |> printSeq
Выходные данные выглядят следующим образом.
1 4 9
36 49 64 81 100
16 25 36 49 64 81 100
Преобразование последовательностей
Seq.pairwise создает новую последовательность, в которой последовательные элементы входной последовательности группируются в кортежи.
let printSeq seq1 = Seq.iter (printf "%A ") seq1; printfn ""
let seqPairwise = Seq.pairwise (seq { for i in 1 .. 10 -> i*i })
printSeq seqPairwise
printfn ""
let seqDelta = Seq.map (fun elem -> snd elem - fst elem) seqPairwise
printSeq seqDelta
Seq.windowed похож Seq.pairwise
, за исключением того, что вместо создания последовательности кортежей он создает последовательность массивов, содержащих копии смежных элементов ( окно) из последовательности. Вы указываете количество смежных элементов, которые требуется в каждом массиве.
В следующем коде показано использование функции Seq.windowed
. В этом случае число элементов в окне равно 3. В примере используется printSeq
, который определен в предыдущем примере кода.
let seqNumbers = [ 1.0; 1.5; 2.0; 1.5; 1.0; 1.5 ] :> seq<float>
let seqWindows = Seq.windowed 3 seqNumbers
let seqMovingAverage = Seq.map Array.average seqWindows
printfn "Initial sequence: "
printSeq seqNumbers
printfn "\nWindows of length 3: "
printSeq seqWindows
printfn "\nMoving average: "
printSeq seqMovingAverage
Выходные данные выглядят следующим образом.
Начальная последовательность:
1.0 1.5 2.0 1.5 1.0 1.5
Windows of length 3:
[|1.0; 1.5; 2.0|] [|1.5; 2.0; 1.5|] [|2.0; 1.5; 1.0|] [|1.5; 1.0; 1.5|]
Moving average:
1.5 1.666666667 1.5 1.333333333
Операции с несколькими последовательности
Seq.zip и Seq.zip3 принимают две или три последовательности и создают последовательность кортежей. Эти функции похожи на соответствующие функции, доступные для списков. Нет соответствующих функций для разделения одной последовательности на две или более последовательностей. Если вам нужна эта функция для последовательности, преобразуйте последовательность в список и используйте List.unzip.
Сортировка, сравнение и группирование
Функции сортировки, поддерживаемые для списков, также работают с последовательности. К ним относятся Seq.sort и Seq.sortBy. Эти функции выполняют итерацию по всей последовательности.
Вы сравниваете две последовательности с помощью функции Seq.compareWith . Функция сравнивает последовательные элементы в свою очередь и останавливается при обнаружении первой неравной пары. Любые дополнительные элементы не способствуют сравнению.
В следующем коде показано использование Seq.compareWith
.
let sequence1 = seq { 1 .. 10 }
let sequence2 = seq { 10 .. -1 .. 1 }
// Compare two sequences element by element.
let compareSequences =
Seq.compareWith (fun elem1 elem2 ->
if elem1 > elem2 then 1
elif elem1 < elem2 then -1
else 0)
let compareResult1 = compareSequences sequence1 sequence2
match compareResult1 with
| 1 -> printfn "Sequence1 is greater than sequence2."
| -1 -> printfn "Sequence1 is less than sequence2."
| 0 -> printfn "Sequence1 is equal to sequence2."
| _ -> failwith("Invalid comparison result.")
В предыдущем коде вычисляется и проверяется только первый элемент, а результат — -1.
Seq.countBy принимает функцию, которая создает значение, называемое ключом для каждого элемента. Ключ создается для каждого элемента, вызывая эту функцию для каждого элемента. Seq.countBy
затем возвращает последовательность, содержащую значения ключа, и количество элементов, создающих каждое значение ключа.
let mySeq1 = seq { 1.. 100 }
let printSeq seq1 = Seq.iter (printf "%A ") seq1
let seqResult =
mySeq1
|> Seq.countBy (fun elem ->
if elem % 3 = 0 then 0
elif elem % 3 = 1 then 1
else 2)
printSeq seqResult
Выходные данные выглядят следующим образом.
(1, 34) (2, 33) (0, 33)
Предыдущие выходные данные показывают, что было 34 элемента исходной последовательности, создавших ключ 1, 33 значения, которые произвели ключ 2 и 33 значения, которые произвели ключ 0.
Элементы последовательности можно сгруппировать, вызвав Seq.groupBy. Seq.groupBy
принимает последовательность и функцию, которая создает ключ из элемента. Функция выполняется для каждого элемента последовательности. Seq.groupBy
возвращает последовательность кортежей, где первый элемент каждого кортежа является ключом, а второй — последовательность элементов, создающих этот ключ.
В следующем примере кода показано использование Seq.groupBy
секционирования последовательности чисел от 1 до 100 на три группы с различными значениями ключей 0, 1 и 2.
let sequence = seq { 1 .. 100 }
let printSeq seq1 = Seq.iter (printf "%A ") seq1
let sequences3 =
sequences
|> Seq.groupBy (fun index ->
if (index % 3 = 0) then 0
elif (index % 3 = 1) then 1
else 2)
sequences3 |> printSeq
Выходные данные выглядят следующим образом.
(1, seq [1; 4; 7; 10; ...]) (2, seq [2; 5; 8; 11; ...]) (0, seq [3; 6; 9; 12; ...])
Вы можете создать последовательность, которая устраняет повторяющиеся элементы, вызвав Seq.distinct. Или можно использовать Seq.distinctBy, которая принимает функцию создания ключей для вызова каждого элемента. Результирующая последовательность содержит элементы исходной последовательности с уникальными ключами; более поздние элементы, создающие повторяющийся ключ к предыдущему элементу, отключаются карта.
В следующем примере кода показано использование Seq.distinct
. Seq.distinct
демонстрируется путем создания последовательностей, представляющих двоичные числа, а затем показывает, что единственными отдельными элементами являются 0 и 1.
let binary n =
let rec generateBinary n =
if (n / 2 = 0) then [n]
else (n % 2) :: generateBinary (n / 2)
generateBinary n
|> List.rev
|> Seq.ofList
printfn "%A" (binary 1024)
let resultSequence = Seq.distinct (binary 1024)
printfn "%A" resultSequence
Следующий код демонстрирует Seq.distinctBy
, начиная с последовательности, содержащей отрицательные и положительные числа, и используя абсолютную функцию значения в качестве функции создания ключей. Результирующая последовательность отсутствует все положительные числа, соответствующие отрицательным числам последовательности, так как отрицательные числа отображаются ранее в последовательности и поэтому выбираются вместо положительных чисел с одинаковым абсолютным значением или ключом.
let inputSequence = { -5 .. 10 }
let printSeq seq1 = Seq.iter (printf "%A ") seq1
printfn "Original sequence: "
printSeq inputSequence
printfn "\nSequence with distinct absolute values: "
let seqDistinctAbsoluteValue = Seq.distinctBy (fun elem -> abs elem) inputSequence
printSeq seqDistinctAbsoluteValue
Чтение и кэшированные последовательности
Seq.readonly создает копию последовательности только для чтения. Seq.readonly
полезно, если у вас есть коллекция чтения и записи, например массив, и вы не хотите изменять исходную коллекцию. Эту функцию можно использовать для сохранения инкапсуляции данных. В следующем примере кода создается тип, содержащий массив. Свойство предоставляет массив, но вместо возврата массива возвращает последовательность, созданную из массива.Seq.readonly
type ArrayContainer(start, finish) =
let internalArray = [| start .. finish |]
member this.RangeSeq = Seq.readonly internalArray
member this.RangeArray = internalArray
let newArray = new ArrayContainer(1, 10)
let rangeSeq = newArray.RangeSeq
let rangeArray = newArray.RangeArray
// These lines produce an error:
//let myArray = rangeSeq :> int array
//myArray[0] <- 0
// The following line does not produce an error.
// It does not preserve encapsulation.
rangeArray[0] <- 0
Seq.cache создает сохраненную версию последовательности. Используйте Seq.cache
для предотвращения повторной оценки последовательности или при наличии нескольких потоков, использующих последовательность, но необходимо убедиться, что каждый элемент действует только один раз. При наличии последовательности, используемой несколькими потоками, можно использовать один поток, который перечисляет и вычисляет значения исходной последовательности, а остальные потоки могут использовать кэшированную последовательность.
Выполнение вычислений в последовательностях
Простые арифметические операции похожи на списки, такие как Seq.average, Seq.sum, Seq.averageBy, Seq.sumBy и т. д.
Seq.fold, Seq.reduce и Seq.scan похожи на соответствующие функции, доступные для списков. Последовательности поддерживают подмножество полных вариантов этих функций, которые перечисляют поддержку. Дополнительные сведения и примеры см. в разделе "Списки".