SqueakByExample:10.3

From 흡혈양파의 번역工房
Revision as of 02:09, 22 February 2013 by Onionmixer (talk | contribs) (용어수정)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

컬렉션을 통한 스트리밍

스트림은 구성요소들의 컬렉션들을 다룰 때 매우 유용합니다. 스트림은 컬렉션에서 구성요소들을 읽고 쓰기 위해 사용될 수 있습니다. 우리는 이제 컬렉션들을 위한 스트림의 특징들을 탐구해 볼 것입니다.


컬렉션 읽기

이 섹션은 컬렉션을 읽기 위해 사용하는 기능을 소개할 것입니다. 스트림을 사용하여 컬렉션을 읽는 작업은, 여러분에게 본질적으로 컬렉션으로 들어가는 포인터를 제공해 드릴 것입니다. 이 포인터는 읽기를 할 때 앞으로 이동될 것이며, 여러분은 그 포인터를 여러분이 원할 때마다 이동시킬 수 있습니다. 클래스 ReadStream은 컬렉션으로부터 구성요소들을 읽기 위해 사용되어야 합니다.

메서드 next와 next: 는 컬렉션으로부터 한 개 또는 그 이상의 구성요소들을 복구하기 위해 사용합니다 .

stream := ReadStream on: #(1 (a b c) false).
stream next.        1
stream next.        #(#a #b #c)
stream next.        false
stream := ReadStream on: 'abcdef'.
stream next: 0.        ''
stream next: 1.        'a'
stream next: 3.        'bcd'
stream next: 2.        'ef'


메시지 peek는 여러분이 앞으로 진행하지 않은 채, 스트림에서 다음 구성요소가 무엇인지를 알기 원하실 때 사용합니다.

stream := ReadStream on: '--143'.
negative := (stream peek = $--). "look at the first element without reading it"
negative.        true
negative ifTrue: [stream next].
"ignores the minus character"
number := stream upToEnd.
number.        '143'


이 코드는 스트림에 있는 숫자 신호에 따라 Boolean 변수를 음수로, 그리고 숫자를 절대 값으로 설정합니다. 메서드 upToEnd는 현재 위치로부터 스트림의 끝부분에 모든 것을 리턴하고 스트림을 그것의 끝 부분에 설정합니다. 이 코드는 peekFor:를 사용하여 단순화될 수 있으며, 이것은 다음 구성요소가 파라미터와 동등할 경우 앞으로 이동하며, 동등하지 않을 경우 이동하지 않습니다.

stream := '--143' readStream.
(stream peekFor: $--)        true
stream upToEnd        '143'


peekFor: 는 또한 파라미터가 구성요소와 동일할 경우를 가리키는 Boolean을 리턴합니다.

여러분은 아마 위의 예시에서 스트림을 구현하는 새로운 방법을 알아내셨을 것입니다: 사용자는 그 특정한 컬렉션에서 읽기 스트림을 얻기 위해 readStream을 sequenceable 컬렉션에 보낼 수 있습니다.


Positioning(위치선정). 스트림 포인터의 위치를 선정하기 위한 메서드들이 있습니다. 여러분이 색인을 갖고 있으시다면, 위치를 사용하여 그 색인에 직접 이동할 수 있습니다. 여러분은 위치을 사용하여 현재 위치를 요청할 수 있습니다. 스트림이 구성요소에 위치해 있지 않고 2 개의 구성요소 사이에 위치한다는 것을 꼭 기억해 주십시오. 스트림의 시작시에 대응하는 색인 위치는 0입니다.

여러분은 다음 코드로 그림 10.4에 그려진 스트림의 상태를 얻을 수 있습니다.

stream := 'abcde' readStream.
stream position: 2.
stream peek        $c
그림 10.4 위치 2에 있는 스트림


시작과 끝에 스트림을 배치시키기 위해, 여러분은 reset 또는 seetToEnd를 사용할 수 있습니다. skip과 skipTo는 현재 위치에 비례한 위치로 나아가는 작업에 사용됩니다: skepTo:가 그 자체 파라미터와 동등한 구성요소를 찾을 때까지 스트림에 있는 모든 구성요소를 건너뛰는 반면에, skip:은 인수로서 숫자를 수락하고 구성요소들의 숫자를 스킵 합니다. 매칭된 구성요소 뒤에, 스트림이 위치된다는 것을 염두해 주십시오.

stream := 'abcdef' readStream.
stream next.                                   $a    "stream is now positioned just after the a"
stream skip: 3.                                        "stream is now after the d"
stream position.                               4
stream skip: --2.                                      "stream is after the b"
stream position.                               2
stream reset.
stream position.                               0
stream skipTo: $e.                                      "stream is just after the e now"
stream next.                                    $f
stream contents.                               'abcdef'


