Sequenzen

Eine Sequenz ist eine logische Reihe von Elementen eines einzigen Typs. Sequenzen sind besonders nützlich, wenn Sie über eine große, geordnete Sammlung von Daten verfügen, aber nicht unbedingt alle Elemente auch verwenden möchten. Einzelne Sequenzelemente werden nur bei Bedarf berechnet. Daher kann in Situationen, in denen nicht alle Elemente verwendet werden, mit einer Sequenz eine bessere Leistung als mit einer Liste erreicht werden. Sequenzen werden durch den seq<'T>-Typ dargestellt, der ein Alias für IEnumerable<T> ist. Daher kann jeder .NET-Typ, der die IEnumerable<T>-Schnittstelle implementiert, als Sequenz verwendet werden. Das Seq-Modul bietet Unterstützung für Manipulationen mit Sequenzen.

Sequenzausdrücke

Ein Sequenzausdruck ist ein Ausdruck, dessen Auswertung eine Sequenz ergibt. Sequenzausdrücke können verschiedene Formen haben. Die einfachste Form gibt einen Bereich an. seq { 1 .. 5 } erstellt beispielsweise eine Sequenz, die fünf Elemente enthält, einschließlich der Endpunkte 1 und 5. Sie können auch ein Inkrement (oder ein Dekrement) zwischen jeweils zwei Punkten angeben. Der folgende Code erstellt beispielsweise die Sequenz von Vielfachen von 10.

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

Sequenzausdrücke bestehen aus F#-Ausdrücken, die Werte der Sequenz generieren. Sie können Werte auch programmgesteuert generieren:

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

Im vorherigen Beispiel wird der ->-Operator verwendet, mit dem Sie einen Ausdruck angeben können, dessen Wert Teil der Sequenz wird. Sie können -> nur verwenden, wenn jeder Teil des darauffolgenden Codes einen Wert zurückgibt.

Alternativ können Sie das do-Schlüsselwort mit einem optionalen, darauffolgenden yield-Schlüsselwort angeben:

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
}

Der folgende Code generiert eine Liste von Koordinatenpaaren zusammen mit einem Index in einem Array, das das Raster darstellt. Beachten Sie, dass für den ersten for-Ausdruck ein do-Schlüsselwort angegeben werden muss.

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

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

Ein if-Ausdruck, der in einer Sequenz verwendet wird, ist ein Filter. Um beispielsweise eine Sequenz zu generieren, die nur aus Primzahlen besteht, erstellen Sie die Sequenz wie folgt, sofern Sie über eine Funktion isprime vom Typ int -> bool verfügen.

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

Wie bereits erwähnt, ist do hier erforderlich, da es keinen else-Branch gibt, der mit verwendet if wird. Wenn Sie versuchen, -> zu verwenden, erhalten Sie eine Fehlermeldung, dass nicht alle Branches einen Wert zurückgeben.

dem yield!-Schlüsselwort

Möglicherweise möchten Sie eine Sequenz von Elementen in eine andere Sequenz einschließen. Um eine Sequenz in eine andere Sequenz einzuschließen, müssen Sie das yield!-Schlüsselwort verwenden:

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

Sie können sich das Ganze auch so vorstellen, dass yield! eine innere Sequenz vereinfacht und diese dann in die enthaltende Sequenz einschließt.

Wenn yield! in einem Ausdruck verwendet wird, müssen alle anderen einzelnen Werte das yield-Schlüsselwort verwenden:

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

Im vorherigen Beispiel wird der Wert von x zusätzlich zu allen Werten von 1 bis x für jedes x erzeugt.

Beispiele

Im ersten Beispiel wird ein Sequenzausdruck verwendet, der eine Iteration, einen Filter und ein Yield zum Generieren eines Arrays enthält. Dieser Code gibt eine Sequenz von Primzahlen zwischen 1 und 100 an die Konsole aus.

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

Im folgenden Beispiel wird eine Multiplikationstabelle erstellt, die aus Tupeln mit drei Elementen besteht, die jeweils aus zwei Faktoren und dem Produkt bestehen:

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

