Partilhar via


Sequências

Uma sequência é uma série lógica de elementos, todos de um tipo. As sequências são particularmente úteis quando você tem uma coleção grande e ordenada de dados, mas não necessariamente espera usar todos os elementos. Os elementos de sequência individuais são calculados apenas conforme necessário, de modo que uma sequência pode fornecer um melhor desempenho do que uma lista em situações em que nem todos os elementos são usados. As sequências são representadas pelo seq<'T> tipo, que é um alias para IEnumerable<T>. Portanto, qualquer tipo .NET que implementa interface IEnumerable<T> pode ser usado como uma sequência. O módulo Seq fornece suporte para manipulações envolvendo sequências.

Expressões de sequência

Uma expressão de sequência é uma expressão que é avaliada como uma sequência. As expressões de sequência podem assumir várias formas. O formulário mais simples especifica um intervalo. Por exemplo, seq { 1 .. 5 } cria uma sequência que contém cinco elementos, incluindo os pontos de extremidade 1 e 5. Também é possível especificar um incremento (ou decréscimo) entre dois períodos duplos. Por exemplo, o código a seguir cria a sequência de múltiplos de 10.

// Sequence that has an increment.
seq { 0..10..100 }

As expressões de sequência são compostas por expressões F# que produzem valores da sequência. Você também pode gerar valores programaticamente:

seq { for i in 1..10 -> i * i }

O exemplo anterior usa o -> operador, que permite especificar uma expressão cujo valor se tornará parte da sequência. Você só pode usar -> se cada parte do código que se segue retornar um valor.

Como alternativa, você pode especificar a do palavra-chave, com um opcional yield que se segue:

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
}

O código a seguir gera uma lista de pares de coordenadas junto com um índice em uma matriz que representa a grade. Observe que a primeira for expressão requer que um do seja especificado.

let (height, width) = (10, 10)

seq {
    for row in 0 .. width - 1 do
        for col in 0 .. height - 1 -> (row, col, row * width + col)
}

Uma if expressão usada em uma sequência é um filtro. Por exemplo, para gerar uma sequência de apenas números primos, supondo que você tenha uma função isprime do tipo int -> bool, construa a sequência da seguinte maneira.

seq {
    for n in 1..100 do
        if isprime n then
            n
}

Como mencionado anteriormente, do é necessário aqui porque não há nenhum else ramo que vá com o if. Se você tentar usar ->o , receberá um erro informando que nem todas as ramificações retornam um valor.

A palavra-chave yield!

Às vezes, você pode querer incluir uma sequência de elementos em outra sequência. Para incluir uma sequência em outra sequência, você precisará usar a yield! palavra-chave:

// Repeats '1 2 3 4 5' ten times
seq {
    for _ in 1..10 do
        yield! seq { 1; 2; 3; 4; 5}
}

Outra maneira de yield! pensar é que ele achata uma sequência interna e, em seguida, inclui isso na sequência que a contém.

Quando yield! é usado em uma expressão, todos os outros valores únicos devem usar a yield palavra-chave:

// Combine repeated values with their values
seq {
    for x in 1..10 do
        yield x
        yield! seq { for i in 1..x -> i}
}

O exemplo anterior produzirá o valor de além de x todos os valores de 1 para x cada x.

Exemplos

O primeiro exemplo usa uma expressão de sequência que contém uma iteração, um filtro e um rendimento para gerar uma matriz. Este código imprime uma sequência de números primos entre 1 e 100 na consola.

// 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

O exemplo a seguir cria uma tabela de multiplicação que consiste em tuplas de três elementos, cada um consistindo de dois fatores e o produto:

let multiplicationTable =
    seq {
        for i in 1..9 do
            for j in 1..9 -> (i, j, i * j)
    }

O exemplo a seguir demonstra o uso de yield! para combinar sequências individuais em uma única sequência final. Neste caso, as sequências para cada subárvore em uma árvore binária são concatenadas em uma função recursiva para produzir a sequência final.

