목록(F#)
업데이트: 2010년 10월
F#의 목록은 순서가 지정된 일련의 변경 불가능한 요소 모음입니다. 목록에 포함된 요소의 형식은 동일해야 합니다.
목록 만들기 및 초기화
다음 코드 줄에서와 같이 세미콜론으로 구분하고 대괄호로 묶은 요소를 명시적으로 열거하여 목록을 정의할 수 있습니다.
let list123 = [ 1; 2; 3 ]
요소 사이에 줄 바꿈을 삽입할 수도 있습니다. 이 경우 세미콜론을 생략해도 됩니다. 이 나중 구문을 사용하면 요소 초기화 식이 길거나 각 요소에 대해 주석을 포함하고 싶을 때 코드를 더 읽기 쉽게 만들 수 있습니다.
let list123 = [
1
2
3 ]
일반적으로 목록의 요소는 형식이 모두 같아야 합니다. 그러나 요소가 기본 형식으로 지정된 목록에는 파생 형식인 요소가 포함될 수 있습니다. 따라서 다음과 같은 목록을 정의할 수 있습니다. Button과 CheckBox가 둘 다 Control에서 파생된 요소이기 때문입니다.
let myControlList : Control list = [ new Button(); new CheckBox() ]
다음 코드에서와 같이 범위 연산자(..)로 구분된 정수가 나타내는 범위를 사용하여 목록 요소를 정의할 수도 있습니다.
let list1 = [ 1 .. 10 ]
다음 코드에서와 같이 루프 구문을 사용하여 목록을 정의할 수도 있습니다.
let listOfSquares = [ for i in 1 .. 10 -> i*i ]
빈 목록을 지정하려면 아무것도 포함되지 않은 한 쌍의 대괄호를 사용합니다.
// An empty list.
let listEmpty = []
시퀀스 식을 사용하여 목록을 만들 수도 있습니다. 자세한 내용은 시퀀스의 "시퀀스 식"을 참조하십시오. 예를 들어 다음 코드에서는 1부터 10까지의 정수의 제곱으로 이루어진 목록을 만듭니다.
let squaresList = [ for i in 1 .. 10 -> i * i ]
목록 작업을 위한 연산자
::(cons) 연산자를 사용하여 목록에 요소를 첨부할 수 있습니다. list1이 [2; 3; 4]인 경우 다음 코드를 실행하여 얻는 list2는 [100; 2; 3; 4]가 됩니다.
let list2 = 100 :: list1
다음 코드에서와 같이 @ 연산자를 사용하여 형식이 서로 호환되는 목록을 연결할 수 있습니다. list1이 [2; 3; 4]이고 list2가 [100; 2; 3; 4 ]인 경우 다음 코드를 실행하여 얻는 list3은 [2; 3; 4; 100; 2; 3; 4]가 됩니다.
let list3 = list1 @ list2
목록에 대한 작업을 수행하는 함수는 List 모듈에 포함되어 있습니다.
F#의 목록은 변경할 수 없으므로 수정 작업을 수행하면 기존 목록이 수정되는 것이 아니라 새 목록이 생성됩니다.
F#의 목록은 단일 연결 목록으로 구현됩니다. 즉, 목록의 헤드에만 액세스하는 작업은 O(1)이고 요소 액세스는 O(n)입니다.
속성
목록 형식은 다음 속성을 지원합니다.
Property |
형식 |
설명 |
---|---|---|
'T |
첫째 요소입니다. |
|
'T list |
적합한 형식의 빈 목록을 반환하는 정적 속성입니다. |
|
bool |
목록에 요소가 없으면 true입니다. |
|
'T |
지정된 인덱스에 있는 요소입니다. 이 인덱스는 0부터 시작합니다. |
|
int |
요소 수입니다. |
|
'T list |
첫째 요소를 제외한 목록입니다. |
아래에는 이러한 속성을 사용한 몇 가지 예가 나와 있습니다.
let list1 = [ 1; 2; 3 ]
// Properties
printfn "list1.IsEmpty is %b" (list1.IsEmpty)
printfn "list1.Length is %d" (list1.Length)
printfn "list1.Head is %d" (list1.Head)
printfn "list1.Tail.Head is %d" (list1.Tail.Head)
printfn "list1.Tail.Tail.Head is %d" (list1.Tail.Tail.Head)
printfn "list1.Item(1) is %d" (list1.Item(1))
목록 사용
프로그래밍에 목록을 사용하면 적은 분량의 코드로도 복잡한 작업을 수행할 수 있습니다. 이 단원에서는 함수형 프로그래밍의 중요한 부분인 목록에 대한 일반적인 작업을 설명합니다.
목록 재귀
목록은 재귀 프로그래밍 기술에 특히 적합합니다. 목록의 각 요소에 대해 수행해야 할 작업이 있다고 가정해 봅시다. 목록의 헤드에 대해 작업을 수행한 다음 목록의 테일을 거치면서 다음 수준의 재귀를 반복하는 방식으로 이와 같은 작업을 되풀이하여 수행할 수 있습니다. 테일은 원래 목록에서 첫째 요소를 제외한 부분으로 이루어진 더 작은 목록입니다.
이와 같은 재귀 함수를 작성하려면 패턴 일치에 cons 연산자(::)를 사용합니다. 이렇게 하면 목록의 헤드를 테일로부터 구분할 수 있습니다.
다음 코드 예제에서는 패턴 일치를 사용하여 목록에 대한 작업을 수행하는 재귀 함수를 구현하는 방법을 보여 줍니다.
let rec sum list =
match list with
| head :: tail -> head + sum tail
| [] -> 0
목록이 길지 않으면 위 코드를 실행하는 데 문제가 없지만 목록이 큰 경우에는 스택이 오버플로될 수 있습니다. 다음 코드는 재귀 함수에 사용되는 표준 기술인 누적기 인수를 사용하여 위 코드를 개선한 것입니다. 누적기 인수를 사용하면 함수가 테일을 재귀 반복하도록 만들어 스택 공간을 절약할 수 있습니다.
let sum list =
let rec loop list acc =
match list with
| head :: tail -> loop tail (acc + head)
| [] -> acc
loop list 0
RemoveAllMultiples 함수는 목록 두 개를 취하는 재귀 함수입니다. 첫째 목록에는 해당 배수를 제거할 숫자가 포함되고, 둘째 목록에는 제거할 숫자가 포함됩니다. 다음 예제의 코드에서는 이 재귀 함수를 사용하여 목록에서 소수가 아닌 숫자를 모두 제거합니다. 그 결과로 소수로만 이루어진 목록을 구할 수 있습니다.
let IsPrimeMultipleTest n x =
x = n || x % n <> 0
let rec RemoveAllMultiples listn listx =
match listn with
| head :: tail -> RemoveAllMultiples tail (List.filter (IsPrimeMultipleTest head) listx)
| [] -> listx
let GetPrimesUpTo n =
let max = int (sqrt (float n))
RemoveAllMultiples [ 2 .. max ] [ 1 .. n ]
printfn "Primes Up To %d:\n %A" 100 (GetPrimesUpTo 100)
출력은 다음과 같습니다.
Primes Up To 100:
[2; 3; 5; 7; 11; 13; 17; 19; 23; 29; 31; 37; 41; 43; 47; 53; 59; 61; 67; 71; 73; 79; 83; 89; 97]
모듈 함수
List 모듈에는 목록의 요소에 액세스하기 위한 함수가 있습니다. 가장 빠르고 쉽게 액세스할 수 있는 요소는 헤드 요소입니다. 헤드에 액세스하려면 Head 속성을 사용하거나 모듈 함수 List.head를 사용합니다. 목록의 테일에는 Tail 속성을 사용하거나 List.tail 함수를 사용하여 액세스할 수 있습니다. 인덱스를 기준으로 요소를 찾으려면 List.nth 함수를 사용합니다. List.nth는 목록을 트래버스합니다. 따라서 이는 O(n)입니다. 코드에 List.nth가 자주 사용되는 경우 목록 대신 배열을 사용하는 방법도 생각해 볼 수 있습니다. 배열의 요소 액세스는 O(1)입니다.
목록에 대한 부울 작업
List.isEmpty 함수는 목록에 요소가 하나라도 있는지 여부를 확인합니다.
List.exists 함수는 목록의 요소에 부울 테스트를 적용하고 테스트를 충족하는 요소가 하나라도 있으면 true를 반환합니다. List.exists2도 이와 비슷하지만 두 목록의 연속적인 요소 쌍에 대해 작업을 수행한다는 점에서 차이가 있습니다.
다음 코드에서는 List.exists를 사용하는 방법을 보여 줍니다.
// Use List.exists to determine whether there is an element of a list satisfies a given Boolean expression.
// containsNumber returns true if any of the elements of the supplied list match
// the supplied number.
let containsNumber number list = List.exists (fun elem -> elem = number) list
let list0to3 = [0 .. 3]
printfn "For list %A, contains zero is %b" list0to3 (containsNumber 0 list0to3)
출력은 다음과 같습니다.
For list [0; 1; 2; 3], contains zero is true
다음 예제는 List.exists2의 사용을 보여 줍니다.
// Use List.exists2 to compare elements in two lists.
// isEqualElement returns true if any elements at the same position in two supplied
// lists match.
let isEqualElement list1 list2 = List.exists2 (fun elem1 elem2 -> elem1 = elem2) list1 list2
let list1to5 = [ 1 .. 5 ]
let list5to1 = [ 5 .. -1 .. 1 ]
if (isEqualElement list1to5 list5to1) then
printfn "Lists %A and %A have at least one equal element at the same position." list1to5 list5to1
else
printfn "Lists %A and %A do not have an equal element at the same position." list1to5 list5to1
출력은 다음과 같습니다.
Lists [1; 2; 3; 4; 5] and [5; 4; 3; 2; 1] have at least one equal element at the same position.
목록의 모든 요소가 조건을 만족하는지 여부를 테스트하려면 List.forall을 사용하면 됩니다.
let isAllZeroes list = List.forall (fun elem -> elem = 0.0) list
printfn "%b" (isAllZeroes [0.0; 0.0])
printfn "%b" (isAllZeroes [0.0; 1.0])
출력은 다음과 같습니다.
true
false
마찬가지로, 두 목록의 상응하는 위치에 있는 모든 요소가 각 요소 쌍에 관련된 부울 식을 충족하는지 여부를 확인하려면 List.forall2를 사용합니다.
let listEqual list1 list2 = List.forall2 (fun elem1 elem2 -> elem1 = elem2) list1 list2
printfn "%b" (listEqual [0; 1; 2] [0; 1; 2])
printfn "%b" (listEqual [0; 0; 0] [0; 1; 0])
출력은 다음과 같습니다.
true
false
목록에 대한 정렬 작업
List.sort, List.sortBy 및 List.sortWith 함수는 목록을 정렬합니다. 이 세 가지 함수 중 어느 것을 사용할지는 정렬 함수에 의해 결정됩니다. List.sort에는 기본 제네릭 비교가 사용됩니다. 제네릭 비교에서는 제네릭 비교 함수를 기반으로 하는 전역 연산자를 사용하여 값을 비교합니다. 이는 단순한 숫자 형식, 튜플, 레코드, 구분된 공용 구조체, 목록, 배열, IComparable을 구현하는 형식 등과 같은 매우 다양한 요소 형식에 적합합니다. IComparable을 구현하는 형식에 대해서는 제네릭 비교에 CompareTo 함수가 사용됩니다. 문자열을 대상으로 제네릭 비교를 수행할 수도 있지만 이 경우 문화권에 따른 정렬 순서가 사용됩니다. 함수 형식 등과 같은 지원되지 않는 형식에는 제네릭 비교를 사용하지 말아야 합니다. 또한 기본 제네릭 비교는 구조화된 형식의 크기가 작을 때 가장 좋은 성능을 발휘합니다. 구조화된 형식의 크기가 커서 비교 및 정렬 작업을 자주 수행해야 하는 경우에는 IComparable을 구현하고 CompareTo 메서드의 효율적인 구현을 제공하는 것이 더 좋습니다.
List.sortBy는 정렬 기준으로 사용되는 값을 반환하는 함수를 필요로 하며, List.sortWith는 비교 함수를 인수로 취합니다. 이 마지막 두 함수는 비교를 지원하지 않는 형식을 다뤄야 하거나 문화권의 영향을 받는 문자열의 경우와 같이 더 복잡한 비교 의미 체계가 필요한 작업에 유용합니다.
다음 예제는 List.sort의 사용을 보여 줍니다.
let sortedList1 = List.sort [1; 4; 8; -2; 5]
printfn "%A" sortedList1
출력은 다음과 같습니다.
[-2; 1; 4; 5; 8]
다음 예제는 List.sortBy의 사용을 보여 줍니다.
let sortedList2 = List.sortBy (fun elem -> abs elem) [1; 4; 8; -2; 5]
printfn "%A" sortedList2
출력은 다음과 같습니다.
[1; -2; 4; 5; 8]
다음 예제에서는 List.sortWith를 사용하는 방법을 보여 줍니다. 이 예제에서는 사용자 지정 비교 함수인 compareWidgets를 통해 사용자 지정 형식의 필드 하나를 먼저 비교한 다음 첫째 필드의 값이 같으면 다음 값을 비교합니다.
type Widget = { ID: int; Rev: int }
let compareWidgets widget1 widget2 =
if widget1.ID < widget2.ID then -1 else
if widget1.ID > widget2.ID then 1 else
if widget1.Rev < widget2.Rev then -1 else
if widget1.Rev > widget2.Rev then 1 else
0
let listToCompare = [
{ ID = 92; Rev = 1 }
{ ID = 110; Rev = 1 }
{ ID = 100; Rev = 5 }
{ ID = 100; Rev = 2 }
{ ID = 92; Rev = 1 }
]
let sortedWidgetList = List.sortWith compareWidgets listToCompare
printfn "%A" sortedWidgetList
출력은 다음과 같습니다.
[{ID = 92;
Rev = 1;}; {ID = 92;
Rev = 1;}; {ID = 100;
Rev = 2;}; {ID = 100;
Rev = 5;}; {ID = 110;
Rev = 1;}]
목록에 대한 검색 작업
목록에 대해 지원되는 검색 작업에는 여러 가지가 있습니다. 가장 단순한 List.find를 사용하면 주어진 조건에 일치하는 첫째 요소를 찾을 수 있습니다.
다음 코드 예제에서는 List.find를 사용하여 목록에서 5로 나누어지는 첫째 숫자를 찾는 방법을 보여 줍니다.
let isDivisibleBy number elem = elem % number = 0
let result = List.find (isDivisibleBy 5) [ 1 .. 100 ]
printfn "%d " result
결과는 5입니다.
요소를 먼저 변환해야 하는 경우 옵션을 반환하는 함수를 취하는 List.pick을 호출하여 Some(x)에 해당하는 첫째 옵션 값을 조회할 수 있습니다. List.pick은 요소를 반환하는 대신 결과 x를 반환합니다. 일치하는 요소를 찾지 못하면 List.pick은 KeyNotFoundException을 throw합니다. 다음 코드에서는 List.pick을 사용하는 방법을 보여 줍니다.
let valuesList = [ ("a", 1); ("b", 2); ("c", 3) ]
let resultPick = List.pick (fun elem ->
match elem with
| (value, 2) -> Some value
| _ -> None) valuesList
printfn "%A" resultPick
출력은 다음과 같습니다.
"b"
검색 작업의 또 다른 그룹에 속하는 List.tryFind와 그 관련 함수는 옵션 값을 반환합니다. List.tryFind 함수는 목록에서 조건을 충족하는 요소가 있는 경우 그 첫째 요소를 반환하고, 조건을 충족하는 요소가 없으면 옵션 값 None을 반환합니다. 여기서 변형된 함수인 List.tryFindIndex는 조건을 충족하는 요소가 있는 경우 요소 자체를 반환하지 않고 해당 인덱스를 반환합니다. 다음 코드에서는 이러한 함수를 보여 줍니다.
let list1d = [1; 3; 7; 9; 11; 13; 15; 19; 22; 29; 36]
let isEven x = x % 2 = 0
match List.tryFind isEven list1d with
| Some value -> printfn "The first even value is %d." value
| None -> printfn "There is no even value in the list."
match List.tryFindIndex isEven list1d with
| Some value -> printfn "The first even value is at position %d." value
| None -> printfn "There is no even value in the list."
출력은 다음과 같습니다.
The first even value is 22.
The first even value is at position 8.
목록에 대한 산술 작업
합이나 평균 같은 일반적인 산술 작업은 List 모듈에 기본 제공됩니다. List.sum을 사용하여 작업하려면 목록 요소 형식이 + 연산자를 지원해야 하고 0 값을 가져야 합니다. 모든 기본 제공 산술 형식은 이러한 조건을 충족합니다. List.average를 사용하여 작업하려면 요소 형식이 나머지 없는 나눗셈을 지원해야 합니다. 여기서 정수 형식은 제외되지만 부동 소수점 형식은 허용됩니다. List.sumBy 및 List.averageBy 함수는 함수를 매개 변수로 취합니다. 매개 변수로 사용되는 함수의 결과는 합이나 평균의 값을 계산하는 데 사용됩니다.
다음 코드에서는 List.sum, List.sumBy 및 List.average를 사용하는 방법을 보여 줍니다.
// Compute the sum of the first 10 integers by using List.sum.
let sum1 = List.sum [1 .. 10]
// Compute the sum of the squares of the elements of a list by using List.sumBy.
let sum2 = List.sumBy (fun elem -> elem*elem) [1 .. 10]
// Compute the average of the elements of a list by using List.average.
let avg1 = List.average [0.0; 1.0; 1.0; 2.0]
printfn "%f" avg1
출력은 1.000000입니다.
다음 코드에서는 List.averageBy를 사용하는 방법을 보여 줍니다.
let avg2 = List.averageBy (fun elem -> float elem) [1 .. 10]
printfn "%f" avg2
출력은 5.5입니다.
목록과 튜플
목록에 튜플이 포함되어 있으면 zip 및 unzip 함수를 사용하여 해당 목록을 조작할 수 있습니다. 이들 함수는 단일 값으로 이루어진 목록 두 개를 튜플로 이루어진 목록 한 개로 결합하거나 튜플로 이루어진 목록 한 개를 단일 값으로 이루어진 목록 두 개로 분리합니다. 가장 단순한 List.zip 함수는 요소가 한 개인 목록 두 개를 취하고 튜플 쌍으로 이루어진 단일 목록을 생성합니다. 다른 버전인 List.zip3은 요소가 한 개인 목록 세 개를 취하고 요소 세 개를 포함하는 튜플로 이루어진 단일 목록을 생성합니다. 다음 코드 예제에서는 List.zip을 사용하는 방법을 보여 줍니다.
let list1 = [ 1; 2; 3 ]
let list2 = [ -1; -2; -3 ]
let listZip = List.zip list1 list2
printfn "%A" listZip
출력은 다음과 같습니다.
[(1, -1); (2, -2); (3; -3)]
다음 코드 예제에서는 List.zip3을 사용하는 방법을 보여 줍니다.
let list3 = [ 0; 0; 0]
let listZip3 = List.zip3 list1 list2 list3
printfn "%A" listZip3
출력은 다음과 같습니다.
[(1, -1, 0); (2, -2, 0); (3, -3, 0)]
그에 상응하는 압축 해제 버전인 List.unzip 및 List.unzip3은 튜플 목록을 취하고 목록을 튜플 하나로 반환합니다. 여기서 첫째 목록에는 각 튜플의 첫째 항목이었던 요소가 모두 포함되고, 둘째 목록에는 각 튜플의 둘째 요소가 포함되는 방식입니다.
다음 코드 예제에서는 List.unzip을 사용하는 방법을 보여 줍니다.
let lists = List.unzip [(1,2); (3,4)]
printfn "%A" lists
printfn "%A %A" (fst lists) (snd lists)
출력은 다음과 같습니다.
([1; 3], [2; 4])
[1; 3] [2; 4]
다음 코드 예제에서는 List.unzip3을 사용하는 방법을 보여 줍니다.
let listsUnzip3 = List.unzip3 [(1,2,3); (4,5,6)]
printfn "%A" listsUnzip3
출력은 다음과 같습니다.
([1; 4], [2; 5], [3; 6])
목록 요소에 대한 작업
F#은 목록 요소에 대한 다양한 작업을 지원합니다. 가장 단순한 List.iter을 사용하면 목록의 모든 요소에 대해 함수를 호출할 수 있습니다. 여기서 변형된 함수로는 목록 두 개의 요소에 대해 작업을 수행하는 데 사용할 수 있는 List.iter2, List.iter과 비슷하지만 각 요소에 대해 호출되는 함수에 각 요소의 인덱스를 인수로 전달한다는 점에서 차이가 있는 List.iteri, List.iter2와 List.iteri의 기능을 결합한 것과 같은 List.iteri2가 있습니다. 다음 코드 예제에서는 이러한 함수를 보여 줍니다.
let list1 = [1; 2; 3]
let list2 = [4; 5; 6]
List.iter (fun x -> printfn "List.iter: element is %d" x) list1
List.iteri(fun i x -> printfn "List.iteri: element %d is %d" i x) list1
List.iter2 (fun x y -> printfn "List.iter2: elements are %d %d" x y) list1 list2
List.iteri2 (fun i x y ->
printfn "List.iteri2: element %d of list1 is %d element %d of list2 is %d"
i x i y)
list1 list2
출력은 다음과 같습니다.
List.iter: element is 1
List.iter: element is 2
List.iter: element is 3
List.iteri: element 0 is 1
List.iteri: element 1 is 2
List.iteri: element 2 is 3
List.iter2: elements are 1 4
List.iter2: elements are 2 5
List.iter2: elements are 3 6
List.iteri2: element 0 of list1 is 1; element 0 of list2 is 4
List.iteri2: element 1 of list1 is 2; element 1 of list2 is 5
List.iteri2: element 2 of list1 is 3; element 2 of list2 is 6
목록 요소를 변환하는 데 자주 사용되는 또 다른 함수로 List.map이 있습니다. 이 함수를 사용하면 목록의 각 요소에 함수를 적용하고 그 결과가 모두 포함된 새 목록을 만들 수 있습니다. List.map2와 List.map3은 여기서 변형된 함수로서 목록 여러 개에 대해 실행됩니다. 요소뿐 아니라 각 요소의 인덱스도 함수에 전달해야 하는 경우에는 List.mapi 및 List.mapi2를 사용할 수도 있습니다. List.mapi2와 List.mapi의 유일한 차이점은 List.mapi2가 목록 두 개를 대상으로 실행된다는 점입니다. 다음 예제에서는 List.map을 보여 줍니다.
let list1 = [1; 2; 3]
let newList = List.map (fun x -> x + 1) list1
printfn "%A" newList
출력은 다음과 같습니다.
[2; 3; 4]
다음 예제에서는 List.map2를 사용하는 방법을 보여 줍니다.
let list1 = [1; 2; 3]
let list2 = [4; 5; 6]
let sumList = List.map2 (fun x y -> x + y) list1 list2
printfn "%A" sumList
출력은 다음과 같습니다.
[5; 7; 9]
다음 예제에서는 List.map3을 사용하는 방법을 보여 줍니다.
let newList2 = List.map3 (fun x y z -> x + y + z) list1 list2 [2; 3; 4]
printfn "%A" newList2
출력은 다음과 같습니다.
[7; 10; 13]
다음 예제에서는 List.mapi를 사용하는 방법을 보여 줍니다.
let newListAddIndex = List.mapi (fun i x -> x + i) list1
printfn "%A" newListAddIndex
출력은 다음과 같습니다.
[1; 3; 5]
다음 예제에서는 List.mapi2를 사용하는 방법을 보여 줍니다.
let listAddTimesIndex = List.mapi2 (fun i x y -> (x + y) * i) list1 list2
printfn "%A" listAddTimesIndex
출력은 다음과 같습니다.
[0; 7; 18]
List.collect는 List.map과 비슷하지만 각 요소를 통해 목록이 생성되고 이렇게 만든 목록을 모두 연결하여 최종 목록 하나가 만들어진다는 점에서 차이가 있습니다. 다음 코드에서는 목록의 각 요소를 통해 숫자 세 개가 생성됩니다. 이렇게 생성한 모든 숫자가 목록 하나로 수집됩니다.
let collectList = List.collect (fun x -> [for i in 1..3 -> x * i]) list1
printfn "%A" collectList
출력은 다음과 같습니다.
[1; 2; 3; 2; 4; 6; 3; 6; 9]
부울 조건을 취하는 List.filter를 사용하여 주어진 조건을 충족하는 요소로만 이루어진 새 목록을 만들 수도 있습니다.
let evenOnlyList = List.filter (fun x -> x % 2 = 0) [1; 2; 3; 4; 5; 6]
결과 목록은 [2; 4; 6]입니다.
맵과 필터의 조합인 List.choose를 사용하면 요소를 동시에 변환하고 선택할 수 있습니다. List.choose는 옵션을 반환하는 함수를 목록의 각 요소에 적용하고 함수에서 반환하는 옵션 값이 Some인 요소에 대한 결과로 이루어진 새 목록을 반환합니다.
다음 코드에서는 List.choose를 사용하여 단어 목록에서 대문자로 시작하는 단어를 선택하는 방법을 보여 줍니다.
let listWords = [ "and"; "Rome"; "Bob"; "apple"; "zebra" ]
let isCapitalized (string1:string) = System.Char.IsUpper string1.[0]
let results = List.choose (fun elem ->
match elem with
| elem when isCapitalized elem -> Some(elem + "'s")
| _ -> None) listWords
printfn "%A" results
출력은 다음과 같습니다.
["Rome's"; "Bob's"]
여러 목록에 대한 작업
목록을 함께 조인할 수 있습니다. 목록 두 개를 하나로 조인하려면 List.append를 사용합니다. 세 개 이상의 목록을 조인하려면 List.concat를 사용합니다.
let list1to10 = List.append [1; 2; 3] [4; 5; 6; 7; 8; 9; 10]
let listResult = List.concat [ [1; 2; 3]; [4; 5; 6]; [7; 8; 9] ]
List.iter (fun elem -> printf "%d " elem) list1to10
printfn ""
List.iter (fun elem -> printf "%d " elem) listResult
폴드 및 스캔 작업
목록 작업 중에는 모든 목록 요소 사이의 상호 종속성에 관여하는 것이 있습니다. 폴드 및 스캔 작업은 각 요소에 대해 함수를 호출한다는 점에서 List.iter 및 List.map과 같지만 이들 작업은 연산을 통해 정보를 얻는 누적기라는 추가 매개 변수를 제공합니다.
목록에 대한 계산을 수행하려면 List.fold를 사용합니다.
다음 코드 예제에서는 List.fold를 사용하여 다양한 작업을 수행하는 방법을 보여 줍니다.
누적기 acc는 목록을 따라 이동하면서 계산이 진행됨에 따라 전달되는 값입니다. 첫째 인수는 누적기와 목록 요소를 취하고 해당 목록 요소에 대한 계산의 중간 결과를 반환합니다. 둘째 인수는 누적기의 초기 값입니다.
let sumList list = List.fold (fun acc elem -> acc + elem) 0 list
printfn "Sum of the elements of list %A is %d." [ 1 .. 3 ] (sumList [ 1 .. 3 ])
// The following example computes the average of a list.
let averageList list = (List.fold (fun acc elem -> acc + float elem) 0.0 list / float list.Length)
// The following example computes the standard deviation of a list.
// The standard deviation is computed by taking the square root of the
// sum of the variances, which are the differences between each value
// and the average.
let stdDevList list =
let avg = averageList list
sqrt (List.fold (fun acc elem -> acc + (float elem - avg) ** 2.0 ) 0.0 list / float list.Length)
let testList listTest =
printfn "List %A average: %f stddev: %f" listTest (averageList listTest) (stdDevList listTest)
testList [1; 1; 1]
testList [1; 2; 1]
testList [1; 2; 3]
// List.fold is the same as to List.iter when the accumulator is not used.
let printList list = List.fold (fun acc elem -> printfn "%A" elem) () list
printList [0.0; 1.0; 2.5; 5.1 ]
// The following example uses List.fold to reverse a list.
// The accumulator starts out as the empty list, and the function uses the cons operator
// to add each successive element to the head of the accumulator list, resulting in a
// reversed form of the list.
let reverseList list = List.fold (fun acc elem -> elem::acc) [] list
printfn "%A" (reverseList [1 .. 10])
여기서 변형된 함수로서 함수 이름에 숫자가 포함된 버전의 함수는 여러 목록에 대해 작업을 수행합니다. 예를 들어 List.fold2는 목록 두 개에 대해 계산을 수행합니다.
다음 예제는 List.fold2의 사용을 보여 줍니다.
// Use List.fold2 to perform computations over two lists (of equal size) at the same time.
// Example: Sum the greater element at each list position.
let sumGreatest list1 list2 = List.fold2 (fun acc elem1 elem2 ->
acc + max elem1 elem2) 0 list1 list2
let sum = sumGreatest [1; 2; 3] [3; 2; 1]
printfn "The sum of the greater of each pair of elements in the two lists is %d." sum
List.fold와 List.scan은 List.fold의 경우 추가 매개 변수의 최종 값을 반환하는 반면 List.scan의 경우 추가 매개 변수의 중간 값과 최종 값으로 이루어진 목록을 반환한다는 점에서 차이가 있습니다.
이들 각 함수에는 방향이 반대인 버전이 있습니다. 예를 들어 List.foldBack은 목록을 이동하는 순서와 인수의 순서가 반대라는 점에서 차이가 있습니다. 또한 List.fold와 List.foldBack에는 각각 List.fold2와 List.foldBack2라는 변형된 함수가 있습니다. 이들 함수는 길이가 같은 목록 두 개를 취합니다. 각 요소에 대해 실행되는 함수에서 두 목록의 상응하는 요소를 사용하여 일부 작업을 수행할 수 있습니다. 두 목록의 요소 형식은 다음 예제에서와 같이 서로 달라도 됩니다. 다음 예제에서 한 목록은 은행 계좌의 거래 금액을 포함하고, 다른 목록은 예금 또는 인출 같은 거래 형식을 포함합니다.
// Discriminated union type that encodes the transaction type.
type Transaction =
| Deposit
| Withdrawal
let transactionTypes = [Deposit; Deposit; Withdrawal]
let transactionAmounts = [100.00; 1000.00; 95.00 ]
let initialBalance = 200.00
// Use fold2 to perform a calculation on the list to update the account balance.
let endingBalance = List.fold2 (fun acc elem1 elem2 ->
match elem1 with
| Deposit -> acc + elem2
| Withdrawal -> acc - elem2)
initialBalance
transactionTypes
transactionAmounts
printfn "%f" endingBalance
합계 같은 계산에 대해 List.fold와 List.foldBack을 사용한 결과는 동일합니다. 이러한 계산에서는 진행 순서가 바뀐다고 해서 결과가 달라지지 않기 때문입니다. 다음 예제에서는 List.foldBack을 사용하여 목록의 요소를 더합니다.
let sumListBack list = List.foldBack (fun acc elem -> acc + elem) list 0
printfn "%d" (sumListBack [1; 2; 3])
// For a calculation in which the order of traversal is important, fold and foldBack have different
// results. For example, replacing fold with foldBack in the listReverse function
// produces a function that copies the list, rather than reversing it.
let copyList list = List.foldBack (fun elem acc -> elem::acc) list []
printfn "%A" (copyList [1 .. 10])
다음 예제에서는 은행 계좌 예제를 다시 살펴봅니다. 이번에는 이자 계산이라는 새 거래 형식을 추가했습니다. 이 경우 최종 잔고는 거래 순서에 따라 달라집니다.
type Transaction2 =
| Deposit
| Withdrawal
| Interest
let transactionTypes2 = [Deposit; Deposit; Withdrawal; Interest]
let transactionAmounts2 = [100.00; 1000.00; 95.00; 0.05 / 12.0 ]
let initialBalance2 = 200.00
// Because fold2 processes the lists by starting at the head element,
// the interest is calculated last, on the balance of 1205.00.
let endingBalance2 = List.fold2 (fun acc elem1 elem2 ->
match elem1 with
| Deposit -> acc + elem2
| Withdrawal -> acc - elem2
| Interest -> acc * (1.0 + elem2))
initialBalance2
transactionTypes2
transactionAmounts2
printfn "%f" endingBalance2
// Because foldBack2 processes the lists by starting at end of the list,
// the interest is calculated first, on the balance of only 200.00.
let endingBalance3 = List.foldBack2 (fun elem1 elem2 acc ->
match elem1 with
| Deposit -> acc + elem2
| Withdrawal -> acc - elem2
| Interest -> acc * (1.0 + elem2))
transactionTypes2
transactionAmounts2
initialBalance2
printfn "%f" endingBalance3
List.reduce 함수는 List.fold 및 List.scan과 비슷한 부분이 있지만, List.reduce의 경우 별도의 누적기를 차례로 전달하는 대신 요소 형식의 인수 한 개가 아닌 두 개를 취하는 함수를 사용한다는 점에서 차이가 있습니다. 즉, 이 함수는 계산의 중간 결과를 저장합니다. List.reduce는 처음 두 목록 요소에 대해 작업을 시작한 후 연산의 결과를 다음 요소와 함께 사용합니다. 자체 형식의 누적기가 별도로 있지 않으므로 누적기와 요소 형식이 동일한 형식일 때만 List.fold 대신 List.reduce를 사용할 수 있습니다. 다음 코드에서는 List.reduce를 사용하는 방법을 보여 줍니다. 제공된 목록에 요소가 없으면 List.reduce에서 예외가 throw됩니다.
다음 코드에서 람다 식을 처음 호출할 때는 2와 4를 인수로 사용하여 6이 반환되고, 다음 호출에서는 처음 계산 결과인 6과 다음 요소인 10이 인수로 주어지므로 결과는 16이 됩니다.
let sumAList list =
try
List.reduce (fun acc elem -> acc + elem) list
with
| :? System.ArgumentException as exc -> 0
let resultSum = sumAList [2; 4; 10]
printfn "%d " resultSum
목록과 기타 컬렉션 형식 사이의 변환
List 모듈에는 시퀀스와 배열을 목록으로 변환하거나 목록을 시퀀스와 배열로 변환하는 함수가 있습니다. 목록과 시퀀스 사이의 변환에는 List.toSeq 또는 List.ofSeq를 사용합니다. 목록과 배열 사이의 변환에는 List.toArray 또는 List.ofArray를 사용합니다.
기타 작업
목록에 대해 수행할 수 있는 기타 작업에 대한 자세한 내용은 라이브러리 참조 항목인 Collections.List 모듈(F#)을 참조하십시오.
참고 항목
참조
기타 리소스
변경 기록
날짜 |
변경 내용 |
이유 |
---|---|---|
2010년 10월 |
코드 예제 중 하나의 출력이 수정되었습니다. |
고객 의견 |
2011년 4월 |
속성 단원에서 Empty 속성에 대한 정보가 수정되었습니다. |
고객 의견 |