시퀀스(F#)

업데이트: 2010년 5월

시퀀스는 일련의 요소로 이루어진 논리적 모음입니다. 시퀀스에 포함된 요소의 형식은 모두 동일해야 합니다. 시퀀스는 순서가 지정된 많은 양의 데이터로 이루어진 컬렉션이 있지만 해당 요소 중 일부만 주로 사용할 때 특히 유용합니다. 개별 시퀀스 요소는 필요할 때만 계산되므로 일부 요소만 필요한 상황에서 목록 대신 시퀀스를 사용하면 성능을 더 향상시킬 수 있습니다. 시퀀스는 IEnumerable<T>의 별칭인 seq<'T> 형식으로 표현됩니다. 따라서 System.IEnumerable을 구현하는 .NET Framework 형식은 무엇이든 시퀀스로 사용할 수 있습니다. Seq 모듈에서는 시퀀스가 관련된 조작을 지원합니다.

시퀀스 식

시퀀스 식은 계산 결과가 시퀀스인 식입니다. 시퀀스 식은 여러 가지 형태를 취할 수 있습니다. 가장 단순한 것으로는 범위를 지정하는 형식이 있습니다. 예를 들어 seq { 1 .. 5 }는 끝점 1과 5를 포함하여 다섯 개의 요소로 이루어진 시퀀스를 만듭니다. 두 개의 이중 마침표 사이에 증가분(또는 감소분)을 지정할 수도 있습니다. 예를 들어 다음 코드에서는 10의 배수로 이루어진 시퀀스를 만듭니다.

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

시퀀스 식은 시퀀스의 값을 생성하는 F# 식으로 이루어집니다. 여기에 yield 키워드를 사용하여 시퀀스에 포함되는 값을 생성할 수 있습니다.

예를 들면 다음과 같습니다.

seq { for i in 1 .. 10 do yield i * i }

yield 대신 -> 연산자를 사용할 수 있습니다. 이 경우 다음 예제에서와 같이 do 키워드를 생략할 수 있습니다.

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

다음 코드에서는 표를 나타내는 배열 색인과 함께 좌표 쌍으로 이루어진 목록을 생성합니다.

let (height, width) = (10, 10)
seq { for row in 0 .. width - 1 do
         for col in 0 .. height - 1 do
           yield (row, col, row*width + col)
    }

if 식은 시퀀스에서 필터로 사용됩니다. 예를 들어 int -> bool 형식의 isprime 함수가 있다고 할 때 소수로만 이루어진 시퀀스를 생성하려면 다음과 같이 시퀀스를 작성합니다.

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

반복에 yield 또는 ->를 사용하는 경우 각 반복을 통해 시퀀스의 요소가 하나씩 생성됩니다. 각 반복을 통해 여러 요소로 이루어진 시퀀스를 생성하려면 yield!를 사용합니다. 이 경우 각 반복에서 생성되는 요소를 연결하여 최종 시퀀스가 만들어집니다.

시퀀스 식에서 여러 개의 식을 함께 결합할 수 있습니다. 이 경우 각 식을 통해 생성되는 요소가 서로 연결됩니다. 예제를 보려면 이 항목의 "예제" 단원을 참조하십시오.

예제

첫째 예제에서는 반복, 필터 및 yield가 포함된 시퀀스 식을 사용하여 배열을 생성합니다. 이 코드를 실행하면 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 yield n }
for x in aSequence do
    printfn "%d" x

다음 코드에서는 yield를 사용하여 곱셈표를 만듭니다. 이 표는 요소가 세 개인 튜플로 구성되며 그 각각은 인수 두 개와 곱으로 이루어집니다.

