SqueakByExample:9.5
Collection iterators
스몰토크 루프(loops)와 조건식들은(conditionals) 단순히 컬렉션들에 발송된 메시지들이고 또는 정수들 또는 블록들과 같은 다른 오브젝트들 입니다. (3장을 보십시오) 처음 숫자부터 마지막 숫자에 걸쳐 있는 인수로 블록을 평가하는 to:do:와 같은 저급 레벨(low-level) 메시지 뿐만 아니라 스몰토크 컬렉션 계층도 (the Smalltalk collection hierarchy)는 다양한 고급 레벨(high-level) iterators를 제공합니다. 이러한 iterators를 사용은, 여러분의 코드를 좀더 견고하고 간결하게 만들어 드릴 것입니다.
Iterating (do:)
메소드 do:는 기본 켤렉션 iterator입니다. 이것은 그 자체의 인수(단일 인수를 취하는 블록)를 수신자(the receiver)의 각 구성요소에 적용합니다. 다음 예시는 수신자에 들어 있는 모든 문자열을 transcript에 인쇄합니다.
#('bob' 'joe' 'toto') do: [:each | Transcript show: each; cr].
변수 do:without:, doWithIndex:와 reverseDo::Do와 같은 변수 계열이 있습니다. Indexed 컬렉션들(Array, OrderedCollection, SortedCollectino)을 위해, 메소드 doWithIndex: 역시 현재 index에 대한 접근을 제공합니다. 이 메소드는 클래스 Number에서 정의되는 to:do와 관련됩니다.
#('bob' 'joe' 'toto') doWithIndex: [:each :i | (each = 'joe') ifTrue: [ ↑ i ] ] ⇒ 2
reverseDo:는 정렬된 컬렉션들에 작용하여, 컬력션을 반대 순서로 나타나게 합니다.
다음 코드는 흥미로운 메시지 do:separatedBy:를 보여드립니다. 이 메시지는 두 개의 구성요소 사이에 있는 두 번째 블록만을 실행합니다.
res := ''.
#('bob' 'joe' 'toto') do: [:e | res := res, e ] separatedBy: [res := res, '.'].
res ⇒ 'bob.joe.toto'
이 코드는, 중간 문자열을 만들기 때문에, 특별히 효과적이지 않으며, 결과를 버퍼하기 위한 스트림(stream)을 사용하는 것이 나을 것이라는 사실을 주목해 주십시오. (10장을 보십시오)
String streamContents: [:stream | #('bob' 'joe' 'toto') asStringOn: stream delimiter: '.' ] ⇒ 'bob.joe.toto'
Dictionaries. 메시지 do:가 dicionary로 발송되면 accoount에 취해진 구성요소들은 value들이 되며 association이 되지 않습니다. 사용해야 할 적합한 메소드들은 keysDo:, valuesDo: 와 associationsDo:이며, 이것들은 각각 Keys, values 또는 associations에서 반복실행(iterate)됩니다.
colors := Dictionary newFrom: { #yellow --> Color yellow. #blue --> Color blue. #red --> Color red }.
colors keysDo: [:key | Transcript show: key; cr]. "displays the keys"
colors valuesDo: [:value | Transcript show: value;cr]. "displays the values"
colors associationsDo: [:value | Transcript show: value;cr]. "displays the associations"
Collecting results (collect:)
만약 여러분이 do:를 사용하는 것 보다, 컬렉션의 구성요소들을 처리 하고, 결과로서 새로운 컬렉션을 만드시기를 좀 더 원하신다면, 아마도 collect: 또는 다른 iterator 메소드들 중의 하나를 사용하시는 것이 보다 나은 방법일 것입니다. 이것들 중 대부분을 켈렉션과 그 자체의 서브클래스들 중, 열거 프로토콜(enumerating protocol)에서 발견하실 수 있습니다.
우리가 다른 컬렉션에서 두 배의 구성요소들을 포함한 컬렉션을 원한다고 상상해 보십시오. Do:를 사용하여, 우리는 반드시 다음 표현식을 작성해야만 합니다.
double := OrderedCollection new.
#(1 2 3 4 5 6) do: [:e | double add: 2 * e].
double ⇒ an OrderedCollection(2 4 6 8 10 12)
메소드 collect:는 각 구성요소들을 위해 그 자체의 인수 블록(argument block)을 실행하고 결과들을 포함하고 있는 새로운 컬렉션을 리턴합니다. 대신, collect:를 사용하면 코드는 훨씬 단순해 집니다:
#(1 2 3 4 5 6) collect: [:e | 2 * e] ⇒ #(2 4 6 8 10 12)
do: 에 비교해 볼 때, collect:의 장점들은 우리가 정수들의 컬렉션을 취하고 이 정수들의 절대 값들의 컬렉션을 결과로서 만들어낸 다음 예시에서 좀더 극명하게 보입니다.
aCol := #( 2 --3 4 --35 4 --11).
result := aCol species new: aCol size.
1 to: aCol size do: [ :each | result at: each put: (aCol at: each) abs].
result ⇒ #(2 3 4 35 4 11)
위의 표현식과는 반대로 다음 표현식은 훨씬 단순합니다.
#( 2 --3 4 --35 4 --11) collect: [:each | each abs ] ⇒ #(2 3 4 35 4 11)
두 번째 솔루션의 좀더 많은 장점은, 그것이 bags의 세트들에도 작동될 것이라는 것입니다.
여러분이 일반적으로 컬렉션의 각 구성요소들에 메시지를 발송하기를 원하시지 않는다면, do: 사용을 피하시는 것이 바람직합니다.
메시지 collect: 발송은 수신자(the receiver)로서 동일한 종류의 컬렉션을 리턴한다는 사실에 주의하여 주십시오. 이러한 이유 때문에, 다음 코드가 실패하였습니다. (문자열은 정수 값들을 보유하지 않습니다)
'abc' collect: [:ea | ea asciiValue ] "error!"
대신에 우리는 문자열을 배열 또는 OrderedCollection으로 변환하였습니다.
'abc' asArray collect: [:ea | ea asciiValue ] ⇒ #(97 98 99)
실제로 collect:는 수신자(the receive)와 정확히 동일한 클래스의 컬렉션(collection) 리턴을 보장하지 않으며, 오직 동일한 “종들(species)” 만을 리턴할 뿐입니다. 인터벌의 경우, 실제로 유사종은 배열(Array)입니다 !
(1 to: 5) collect: [ :ea | ea * 2 ] ⇒ #(2 4 6 8 10)
구성요소들을 선택하고 거부하기 (Selecting and rejecting elements)
select:는 특별한 조건을 만족시키는 수신자(the receiver)의 구성요소들을 리턴합니다:
(2 to: 20) select: [:each | each isPrime] ⇒ #(2 3 5 7 11 13 17 19)
reject:는 반대되는 작업을 수행합니다:
(2 to: 20) reject: [:each | each isPrime] ⇒ #(4 6 8 9 10 12 14 15 16 18 20)
detect:로 구성요소 식별하기
메소드 detect:는 블록 인수(block argument)를 매치하는 수신자(the receive)의 첫 번째 구성요소를 리턴 합니다.
'through' detect: [:each | each isVowel] ⇒ $o
메소드 detect:ifNone은 메소드 detect:의 변수입니다. 이것의 두 번째 블록은, 그 블록과 매칭되는 구성요소가 없을 때, 평가됩니다.
Smalltalk allClasses detect: [:each | '*java*' match: each asString] ifNone: [ nil ] ⇒ nil
inject:into:로 결과들을 모으기
기능적 프로그래밍 언어들은, 종종 몇 가지 바이너리 연산자(binary operator)를 컬렉션의 모든 구성요소에 반복적으로 적용하여 결과를 모으기 위해 fold 또는 reduce라고 지칭되는 좀더 높은 수준의 정렬 기능(higher-order function)을 제공합니다 스퀵에서는 이 기능이 Collection»inject:into:에 의해 수행될 수 있습니다.
첫 번째 인수는 초기 값이며 두 번째 인수는 지금까지의 결과와 각각의 구성요소들에 차례대로 적용된 2개의 인수 블록입니다.
inject:into:의 사소한 어플리케이션은 숫자들의 컬렉션들의 합을 생산합니다. 스퀵에서는 가우스를 따라하여, 첫 번째 100 개의 정수들의 총합을 구하기 위해 이 표현식을 작성할 수 있습니다:
(1 to: 100) inject: 0 into: [:sum :each | sum + each ] ⇒ 5050
다른 예시는 분수들을 계산하는 다음 1 인수 블록(one-argument block)입니다:
factorial := [:n | (1 to: n) inject: 1 into: [:product :each | product * each ] ].
factorial value: 10 ⇒ 3628800
다른 메시지들
count: 메시지 count:는 조건식(condition)을 만족시키는 약간의 구성요소들을 리턴합니다. 조건식은 불리언 블록(Boolean block)으로 표시됩니다.
Smalltalk allClasses count: [:each | 'Collection*' match: each asString ] ⇒ 2
includes: 메시지 includes:는 인수가 컬렉션에 포함되었는지의 여부를 점검합니다.
colors := {Color white . Color yellow. Color red . Color blue . Color orange}.
colors includes: Color blue. ⇒ true
anySatisfy: 메시지 anySatisfy:는 만약 적어도 1개의 컬렉션 구성요소가 인수에 의해 제시된 조건식(the condition)을 만족시킬 경우 true로 답변합니다.
colors anySatisfy: [:c | c red > 0.5] ⇒ true