여러분이 보실 수 있듯이 글자 e를 건너뛰었습니다.

메서드 컨텐츠는 항상 전체 스트림의 복사본을 리턴합니다.

Testing. 몇 가지 메서드들은 여러분으로 하여금 현재 스트림의 상태 테스트를 할 수 있게 해드립니다: isEmpty는 컬렉션에 구성요소들이 없거나 없는 경우에 한정하여 true를 리턴하는 반면에, atEnd는 구성요소들이 더 이상 읽혀질 수 없거나 읽혀질 수 없는 경우에 한정하여 true를 리턴합니다.

여기에, 파라미터로서 2 개의 분류된 컬렉션들를 취하거나 컬렉션들을 다른 분류된 컬렉션 속으로 병합시키는 atEnd를 사용하는, 가능한 알고리즘의 실행이 있습니다.

stream1 := #(1 4 9 11 12 13) readStream.
stream2 := #(1 2 3 4 5 10 13 14 15) readStream.

"The variable result will contain the sorted collection."
result := OrderedCollection new.
[stream1 atEnd not & stream2 atEnd not]
  whileTrue: [stream1 peek < stream2 peek
   "Remove the smallest element from either stream and add it to the result."
   ifTrue: [result add: stream1 next]
   ifFalse: [result add: stream2 next]].

"One of the two streams might not be at its end. Copy whatever remains."
result
  addAll: stream1 upToEnd;
  addAll: stream2 upToEnd.

result.        an OrderedCollection(1 1 2 3 4 4 5 9 10 11 12 13 13 14 15)

컬렉션에 쓰기

우리는 ReadStream을 사용하여, 컬렉션의 구성요소에서 반복 진행을 통해 컬렉션을 읽는 방법을 이미 살펴보았습니다. 우리는 이제, WriteStreams를 사용하여 컬렉션을 읽는 방법을 학습할 것입니다.

WriteStreams은 다양한 위치에서 컬렉션에 많은 데이터를 덧붙이는 작업에 유용합니다. 이것은 종종 이 예제에서 정적 동적 부분들에 기초한 문자열을 구축하는 작업에 사용됩니다.

stream := String new writeStream.
stream
  nextPutAll: 'This Smalltalk image contains: ';
  print: Smalltalk allClasses size;
  nextPutAll: ' classes.';
  cr;
  nextPutAll: 'This is really a lot.'.
  
stream contents.        'This Smalltalk image contains: 2322 classes. This is really a lot.'


이 테크닉은 메서드 printOn: for example의 다른 실행에서 사용됩니다. 여러분이 스트림의 컨텐츠에만 관심이 있으시다면 스트림을 만드는 보다 더 단순하고 효과적인 방법이 있습니다.

string := String streamContents:
  [:stream |
    stream
    print: #(1 2 3);
    space;
    nextPutAll: 'size';
    space;
    nextPut: $=;
    space;
    print: 3. ].
string.        '#(1 2 3) size = 3'


메서드 streamContents:는 여러분을 위해 컬렉션 위에 컬렉션과 스트림을 만듭니다. 그 다음 여러분이 부여한 파라미터로서 스트림을 패스하는 블록을 실행합니다. 블록이 끝날 때, streamContents:는 컬렉션의 컨텐츠를 리턴합니다.

다음 WriteStream 메서드는 이 컨텍스트에서 특별히 유용합니다:

nextPut: 파라미터를 스트림에 추가합니다.

nextPutAll: 파라미터로서 패스된 컬렉션의 각 구성요소를 스트림에 추가합니다.

print: 파라미터의 문자 표현을 스트림에 추가합니다.

space, tab 그리고 CR(캐리지 리턴/개행문자)과 같이, 다른 종류의 문자들을 인쇄하기 위한 유용한 메서드들이 있습니다. 또 다른 유용한 메서드는, 마지막 문자가 공백이 아닐 경우 공백을 추가하여, 스트림에서 마지막 문자가 공백이 되도록 보장하는 ensureASpace입니다.


문자열 결합에 대하여. WriteStream에서 nextPut: 과 nextPutAll을 사용하는 것은 종종 문자를 연속되게 하는 최고의 방법입니다. 콤마 연속 연산자 (,)는 훨씬 덜 효과적입니다:

[| temp |
  temp := String new.
  (1 to: 100000)
    do: [:i | temp := temp, i asString, ' ']] timeToRun        115176 "(milliseconds)"

[| temp |
  temp := WriteStream on: String new.
  (1 to: 100000)
    do: [:i | temp nextPutAll: i asString; space].
  temp contents] timeToRun −→ 1262 "(milliseconds)"