Im folgenden Beispiel wird die Verwendung von yield! veranschaulicht, um einzelne Sequenzen in einer einzigen endgültigen Sequenz zu kombinieren. In diesem Fall werden die Sequenzen für jede Unterstruktur in einer binären Struktur in einer rekursiven Funktion verkettet, um die endgültige Sequenz zu erzeugen.

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

Verwenden von Sequenzen

Sequenzen unterstützen viele der Funktionen, die auch von Listen unterstützt werden. Vorgänge wie Gruppieren und Zählen werden ebenfalls von Sequenzen unterstützt. Hierfür werden schlüsselgenerierende Funktionen verwendet. Sequenzen unterstützen auch vielfältigere Funktionen zum Extrahieren von Untersequenzen.

Viele Datentypen wie Listen, Arrays, Sets und Zuordnungen, sind implizite Sequenzen, da es sich um aufzählbare Sammlungen handelt. Eine Funktion, die eine Sequenz als Argument verwendet, funktioniert zusätzlich zu jedem .NET-Datentyp, der System.Collections.Generic.IEnumerable<'T> implementiert, mit jedem der allgemeinen F#-Datentypen. Dies steht im Gegensatz zu einer Funktion, die eine Liste als Argument verwendet und nur mit Listen funktioniert. Der Typ seq<'T> ist eine Typabkürzung für IEnumerable<'T>. Dies bedeutet, dass jeder Typ, der die generische System.Collections.Generic.IEnumerable<'T>-Schnittstelle implementiert, die Arrays, Listen, Sets und Zuordnungen in F# sowie die meisten .NET-Sammlungstypen enthält, mit dem seq-Typ kompatibel ist und überall dort verwendet werden kann, wo eine Sequenz erwartet wird.

Modulfunktionen

Das Seq-Modul im FSharp.Collections-Namespace enthält Funktionen zum Arbeiten mit Sequenzen. Diese Funktionen funktionieren auch mit Listen, Arrays, Zuordnungen und Sets, da all diese Typen aufzählbar sind und daher als Sequenzen behandelt werden können.

Erstellen von Sequenzen

Sie können Sequenzen mithilfe von Sequenzausdrücken erstellen, wie zuvor beschrieben, oder mithilfe bestimmter Funktionen.

Wenn Sie eine leere Sequenz erstellen möchten, verwenden Sie Seq.empty. Um eine Sequenz zu erstellen, in der nur ein Element angegeben ist, verwenden Sie Seq.singleton.

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

Sie können Seq.init verwenden, um eine Sequenz zu erstellen, für die die Elemente mithilfe einer von Ihnen bereitgestellten Funktion erstellt werden. Außerdem geben Sie eine Größe für die Sequenz an. Diese Funktion ist mit List.init vergleichbar. Die Elemente werden jedoch erst erstellt, wenn Sie die Sequenz durchlaufen. Das folgende Codebeispiel veranschaulicht die Verwendung von Seq.init.

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

Ausgabe:

0 10 20 30 40

Mithilfe der Seq.ofArray- und Seq.ofList<'T>-Funktionen können Sie Sequenzen aus Arrays und Listen erstellen. Sie können Arrays und Listen jedoch auch mithilfe eines Umwandlungsoperators in Sequenzen konvertieren. Beide Vorgehensweisen sind im nachfolgenden Code dargestellt.

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

Mithilfe von Seq.cast können Sie eine Sequenz aus einer schwach typisierten Sammlung erstellen. Hierzu gehören beispielsweise die in System.Collectionsdefinierten Sammlungen. Solche schwach typisierten Sammlungen weisen den Elementtyp System.Object auf und werden mithilfe des nicht generischen System.Collections.Generic.IEnumerable&#96;1-Typs aufgezählt. Der folgende Code veranschaulicht die Verwendung von Seq.cast, um einen System.Collections.ArrayList-Wert in eine Sequenz zu konvertieren.

open System

let arr = ResizeArray<int>(10)

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