let multiplicationTable =
  seq { for i in 1..9 do
            for j in 1..9 do
               yield (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

시퀀스 사용

시퀀스는 목록에서 지원하는 여러 가지 기능을 동일하게 지원합니다. 시퀀스는 키 생성 함수를 사용하여 그룹화 및 카운트 세기 등과 같은 연산도 지원합니다. 시퀀스는 하위 시퀀스 추출을 위한 더욱 다양한 함수도 지원합니다.

열거 가능한 컬렉션인 목록, 배열, 집합, 맵 등과 같은 대부분의 데이터 형식이 암시적 시퀀스입니다. 시퀀스를 인수로 취하는 함수는 IEnumerable<T>을 구현하는 .NET Framework 데이터 형식 외에도 임의의 공통 F# 데이터 형식과 함께 사용할 수 있습니다. 이와 달리 목록을 인수로 취하는 함수에는 목록만 사용할 수 있습니다. seq<'a> 형식은 IEnumerable<'a>의 형식 약어입니다. 즉, F#의 배열, 목록, 집합 및 맵과 대부분의 .NET Framework 컬렉션 형식이 속하는 제네릭 IEnumerable<T>을 구현하는 형식은 무엇이든 seq 형식과 호환되며 시퀀스가 필요한 곳 어디서나 사용할 수 있습니다.

모듈 함수

Microsoft.FSharp.Collections 네임스페이스Seq 모듈에는 시퀀스와 함께 사용하기 위한 함수가 포함되어 있습니다. 목록, 배열, 맵 및 집합은 모두 열거 가능한 형식이며 시퀀스로 취급 가능하므로 이러한 형식에도 해당 함수를 사용할 수 있습니다.

시퀀스 만들기

시퀀스를 만드는 데는 앞서 설명한 것처럼 시퀀스 식을 사용할 수도 있고 특정 함수를 사용할 수도 있습니다.

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.ofArraySeq.ofList<'T> 함수(F#)를 사용하면 배열과 목록으로부터 시퀀스를 만들 수 있습니다. 또는 캐스트 연산자를 사용하여 배열과 목록을 시퀀스로 변환할 수도 있습니다. 다음 코드에서는 두 방법을 모두 보여 줍니다.

// 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에 정의된 것과 같은 약한 형식의 컬렉션으로부터 시퀀스를 만들 수 있습니다. 이와 같은 약한 형식의 컬렉션은 요소 형식이 Object이며 제네릭이 아닌 IEnumerable<T> 형식을 사용하여 열거됩니다. 다음 코드에서는 Seq.cast를 사용하여 ArrayList를 시퀀스로 변환하는 방법을 보여 줍니다.

open System
let mutable arrayList1 = new System.Collections.ArrayList(10)
for i in 1 .. 10 do arrayList1.Add(10) |> ignore
let seqCast : seq<int> = Seq.cast arrayList1

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 값을 반환하여 시퀀스를 종료할 수 있습니다. 다음 코드에서는 unfold 연산을 통해 생성된 seq1과 fib라는 두 가지 시퀀스의 예를 보여 줍니다. 첫째 시퀀스인 seq1은 100까지의 숫자로 이루어진 단순한 시퀀스입니다. 둘째 시퀀스인 fib에서는 unfold를 사용하여 피보나치 수열을 계산합니다. 피보나치 수열의 각 요소는 그 앞에 있는 피보나치 숫자 두 개의 합이므로 상태 값은 시퀀스에서 바로 앞 숫자 두 개로 이루어진 튜플입니다. 초기 값은 이 수열의 처음 두 숫자인 (1,1)입니다.

let seq1 = Seq.unfold (fun state -> if (state > 20) then None else Some(state, state + 1)) 0
printfn "The sequence seq1 contains numbers from 0 to 20."
for x in seq1 do printf "%d " x
let fib = Seq.unfold (fun state ->
    if (snd state > 1000) then None
    else Some(fst state + snd state, (snd state, fst state + snd state))) (1,1)
printfn "\nThe sequence fib contains Fibonacci numbers."
for x in fib do printf "%d " x

출력은 다음과 같습니다.

시퀀스 seq1에 0부터 20까지의 숫자가 포함되어 있습니다.

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

시퀀스 fib에 피보나치 숫자가 포함되어 있습니다.

2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597

다음 코드에서는 지금까지 설명한 여러 가지 시퀀스 모듈 함수를 사용하여 길이에 제한이 없는 시퀀스의 값을 생성하고 계산하는 예를 보여 줍니다. 이 코드를 실행하는 데는 몇 분 정도 걸릴 수 있습니다.

// infiniteSequences.fs
// 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 series is the series of reciprocals of whole numbers.
let harmonicSeries = generateInfiniteSequence (fun index -> float index) false
// 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 =
    Seq.unfold (fun state ->
        let subtotal = snd state + Seq.nth (fst state + 1) sequence
        if (fst state >= length) then None
        else Some(subtotal,(fst state + 1, subtotal))) (0, 0.0)

// 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.tryFindSeq.tryFindIndex 함수 기능을 시퀀스에서도 지원합니다. 이에 상응하여 시퀀스에 사용할 수 있는 버전의 함수는 검색 대상인 요소까지만 시퀀스를 계산합니다. 예제를 보려면 목록을 참조하십시오.

시퀀스 가져오기

Seq.filterSeq.choose는 이에 상응하여 목록에 사용되는 버전의 함수와 같지만 시퀀스 요소를 계산할 때까지는 필터링과 선택이 이루어지지 않는다는 점에서 차이가 있습니다.

Seq.truncate 함수는 다른 시퀀스로부터 새 시퀀스를 만들지만 시퀀스를 지정된 수의 요소로만 제한합니다. Seq.take 함수는 시퀀스의 시작 지점부터 지정된 수의 요소만 포함하는 새 시퀀스를 만듭니다. 가져오도록 지정한 요소 수보다 시퀀스의 요소 수가 더 적으면 Seq.take에서 InvalidOperationException이 throw됩니다. Seq.truncate의 경우 지정한 수보다 요소 수가 적더라도 오류가 발생하지 않는다는 점에서 Seq.take와 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.takeWhile을 사용하면 조건자 함수(부울 함수)를 지정하고 다른 시퀀스로부터 새 시퀀스를 만들 수 있습니다. 이렇게 만드는 시퀀스는 원래 시퀀스의 요소 중 조건자가 true가 되게 하는 요소로 이루어집니다. 조건자의 반환 결과가 false인 요소를 처음으로 발견하면 해당 요소 앞에서 시퀀스기 종료됩니다. Seq.skip은 다른 시퀀스의 시작 지점부터 지정된 수의 요소를 건너뛰고 나머지 요소로 이루어진 시퀀스를 반환합니다. Seq.skipWhile은 조건자의 반환 결과가 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

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.zipSeq.zip3은 시퀀스 두 개 또는 세 개를 사용하여 튜플로 이루어진 시퀀스 하나를 생성합니다. 이들 함수는 목록에 사용되는 상응하는 버전의 함수와 같습니다. 이러한 함수의 기능에 대응하여 시퀀스 하나를 두 개 이상의 시퀀스로 분할하는 기능은 없습니다. 시퀀스에 대해 이와 같은 기능이 필요하면 시퀀스를 목록으로 변환한 후 List.unzip을 사용해야 합니다.

정렬, 비교 및 그룹화

목록에 대해 지원되는 정렬 함수를 시퀀스에도 사용할 수 있습니다. 여기에 해당하는 함수로는 Seq.sortSeq.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; printfn ""
let seqResult = Seq.countBy (fun elem -> if elem % 3 = 0 then 0
                                         elif elem % 3 = 1 then 1
                                         else 2) mySeq1

printSeq seqResult

출력은 다음과 같습니다.

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

위 출력을 보면 원래 시퀀스에서 키 1을 생성한 요소가 34개이고, 키 2를 생성한 값이 33개, 키 0을 생성한 값이 33개임을 알 수 있습니다.

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; printfn ""
let sequences3 = Seq.groupBy (fun index ->
                                if (index % 3 = 0) then 0
                                  elif (index % 3 = 1) then 1
                                  else 2) sequence
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 ""
printfn "Original sequence: "
printSeq inputSequence
printfn "\nSequence with distinct absolute values: "
let seqDistinctAbsoluteValue = Seq.distinctBy (fun elem -> abs elem) inputSequence
seqDistinctAbsoluteValue |> printSeq

읽기 전용 시퀀스 및 캐시된 시퀀스

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.reduceSeq.scan은 이에 상응하여 목록에 사용되는 버전의 함수와 같습니다. 시퀀스는 목록에서 지원하는 이러한 함수에 대한 모든 변형의 하위 집합을 지원합니다. 자세한 내용과 예제를 보려면 목록(F#)을 참조하십시오.

참고 항목

참조

IEnumerable<T>

기타 리소스

F# 언어 참조

F# 형식

변경 기록

날짜

변경 내용

이유

2010년 5월

향상된 일부 코드 예.

향상된 기능 관련 정보