스트림을 사용하는 것이 좀 더 효율적인 이유는 콤마가 수신자와 인수의 연결을 포함하고 있는 새로운 문자열을 만들어, 그것이 둘 모두를 복사해야 하기 때문입니다. 여러분이 반복적으로 동일한 수신자에 연결시키면, 매번 길이가 더 늘어나게 되므로 문자의 개수들이 틀림없이 기하 급수적으로 복제될 것입니다. 스트림 사용은 또한, 가비지를 만들며, 이 가비지는 반드시 모아져야 합니다. 문자열 연결 대신에 스트림을 사용하는 것은 잘 알려진 최적화입니다. 실제로, 여러분은 이 작업을 돕기 위해 streamContents (223페이지에서 언급)를 사용하실 수 있습니다:

String streamContents: [ :tempStream |
  (1 to: 100000)
    do: [:i | tempStream nextPutAll: i asString; space]]

동시에 읽고 쓰기

동시에 읽고 쓰기 위해 컬렉션에 접근한다면 스트림을 사용할 수 있습니다. 여러분이 웹브라우저에서 앞으로 버튼과 뒤로 버튼을 관리하기 위해 History class를 만들려는 상황을 상상해 보십시오. History는 수치 10.5에서 10.11까지, 그림과 같이 반응할 것입니다.


그림 10.5 새로운 History가 비었습니다. 웹브라우저에 표시한 내용은 아무것도 없습니다.


그림 10.6: 사용자는 1페이지를 엽니다.


이 동작은 ReadWriteStream을 사용하여 실행될 수 있습니다.


그림 10.7: 사용자는 2페이지에 대한 링크를 클릭합니다.


그림 10.8: 사용자는 3페이지에 대한 링크를 클릭합니다.


그림 10.9: 사용자는 뒤로 가기 버튼을 클릭합니다. 2페이지를 다시 보고 있습니다.


그림 10.10: 사용자는 뒤로 가기 버튼을 다시 클릭합니다. 1페이지가 지금 나타났습니다.


그림 10.11: 1페이지에서 부터, 사용자는 4페이지에 대한 링크를 클릭합니다. History는 2페이지와 3페이지를 잊어버립니다.


Object subclass: #History
  instanceVariableNames: 'stream'
  classVariableNames: ''
  poolDictionaries: ''
  category: 'SBE--Streams'

History>>initialize
  super initialize.
  stream := ReadWriteStream on: Array new.


여기서 어려운 것은 전혀 없습니다. 이제 스트림을 포함하고 있는 새로운 클래스를 정의합니다. 초기화 메서드를 실행하는 동안에 스트림을 만듭니다.

앞으로 또는 뒤로 가기 위한 메서드가 필요합니다:

History>>goBackward
  self canGoBackward ifFalse: [self error: 'Already on the first element'].
  stream skip: --2.
   self next.

History>>goForward
  self canGoForward ifFalse: [self error: 'Already on the last element'].
   stream next


지금까지의 코드는 꽤 간단했습니다. 이제 사용자가 링크를 클릭했을 때, 반드시 활성화해야 할 goTo: 메서드를 다루어야 합니다. 가능한 솔루션은:

History>>goTo: aPage
  stream nextPut: aPage.


이럼에도 불구하고 이 버전은 완전하지 않습니다. 그 이유는 사용자가 링크를 클릭할 때, 앞으로 나아가야 할 미래의 페이지들이 더 이상 없어야 하기 때문입니다. 예를 들어 앞으로 버튼은 반드시 비활성화 되어야 합니다. 이 작업을 수행하기 위한 가장 단순한 해결 방법은 history end를 나타낸 바로 후에 nil을 기록하는 것입니다.

History>>goTo: anObject
  stream nextPut: anObject.
  stream nextPut: nil.
  stream back.

이제 오직 canGoBackward 와 canGoForward만 실행해야 합니다.

스트림은 항상 2 개의 구성요소 사이에 배치됩니다. 뒤로 가기전에, 현재 위치 전에 반드시 2개의 페이지가 있어야 합니다. 한 개의 페이지는 현재 페이지고, 다른 페이지는 우리가 가기 원하는 페이지입니다.

History>>canGoBackward
   stream position > 1
  
History>>canGoForward
   stream atEnd not and: [stream peek notNil]


스트림의 컨텐츠에 있는 peek에 메서드를 추가하겠습니다:

History>>contents
   stream contents


History는 알려진 대로 동작합니다:

History new
  goTo: #page1;
  goTo: #page2;
  goTo: #page3;
  goBackward;
  goBackward;
  goTo: #page4;
  contents        #(#page1 #page4 nil nil)

Notes