let seqCast = Seq.cast arr

Mithilfe der Seq.initInfinite-Funktion können Sie unendliche Sequenzen definieren. Für eine solche Sequenz stellen Sie eine Funktion bereit, die jedes Element aus dem Index des Elements generiert. Unendliche Sequenzen sind aufgrund der verzögerten Auswertung möglich. Elemente werden nach Bedarf erstellt, indem die von Ihnen angegebene Funktion aufgerufen wird. Im folgenden Codebeispiel wird eine unendliche Sequenz von Gleitkommazahlen erzeugt, in diesem Fall die alternierende Reihe der Kehrwerte von Quadraten aufeinanderfolgender ganzer Zahlen.

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 generiert eine Sequenz aus einer Berechnungsfunktion, die einen Zustand transformiert, um die nachfolgenden Elemente in der Sequenz zu erzeugen. Der Zustand ist nur ein Wert, der für die Berechnung jedes Elements verwendet wird und sich ändern kann, wenn das jeweilige Element berechnet wird. Das zweite Argument von Seq.unfold ist der Anfangswert, der zum Starten der Sequenz verwendet wird. Seq.unfold verwendet einen Optionstyp für den Zustand. Hierdurch können Sie die Sequenz beenden, indem Sie den None-Wert zurückgeben. Der folgende Code zeigt zwei Beispiele für Sequenzen, seq1 und fib, die von einem unfold-Vorgang generiert werden. Die seq1-Sequenz ist eine einfache Sequenz mit Zahlen bis 20. Die fib-Sequenz verwendet unfold, um die Fibonacci-Folge zu berechnen. Da jedes Element in der Fibonacci-Folge die Summe der beiden vorherigen Fibonacci-Zahlen ist, ist der Zustandswert ein Tupel, das aus den beiden vorherigen Zahlen in der Sequenz besteht. Der Anfangswert ist (0,1), die ersten beiden Zahlen in der Sequenz.

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

Die Ausgabe lautet wie folgt:

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 

Der folgende Code ist ein Beispiel, in dem viele der hier beschriebenen Sequenzmodulfunktionen verwendet werden, um die Werte unendlicher Sequenzen zu generieren und zu berechnen. Die Ausführung des Codes kann einige Minuten dauern.

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

Suchen von Elementen

Sequenzen unterstützen die Funktionen, die mit Listen verfügbar sind: Seq.exists, Seq.exists2, Seq.find, Seq.findIndex, Seq.pick, Seq.tryFind und Seq.tryFindIndex. Die Versionen dieser für Sequenzen verfügbaren Funktionen werten die Sequenz nur bis zu dem Element aus, nach dem gesucht wird. Beispiele zu diesem Thema finden Sie unter Listen.

Abrufen von Untersequenzen

Seq.filter und Seq.choose sind mit den entsprechenden Funktionen vergleichbar, die für Listen verfügbar sind. Die Sequenzelemente werden jedoch erst gefiltert und ausgewählt, nachdem sie ausgewertet wurden.

Seq.truncate erstellt eine Sequenz aus einer anderen Sequenz, beschränkt diese jedoch auf eine angegebene Anzahl von Elementen. Seq.take erstellt eine neue Sequenz, die nur eine angegebene Anzahl von Elementen vom Anfang einer Sequenz enthält. Wenn die Sequenz weniger Elemente als die von Ihnen angegebene Anzahl enthält, löst Seq.take eine System.InvalidOperationException aus. Der Unterschied zwischen Seq.take und Seq.truncate ist, dass bei Seq.truncate kein Fehler erzeugt wird, wenn die Anzahl der Elemente kleiner als die von Ihnen angegebene Anzahl ist.

Der folgende Code zeigt das Verhalten von Seq.truncate und Seq.take sowie die Unterschiede zwischen den beiden Funktionen.

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

Bevor der Fehler auftritt, sieht die Ausgabe wie folgt aus.

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