// 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

Usando sequências

As sequências suportam muitas das mesmas funções que as listas. As sequências também suportam operações como agrupamento e contagem usando funções de geração de chaves. As sequências também suportam funções mais diversas para extrair subsequências.

Muitos tipos de dados, como listas, matrizes, conjuntos e mapas são implicitamente sequências porque são coleções enumeráveis. Uma função que usa uma sequência como argumento funciona com qualquer um dos tipos de dados F# comuns, além de qualquer tipo de dados .NET que implementa System.Collections.Generic.IEnumerable<'T>o . Compare isso com uma função que usa uma lista como argumento, que só pode tomar listas. O tipo seq<'T> é uma abreviatura de tipo para IEnumerable<'T>. Isso significa que qualquer tipo que implemente o genérico System.Collections.Generic.IEnumerable<'T>, que inclui matrizes, listas, conjuntos e mapas em F#, e também a maioria dos tipos de coleção .NET, é compatível com o seq tipo e pode ser usado sempre que uma sequência for esperada.

Funções do módulo

O módulo Seq no namespace FSharp.Collections contém funções para trabalhar com sequências. Essas funções também funcionam com listas, matrizes, mapas e conjuntos, porque todos esses tipos são enumeráveis e, portanto, podem ser tratados como sequências.

Criação de sequências

Você pode criar sequências usando expressões de sequência, conforme descrito anteriormente, ou usando determinadas funções.

Você pode criar uma sequência vazia usando Seq.empty, ou você pode criar uma sequência de apenas um elemento especificado usando Seq.singleton.

let seqEmpty = Seq.empty
let seqOne = Seq.singleton 10

Você pode usar Seq.init para criar uma sequência para a qual os elementos são criados usando uma função que você fornece. Você também fornece um tamanho para a sequência. Esta função é exatamente como List.init, exceto que os elementos não são criados até que você itere através da sequência. O código a seguir ilustra o uso do Seq.init.

let seqFirst5MultiplesOf10 = Seq.init 5 (fun n -> n * 10)
Seq.iter (fun elem -> printf "%d " elem) seqFirst5MultiplesOf10

A saída é

0 10 20 30 40

Usando Seq.ofArray e Seq.ofList'T<> Function, você pode criar sequências a partir de matrizes e listas. No entanto, você também pode converter matrizes e listas em sequências usando um operador de transmissão. Ambas as técnicas são mostradas no código a seguir.

// 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

Usando Seq.cast, você pode criar uma sequência a partir de uma coleção fracamente tipada, como as definidas em System.Collections. Essas coleções fracamente tipadas têm o tipo System.Object de elemento e são enumeradas usando o tipo não genérico System.Collections.Generic.IEnumerable&#96;1 . O código a seguir ilustra o uso de Seq.cast para converter um System.Collections.ArrayList em uma sequência.

open System

let arr = ResizeArray<int>(10)

for i in 1 .. 10 do
    arr.Add(10)

let seqCast = Seq.cast arr

Você pode definir sequências infinitas usando a função Seq.initInfinite . Para essa sequência, você fornece uma função que gera cada elemento a partir do índice do elemento. Sequências infinitas são possíveis por causa da avaliação preguiçosa; Os elementos são criados conforme necessário, chamando a função que você especificar. O exemplo de código a seguir produz uma sequência infinita de números de ponto flutuante, neste caso a série alternada de recíprocos de quadrados de inteiros sucessivos.

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 gera uma sequência a partir de uma função de computação que toma um estado e o transforma para produzir cada elemento subsequente na sequência. O estado é apenas um valor que é usado para calcular cada elemento, e pode mudar à medida que cada elemento é calculado. O segundo argumento é Seq.unfold o valor inicial usado para iniciar a sequência. Seq.unfold usa um tipo de opção para o estado, que permite encerrar a sequência retornando o None valor. O código a seguir mostra dois exemplos de sequências seq1 e fib, que são geradas por uma unfold operação. A primeira, seq1, é apenas uma sequência simples com números até 20. O segundo, fib, usa unfold para calcular a sequência de Fibonacci. Como cada elemento na sequência de Fibonacci é a soma dos dois números de Fibonacci anteriores, o valor do estado é uma tupla que consiste nos dois números anteriores na sequência. O valor inicial é (0,1), os dois primeiros números da sequência.

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

