SqueakByExample:9.5

From 흡혈양파의 번역工房
Jump to navigation Jump to search

컬렉션의 반복자(Collection iterators)

스몰토크에서 반복문이나 조건문은, 컬렉션, 정수, 블록과같은 객체에 대한 단순한 메시지 전송입니다(3 장을 참고해주세요). 스몰토크의 콜렉션계층은, 초기값부터 마지막값까지 변화하는 수치를 인자로 블록을 평가하는 to:do: 같은 저수준 메시지를 포함한 여러가지 고수준의 반복자iterator[1]를 제공합니다. 이런 반복자의 사용은, 프로그램의 코드를 좀 더 튼튼하고 간결하게 만들어 줍니다.


Iterating (do:)

do: 메서드는 기본적인 컬렉션 반복자 메서드입니다. do: 메서드는 그 자체의 인자(단일 인자를 취하는 블록)를 수신자의 각 요소에 순서대로 적용합니다. 다음 예제는 수신자에 들어 있는 모든 문자열을 Transcript 에 print 합니다.

#('bob' 'joe' 'toto') do: [:each | Transcript show: each; cr].


변종(Variants)

do: 메서드에는 많은 변종이 있는데, 예를들면 do:without:, doWithIndex:, reverseDo: 등이 해당되겠죠. indexed 컬렉션(Array, OrderedCollection, SortedCollectino) 을 위한 doWithIndex: 메서드는 현재 index 에 대한 접근등을 제공합니다. 이 doWithIndex: 메서드는 Number 클래스에서 정의된 to:do: 와 관련이 있습니다.

#('bob' 'joe' 'toto') doWithIndex: [:each :i | (each = 'joe') ifTrue: [  i ] ]        2

orderd collection 의 reverseDo:는 컬렉션의 정렬을 역순으로 처리합니다.


아래의 코드에서 do:separatedBy: 라는 재미있는 메서드를 볼 수 있습니다. 이 메시지는 두개의 요소 사이를 대상으로 두번째 인자로 주어진 블록을 실행합니다.

res := ''.
#('bob' 'joe' 'toto') do: [:e | res := res, e ] separatedBy: [res := res, '.'].
res        'bob.joe.toto'


위의 방법은 효율적이라고 볼 수는 없으며, 결과를 buffer 에 쓰기 위해서는 stream 을 사용하는게 더 좋은 방법이라는걸 주의해주세요(10 장을 참고).

String streamContents: [:stream | #('bob' 'joe' 'toto') asStringOn: stream delimiter: '.' ]        'bob.joe.toto'


Dictionaries.

메시지 do: 가 dictionary 로 전송될때 검사되는 요소는 value 가 되며 association연관은 되지 않습니다.이런 경우 keysDo:, valuesDo: associationsDo: 등을 사용하는것이 더 적당하며, 각 메서드들은 Keys, values 또는 associations 에 대해 반복 실행됩니다.

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: 또는 그 외의 반복자 메서드를 이용하는것이 보다 좋은 방법이 될겁니다. 이런 메서드의 대부분은, Collection 이나 서브클래스에서 열거enumerating 프로토콜쪽에서 찾을 수 있습니다.

예를들어 각 요소의 2 배로 하는 새로운 컬렉션을 만든다고 생각해 보겠습니다. 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: 메서드는, 인자로 건네받은 블록을 각 요소에 대해 실행하고, 결과를 수용하는 새로운 콜렉션을 반환합니다. do: 대신에 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)


두 번째 방법이 더 좋은 이유는, 이런 방법이 Set 이나 Bag 에서도 동일하게 작동할거라는 점입니다.

일반적으로 컬렉션의 각 요소들에 메시지를 발송하는것을 원하지 않는다면, do: 사용을 피하는 것이 바람직합니다.

메시지 collect: 를 송신하면 수신자와 동일한 종류의 컬렉션을 반환한다는 사실에 주의해주세요. 이러한 이유 때문에, 다음 코드는 실패하게 됩니다. (String 은 Integer 값들을 가지고 있지 않습니다.)

'abc' collect: [:ea | ea asciiValue ] "error!"


그대신 String 을 Array 또는 OrderedCollection 으로 변환해서 처리할 수 있습니다.

'abc' asArray collect: [:ea | ea asciiValue ]        #(97 98 99)

실제로 collect: 는 수신자와 정확히 동일한 클래스에 대한 컬렉션 반환을 보장하지 않으며 오직 동일한 "종(species)" 만을 반환합니다. Interval 의 경우 유사한 종류는 사실 Array 입니다.

(1 to: 5) collect: [ :ea | ea * 2 ]        #(2 4 6 8 10)


요소들을 선택하고 거부하기

select: 메서드는 특별한 조건을 만족시키는 수신자의 요소들을 반환합니다:

(2 to: 20) select: [:each | each isPrime]        #(2 3 5 7 11 13 17 19)


reject: 메서드는 select: 와는 반대되는 작업을 수행합니다:

(2 to: 20) reject: [:each | each isPrime]        #(4 6 8 9 10 12 14 15 16 18 20)


detect: 로 요소를 식별하기

detect: 메서드는 수신자의 요소중에서 주어진 블록 인자를 만족하는 첫번째 요소를 반환합니다.

'through' detect: [:each | each isVowel]        $o


메서드 detect:ifNone 은 메서드 detect: 의 변종입니다. 이 메서드의 두 번째 인자(블록)은, 첫번째 블록에서 일치되는 요소가 없을 때, 실행됩니다.

Smalltalk allClasses detect: [:each | '*java*' match: each asString] ifNone: [ nil ]        nil


inject:into:로 결과들을 모으기

함수형 프로그래밍 언어들은, 자주 몇 가지 이항 연산자를 컬렉션의 모든 구성요소에 반복적으로 적용해서 결과를 받기 위해 fold 또는 reduce 라고 지칭되는 좀 더 고수준의 정렬 기능을 제공합니다. 스퀵에서 이런 기능은 Collection>>inject:into: 를 사용하면 됩니다.

이 메서드의 첫 번째 인자는 초기 값이며, 두 번째 인자는 지금까지의 결과와 각각의 구성 요소에 차례대로 적용된 2 개의 인자 블록이 됩니다.

inject:into: 의 간단한 예로서 숫자들의 컬렉션에 대한 총합을 계산해 보겠습니다. 가우스(Gauss) 처럼, 스퀵에서는 1 에서 부터 시작한 100 까지의 정수Integer들의 총합을 구하기 위해 아래와 같은 표현식을 작성하면 됩니다:

(1 to: 100) inject: 0 into: [:sum :each | sum + each ]        5050


또다른 예로서, 분수들을 계산할때 1 개의 인자 블록을 쓴다면 다음과 같이 작성할 수 있겠죠:

factorial := [:n | (1 to: n) inject: 1 into: [:product :each | product * each ] ].
factorial value: 10        3628800


그 외의 메시지들

count: 메시지 count: 는 조건식을 만족시키는 요소의 개수를 반환합니다. 조건식은 Boolean 블록으로 작성합니다.

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 개의 컬렉션 요소가 인자에 의해 제시된 조건식을 만족시킬 경우 true 로 답변합니다.

colors anySatisfy: [:c | c red > 0.5]        true


Notes

  1. Iterator 는 한국어로 대부분 반복자라고 부릅니다만, wikipedia 의 경우에는 객체지향 프로그래밍에서 container 에 제공되는것이라고 언급합니다. 이런 컨테이너는 사실 smalltalk 에서는 컬렉션이라고 할만하죠. 여기서는 일반적으로 한국에서 사용하듯이 반복자 라는 용어를 사용하도록 하겠습니다.