Mithilfe von Seq.takeWhile können Sie eine Prädikatfunktion (eine boolesche Funktion) angeben und eine Sequenz aus einer anderen Sequenz erstellen, die aus den Elementen der ursprünglichen Sequenz besteht, für die das Prädikat true ist, aber vor dem ersten Element beendet wird, für das das Prädikat false zurückgibt. Seq.skip gibt eine Sequenz zurück, die eine angegebene Anzahl der ersten Elemente einer anderen Sequenz überspringt und die verbleibenden Elemente zurückgibt. Seq.skipWhile gibt eine Sequenz zurück, die die ersten Elemente einer anderen Sequenz überspringt, solange das Prädikat true zurückgibt. Anschließend gibt die Sequenz die verbleibenden Elemente zurück, beginnend mit dem ersten Element, für das das Prädikat false zurückgibt.

Das folgende Codebeispiel veranschaulicht das Verhalten von Seq.takeWhile, Seq.skip und Seq.skipWhile sowie und die Unterschiede zwischen diesen Funktionen.

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

Die Ausgabe lautet wie folgt.

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

Transformieren von Sequenzen

Seq.pairwise erstellt eine neue Sequenz, in der aufeinanderfolgende Elemente der Eingabesequenz zu Tupeln gruppiert werden.

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 ist mit Seq.pairwise vergleichbar. Anstelle einer Sequenz von Tupeln wird jedoch eine Sequenz von Arrays erzeugt, die Kopien angrenzender Elemente (ein Fenster) aus der Sequenz enthalten. Sie geben die Anzahl der angrenzenden Elemente an, die in jedem Array enthalten sein sollen.

Das folgende Codebeispiel veranschaulicht die Verwendung von Seq.windowed. In diesem Fall enthält das Fenster 3 Elemente. Im Beispiel wird die printSeq-Funktion verwendet, die im vorherigen Codebeispiel definiert ist.

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

Die Ausgabe lautet wie folgt.

Anfangssequenz:

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

Vorgänge mit mehreren Sequenzen

Seq.zip und Seq.zip3 verwenden zwei oder drei Sequenzen und erzeugen eine Sequenz von Tupeln. Diese Funktionen sind mit den entsprechenden Funktionen vergleichbar, die für Listen verfügbar sind. Es gibt keine entsprechende Funktion, um eine Sequenz in zwei oder mehr Sequenzen zu trennen. Wenn Sie Sequenzen trennen müssen, konvertieren Sie die Sequenz in eine Liste, und verwenden Sie List.unzip.

Sortieren, Vergleichen und Gruppieren

Die für Listen unterstützten Sortierfunktionen sind auch für Sequenzen verfügbar. Zu diesen Funktionen gehören Seq.sort und Seq.sortBy, die die gesamte Sequenz durchlaufen.

Mithilfe der Seq.compareWith-Funktion können Sie zwei Sequenzen miteinander vergleichen. Die Funktion vergleicht aufeinanderfolgende Elemente nacheinander und beendet den Vergleich beim ersten ungleichen Paar. Alle weiteren Elemente werden nicht verglichen.

Im folgenden Code wird die Verwendung von Seq.compareWith veranschaulicht:

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.")

Im vorherigen Code wird nur das erste Element berechnet und untersucht, und das Ergebnis ist –1.

Seq.countBy verwendet eine Funktion, die einen Wert generiert, der als Schlüssel für jedes Element bezeichnet wird. Für jedes Element wird ein Schlüssel generiert, indem diese Funktion für jedes Element aufgerufen wird. Anschließend gibt Seq.countBy eine Sequenz zurück, die die Schlüsselwerte und eine Anzahl der Elemente enthält, von denen die jeweiligen Schlüsselwerte generiert wurden.

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

Die Ausgabe lautet wie folgt.

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

Die vorherige Ausgabe zeigt, dass 34 Elemente der ursprünglichen Sequenz den Schlüssel 1 erzeugt haben, 33 Werte den Schlüssel 2 und 33 Werte den Schlüssel 0.