O resultado é o seguinte:

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 

O código a seguir é um exemplo que usa muitas das funções do módulo de sequência descritas aqui para gerar e calcular os valores de sequências infinitas. O código pode levar alguns minutos para ser executado.

// 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)

Pesquisando e localizando elementos

As sequências suportam a funcionalidade disponível com listas: Seq.exists, Seq.exists2, Seq.find, Seq.findIndex, Seq.pick, Seq.tryFind e Seq.tryFindIndex. As versões dessas funções que estão disponíveis para sequências avaliam a sequência apenas até o elemento que está sendo pesquisado. Para obter exemplos, consulte Listas.

Obtenção de subsequências

Seq.filter e Seq.choose são como as funções correspondentes que estão disponíveis para listas, exceto que a filtragem e a escolha não ocorrem até que os elementos da sequência sejam avaliados.

Seq.truncate cria uma sequência a partir de outra sequência, mas limita a sequência a um número especificado de elementos. Seq.take cria uma nova sequência que contém apenas um número especificado de elementos desde o início de uma sequência. Se houver menos elementos na sequência do que você especifica para tomar, Seq.take lança um System.InvalidOperationExceptionarquivo . A diferença entre Seq.take e Seq.truncate é que Seq.truncate não produz um erro se o número de elementos for menor do que o número especificado.

O código a seguir mostra o comportamento e as diferenças entre Seq.truncate e 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

A saída, antes que o erro ocorra, é a seguinte.

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

Usando Seq.takeWhile, você pode especificar uma função de predicado (uma função booleana) e criar uma sequência de outra sequência composta por esses elementos da sequência original para a qual o predicado é true, mas parar antes do primeiro elemento para o qual o predicado retorna false. Seq.skip retorna uma sequência que ignora um número especificado dos primeiros elementos de outra sequência e retorna os elementos restantes. Seq.skipWhile retorna uma sequência que ignora os primeiros elementos de outra sequência, desde que o predicado retorne truee, em seguida, retorna os elementos restantes, começando com o primeiro elemento para o qual o predicado retorna false.

O exemplo de código a seguir ilustra o comportamento e as diferenças entre Seq.takeWhile, Seq.skipe 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

A saída é a seguinte.

1 4 9
36 49 64 81 100
16 25 36 49 64 81 100

Transformando sequências

Seq.pairwise cria uma nova sequência na qual os elementos sucessivos da sequência de entrada são agrupados em tuplas.

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 é como Seq.pairwise, exceto que, em vez de produzir uma sequência de tuplas, produz uma sequência de matrizes que contêm cópias de elementos adjacentes (uma janela) da sequência. Você especifica o número de elementos adjacentes desejados em cada matriz.

O exemplo de código a seguir demonstra o uso de Seq.windowed. Neste caso, o número de elementos na janela é 3. O exemplo usa printSeq, que é definido no exemplo de código anterior.

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

A saída é a seguinte.

Sequência inicial:

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

Operações com várias sequências

Seq.zip e Seq.zip3 tomam duas ou três sequências e produzem uma sequência de tuplas. Estas funções são como as funções correspondentes disponíveis para listas. Não há funcionalidade correspondente para separar uma sequência em duas ou mais sequências. Se você precisar dessa funcionalidade para uma sequência, converta a sequência em uma lista e use List.unzip.

Classificar, comparar e agrupar

As funções de classificação suportadas para listas também funcionam com sequências. Isso inclui Seq.sort e Seq.sortBy. Estas funções iteram através de toda a sequência.