Durch Aufrufen von Seq.groupBy können Sie Elemente einer Sequenz gruppieren. Seq.groupBy verwendet eine Sequenz und eine Funktion, die einen Schlüssel aus einem Element generiert. Die Funktion wird für jedes Element der Sequenz ausgeführt. Seq.groupBy gibt eine Sequenz von Tupeln zurück, wobei das erste Element jedes Tupels der Schlüssel und das zweite Element eine Sequenz von Elementen ist, die diesen Schlüssel erzeugen.

Das folgende Codebeispiel zeigt, wie die Sequenz der Zahlen von 1 bis 100 mithilfe von Seq.groupBy in drei Gruppen partitioniert wird. Jede dieser Gruppen hat einen anderen Schlüsselwert: 0, 1 oder 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

Die Ausgabe lautet wie folgt.

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

Durch Aufrufen von Seq.distinct können Sie eine Sequenz erstellen, die doppelte Elemente entfernt. Alternativ können Sie Seq.distinctBy verwenden, die eine schlüsselgenerierende Funktion verwendet, die für jedes Element aufgerufen werden soll. Die resultierende Sequenz enthält Elemente der ursprünglichen Sequenz mit eindeutigen Schlüsseln. Spätere Elemente, die einen Schlüssel für ein früheres Element duplizieren, werden verworfen.

Das folgende Codebeispiel veranschaulicht die Verwendung von Seq.distinct. Seq.distinct wird veranschaulicht, indem Sequenzen generiert werden, die binäre Zahlen darstellen, und anschließend gezeigt wird, dass die einzigen unterschiedlichen Elemente 0 und 1 sind.

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

Der folgende Code veranschaulicht Seq.distinctBy. Es wird eine Sequenz gestartet, die negative und positive Zahlen enthält, und die Absolutwertfunktion wird als schlüsselgenerierende Funktion verwendet. Die resultierende Sequenz enthält keine positiven Zahlen, die den negativen Zahlen in der Sequenz entsprechen, da die negativen Zahlen früher in der Sequenz angezeigt und daher anstelle der positiven Zahlen ausgewählt werden, die denselben absoluten Wert oder Schlüssel haben.

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

Schreibgeschützte und zwischengespeicherte Sequenzen

Seq.readonly erstellt eine schreibgeschützte Kopie einer Sequenz. Seq.readonly eignet sich, wenn Sie eine Sammlung mit Lese-/Schreibzugriff haben, z. B. ein Array, und die ursprüngliche Sammlung nicht ändern möchten. Diese Funktion kann verwendet werden, um die Datenkapselung beizubehalten. Im folgenden Codebeispiel wird ein Typ erstellt, der ein Array enthält. Eine Eigenschaft macht das Array verfügbar. Anstatt jedoch ein Array zurückzugeben, wird eine Sequenz zurückgegeben, die mithilfe von Seq.readonly aus dem Array erstellt wird.

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 erstellt eine gespeicherte Version einer Sequenz. Verwenden Sie Seq.cache, um eine erneute Auswertung einer Sequenz zu vermeiden. Wenn Sie über mehrere Threads verfügen, die eine Sequenz verwenden, Sie jedoch sicherstellen müssen, dass jedes Element nur einmal aufgerufen wird, sollten Sie ebenfalls diese Funktion verwenden. Wenn eine Sequenz vorliegt, die von mehreren Threads verwendet wird, kann ein Thread zum Aufzählen und Berechnen der Werte für die ursprüngliche Sequenz verwendet werden, und die verbleibenden Threads verwenden die zwischengespeicherte Sequenz.

Ausführen von Berechnungen für Sequenzen

Einfache arithmetische Operationen sind mit denen von Listen vergleichbar, z. B. Seq.average, Seq.sum, Seq.averageBy, Seq.sumBy usw.

Seq.fold, Seq.reduce und Seq.scan sind mit den entsprechenden Funktionen vergleichbar, die für Listen verfügbar sind. Sequenzen unterstützen eine Teilmenge der vollständigen Variationen der Funktionen, die von Listen unterstützt werden. Weitere Informationen und Beispiele finden Sie unter Listen.

Weitere Informationen