Você compara duas sequências usando a função Seq.compareWith . A função compara elementos sucessivos por sua vez, e para quando encontra o primeiro par desigual. Quaisquer elementos adicionais não contribuem para a comparação.

O código a seguir mostra o uso de 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.")

No código anterior, apenas o primeiro elemento é calculado e examinado, e o resultado é -1.

Seq.countBy usa uma função que gera um valor chamado chave para cada elemento. Uma chave é gerada para cada elemento chamando essa função em cada elemento. Seq.countBy Em seguida, retorna uma sequência que contém os valores de chave e uma contagem do número de elementos que geraram cada valor da chave.

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

A saída é a seguinte.

(1, 34) (2, 33) (0, 33)

A saída anterior mostra que havia 34 elementos da sequência original que produziram a chave 1, 33 valores que produziram a chave 2 e 33 valores que produziram a chave 0.

Você pode agrupar elementos de uma sequência chamando Seq.groupBy. Seq.groupBy usa uma sequência e uma função que gera uma chave a partir de um elemento. A função é executada em cada elemento da sequência. Seq.groupBy retorna uma sequência de tuplas, onde o primeiro elemento de cada tupla é a chave e o segundo é uma sequência de elementos que produzem essa chave.

O exemplo de código a seguir mostra o uso de Seq.groupBy para particionar a sequência de números de 1 a 100 em três grupos que têm os valores de chave distintos 0, 1 e 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

A saída é a seguinte.

(1, seq [1; 4; 7; 10; ...]) (2, seq [2; 5; 8; 11; ...]) (0, seq [3; 6; 9; 12; ...])

Você pode criar uma sequência que elimine elementos duplicados chamando Seq.distinct. Ou você pode usar Seq.distinctBy, que usa uma função de geração de teclas para ser chamada em cada elemento. A sequência resultante contém elementos da sequência original que têm chaves exclusivas; Os elementos posteriores que produzem uma chave duplicada para um elemento anterior são descartados.

O exemplo de código a seguir ilustra o uso de Seq.distinct. Seq.distinct é demonstrado gerando sequências que representam números binários e, em seguida, mostrando que os únicos elementos distintos são 0 e 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

O código a seguir demonstra Seq.distinctBy começando com uma sequência que contém números negativos e positivos e usando a função de valor absoluto como a função de geração de chave. A sequência resultante está faltando todos os números positivos que correspondem aos números negativos na sequência, porque os números negativos aparecem mais cedo na sequência e, portanto, são selecionados em vez dos números positivos que têm o mesmo valor absoluto, ou chave.

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

Sequências somente leitura e em cache

Seq.readonly cria uma cópia somente leitura de uma sequência. Seq.readonly é útil quando você tem uma coleção de leitura-gravação, como uma matriz, e não deseja modificar a coleção original. Esta função pode ser usada para preservar o encapsulamento de dados. No exemplo de código a seguir, um tipo que contém uma matriz é criado. Uma propriedade expõe a matriz, mas em vez de retornar uma matriz, ela retorna uma sequência que é criada a partir da matriz usando 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 cria uma versão armazenada de uma sequência. Use Seq.cache para evitar a reavaliação de uma sequência ou quando você tiver vários threads que usam uma sequência, mas você deve certificar-se de que cada elemento é acionado apenas uma vez. Quando você tem uma sequência que está sendo usada por vários threads, você pode ter um thread que enumera e calcula os valores para a sequência original, e os threads restantes podem usar a sequência em cache.

Realizando cálculos em sequências

Operações aritméticas simples são como as de listas, como Seq.average, Seq.sum, Seq.averageBy, Seq.sumBy, e assim por diante.

Seq.fold, Seq.reduce e Seq.scan são como as funções correspondentes que estão disponíveis para listas. As sequências suportam um subconjunto das variações completas dessas funções que listam o suporte. Para obter mais informações e exemplos, consulte Listas.

Consulte também