SqueakByExample:10.3: Difference between revisions

From 흡혈양파의 번역工房
Jump to navigation Jump to search
(SBE 컬렉션스트리밍작업하기 페이지 추가)
 
(번역수정)
 
(8 intermediate revisions by the same user not shown)
Line 1: Line 1:
==컬렉션(collections) 스트리밍 작업하기==
==컬렉션에 대한 stream 처리==


스트림은 구성요소들의 컬렉션들을 다룰 때 매우 유용합니다. 스트림은 컬렉션에서 구성요소들을 읽고 쓰기 위해 사용될 수 있습니다. 우리는 이제 컬렉션들을 위한 스트림의 특징들을 탐구해 볼 것입니다.  
stream 은 컬렉션을 다루는데 매우 유용합니다. stream 은 컬렉션에서 요소들을 읽고 쓰는 작업을 위해 사용할 수 있습니다. 이제부터 컬렉션을 다룰때 필요한 stream 의 특징을 자세히 알아보도록 하겠습니다.




===컬렉션 읽기===
===컬렉션 읽기===


이 섹션은 컬렉션을 읽기 위해 사용된 특징들을 제시할 것입니다. 스트림(stream)사용하여 컬렉션을 읽는 작업은, 여러분에게 본질적으로 컬렉션(the collection)으로 들어가는 포인터(pointer)를 제공해 드릴 것입니다. 이 포인터는 읽기를 할 때 앞으로 이동될 것이며, 여러분은 그 포인터를 여러분이 원할 때마다 이동시킬 수 있습니다. 클래스 ReadStream은 컬렉션으로부터 구성요소들을 읽기 위해 사용되어야만 합니다.  
여기서는 컬렉션에 대한 읽기작업을 할 때 사용되는 기능을 소개하겠습니다. 컬렉션을 읽는 작업에 stream 을 사용한다는것은, 본질적으로는 컬렉션의 포인터(요소에 대한 Pointer)를 얻는다는것을 의미합니다. 이 포인터는 읽기를 하면 다음 방향으로 이동되며, 그 포인터를 사용자가 원할 때 원하는 위치로 이동시킬 수도 있습니다. ReadStream 클래스를 사용해서 컬렉션으로부터 요소들의 읽기작업을 할 수 있습니다.  
 
next 메서드를 이용하면 컬렉션으로 부터 요소를 1 개 읽을 수 있으며, next: 메서드를 이용하면 원하는 개수만큼의 요소를 읽어낼 수 있습니다.  


메소드 next와 next: 는 컬렉션으로부터 한 개 또는 그 이상의 구성요소들을 복구하기 위해 사용됩니다 .
<syntaxhighlight lang="smalltalk">
<syntaxhighlight lang="smalltalk">
stream := ReadStream on: #(1 (a b c) false).
stream := ReadStream on: #(1 (a b c) false).
Line 25: Line 26:
   
   


메시지 '''peek'''는 여러분이 앞으로 진행하지 않은 채, 스트림에서 다음 구성요소가 무엇인지를 알기 원하실 때 사용합니다.  
'''peek''' 메시지는 앞으로 진행하지 않은 채, stream 에서 다음 구성요소가 무엇인지 알고싶을때 사용합니다.  
 
<syntaxhighlight lang="smalltalk">
<syntaxhighlight lang="smalltalk">
stream := ReadStream on: '--143'.
stream := ReadStream on: '-143'.
negative := (stream peek = $--). "look at the first element without reading it"
negative := (stream peek = $-). "look at the first element without reading it"
negative.    ⇒    true
negative.    ⇒    true
negative ifTrue: [stream next].
negative ifTrue: [stream next].
Line 37: Line 39:
   
   


코드는 스트림(the stream)에 있는 숫자의 신호(sign)에 따라 불리언 변수를 음수(negative)로, 그리고 숫자를 절대 값으로 설정합니다. 메소드 upToEnd는 현재 위치로부터 스트림의 끝부분에 모든 것을 리턴하고 스트림을 그것의 끝 부분에 설정합니다. 코드는 peekFor:를 사용하여 단순화될 수 있으며, 이것은 만약 다음 구성요소가 파라미터와 동등할 경우 앞으로 이동하며, 동등하지 않을 경우 이동하지 않습니다.
위의 코드는 stream 에 숫자에 따라 얻어진 결과값이 - 라면 boolen 으로 값을 반환하고 결과값을 무조건 양수로 반환합니다. upToEnd 메서드는 현재 위치부터 stream 의 끝까지 있는 모든것을 반환하며, 위치를 stream 의 마지막 지점으로 설정합니다. 위의 코드는 peekFor: 를 사용해서 단순화 할 수 있으며, peakFor: 메서드는 stream 의 다음차례의 요소와 인수가 같은경우 stream 의 위치를 그 다음으로 이동시키며 진행하고, 인수와 다를때는 stream 의 위치를 이동시키지 않습니다.
 
<syntaxhighlight lang="smalltalk">
<syntaxhighlight lang="smalltalk">
stream := '--143' readStream.
stream := '-143' readStream.
(stream peekFor: $--)    ⇒    true
(stream peekFor: $-)    ⇒    true
stream upToEnd    ⇒    '143'
stream upToEnd    ⇒    '143'
</syntaxhighlight>
</syntaxhighlight>


   
   
'''peekFor:''' 는 또한 파라미터가 구성요소와 동일할 경우를 가리키는 불리언(Boolean)을 리턴합니다.
'''peekFor:''' 는 또한 인수가 요소와 동일한 경우, 그 결과를 Boolean 으로 반환합니다.


여러분은 아마 위의 예시에서 스트림(stream)구축하는 새로운 방법을 알아내셨을 것입니다: 유저는 그 특정한 컬렉션에서 읽기 스트림(a reading stream)얻기 위해 readStream을 sequenceable 컬렉션에 보낼 수 있습니다.
위의 예제에서 stream 을 만드는 새로운 방법을 눈치채셨나요: 순차 컬렉션에 대해 단지 readStream 을 보내는것 만으로도, 대상이 되는 컬렉션에 대한 read stream 을 만들 수 있습니다.




'''Positioning(위치선정).''' 스트림 포인터(the stream pointer)의 위치를 선정하기 위한 메소드들이 있습니다. 만약 여러분이 색인(index)갖고 있으시다면, 위치(position)를 사용하여 그 색인(index)에 직접 이동할 수 있습니다. 여러분은 위치(position)사용하여 현재 위치(position)를 요청할 수 있습니다. 스트림이 구성요소에 위치해 있지 않고 2 개의 구성요소 사이에 위치한다는 것을 기억해 주십시오. 스트림의 시작시에 대응하는 색인(index)은 0입니다.  
'''Positioning(위치선정).'''  
 
stream 에 대한 포인터의 위치를 설정하기 위한 메서드가 있습니다. 만약 index 를 알고 있으면 position: 메서드를 사용해서 해당되는 위치로 직접 이동할 수 있죠. position 을 이용하면 현재의 위치를 알 수 있습니다. stream 의 position 은 요소 자체가 아니라 2 개의 요소 사이라는것을 기억해주시기 바랍니다. stream 의 초기 index 상 위치는 0 입니다.
 
사용자는 아레의 코드를 이용해서 그림 10.4 에 그려진 stream 의 상태를 얻을 수 있습니다.


여러분은 다음 코드로 그림 10.4에 그려진 스트림의 상태를 얻을 수 있습니다.
<syntaxhighlight lang="smalltalk">
<syntaxhighlight lang="smalltalk">
stream := 'abcde' readStream.
stream := 'abcde' readStream.
Line 58: Line 64:
stream peek    ⇒    $c
stream peek    ⇒    $c
</syntaxhighlight>
</syntaxhighlight>
[[image:ab_cdeStef.png|none|910px|thumb|그림 10.4 위치(position) 2에 있는 스트림]]


[[image:ab_cdeStef.png|none|910px|thumb|그림 10.4 위치 2 에 있는 stream]]
stream 을 처음이나 맨 끝으로 이동시키려면, reset 또는 setToEnd 를 사용하면 됩니다. skip: 과 skipTo: 는 현재의 위치를 기준으로 다음위치로 이동하는 경우 사용합니다. skipTo: 는 건네받은 인수와 같은 요소를 찾을때까지 stream 에 있는 모든 요소를 건너뛰는 반면에, skip: 인 인수로 받은 숫자만큼 요소를 건너뜁니다. 매칭된 요소의 뒤쪽에 stream 의 포인터가 위치한다는걸 잊지 마시기 바랍니다.


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


여러분이 보실 수 있듯이 글자 e는 스킵되었습니다.
보시는 것처럼 글자 e 를 건너뛰었습니다.
 
contents 메서드는 항상 전체 stream 의 복사본을 반환합니다.
 
 
'''Testing.'''


메소드 컨텐츠는 항상 전체 스트림의 복사본을 리턴합니다.
몇가지 메서드를 이용하면 현재의 stream 상태를 테스트 할 수 있습니다: isEmpty 는 컬렉션이 가지고있는 요소가 없는경우 true 를 반환하는 반면, atEnd 는 컬렉션의 더이상 읽을 수 있는요소가 없는 경우 true 를 반환합니다.


'''Testing.''' 몇 가지 메소드들은 여러분으로 하여금 현재 스트림의 상태 테스트를 할 수 있게 해드립니다: isEmpty는 컬렉션에 구성요소들이 없거나 없는 경우에 한정하여 true를 리턴하는 반면에, atEnd는 구성요소들이 더 이상 읽혀질 수 없거나 읽혀질 수 없는 경우에 한정하여 true를 리턴합니다.  
알고리즘에서 atEnd 를 사용하는 경우를 살펴보도록 하겠습니다. 아래의 코드에서는 2 개의 정렬이 끝난 콜렉션을 인자로 받아, 이것들을 병합해서 정렬이 끝난 새로운 콜렉션으로 만듭니다.


여기에, 파라미터로서 2 개의 분류된 컬렉션들(sorted collections)를 취하거나 컬렉션들을 다른 분류된 컬렉션 속으로 병합시키는 atEnd를 사용하는, 가능한 알고리즘의 실행이 있습니다.
<syntaxhighlight lang="smalltalk">
<syntaxhighlight lang="smalltalk">
stream1 := #(1 4 9 11 12 13) readStream.
stream1 := #(1 4 9 11 12 13) readStream.
Line 105: Line 117:




===컬렉션에 쓰기===


우리는 ReadStream을 사용하여, 컬렉션의 구성요소에서 반복적용(iterating)을 함으로써 컬렉션을 읽는 방법을 이미 살펴보았습니다. 우리는 이제, WriteStreams를 사용하여 컬렉션(collections)을 읽는 방법을 학습할 것입니다.  
===컬렉션 쓰기===
 
ReadStream 을 사용해서, 컬렉션의 요소를 반복 진행을 통해 읽는 방법을 위에서 살펴봤습니다. 이제부터는 WriteStreams 를 사용해서 컬렉션을 만드는 방법을 배워보도록 하겠습니다.  
 
WriteStreams 은 컬렉션 안의 다양한 위치에서 많은 데이터를 덧붙이는 작업에 유용합니다. 아래의 예제처럼, 정적이거나 동적인 문자열을 생성하는 경우에 주로 사용됩니다:


WriteStreams은 다양한 위치에서 컬렉션에 많은 데이터를 덧붙이는 작업에 유용합니다. 이것은 종종 이 예제에서 정적 동적 부분(static parts, dynamic parts)들에 기초한 문자열을 구축하는 작업에 사용됩니다.
<syntaxhighlight lang="smalltalk">
<syntaxhighlight lang="smalltalk">
stream := String new writeStream.
stream := String new writeStream.
Line 123: Line 137:




이 테크닉은 메소드 printOn: for example의 다른 실행에서 사용됩니다. 만약 여러분이 스트림의 컨텐츠에만 관심이 있으시다면 스트림(stream)만드는 보다 더 단순하고 효과적인 방법이 있습니다.  
이런 기법은, 다양한 클래스의 printOn: 메서드의 구현에서 사용되고 있습니다. 단순히 String 을 생성해야하는 경우, stream 을 사용하게되면 조금 더 단순하고 효과적인 방법을 사용할 수 있습니다.
 
<syntaxhighlight lang="smalltalk">
<syntaxhighlight lang="smalltalk">
string := String streamContents:
string := String streamContents:
Line 139: Line 154:




메소드 streamContents:는 여러분을 위해 컬렉션 위에 컬렉션과 스트림을 만듭니다. 그 다음 여러분이 부여한 파라미터로서 스트림을 패스하는 블록을 실행합니다. 블록이 끝날 때, streamContents:는 컬렉션의 컨텐츠를 리턴합니다.  
메서드 streamContents: 는 컬렉션과 컬렉션을 이용한 stream 을 만듭니다. 이 메서드는 인자로 블록을 받아 실행하며, 인자로 주어질 블록에는 stream 이 들어가야 합니다. 블록이 종료되면, streamContents: 는 컬렉션의 내용을 반환합니다.
 
다음 WriteStream 메서드는 이런 컨텍스트에서 특별히 유용합니다:
 
'''nextPut:''' 인자로 받은 내용을 stream 에 추가합니다.


다음 WriteStream 메소드는 이 컨텍스트(context)에서 특별히 유용합니다:
'''nextPutAll:''' 인자로 받은 컬렉션의 각 구성요소를 stream 에 추가합니다.


'''nextPut:''' 파라미터를 스트림(stream)에 추가합니다.
'''print:''' 인자의의 텍스트표현을 stream 에 추가합니다.


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


'''print:''' 파라미터의 문자 표현(the textual representation)을 스트림에 추가합니다.
특수문자를 stream 에 표시하는데 편리한 '''space''', '''tab''' 그리고 CR<sup>carriage return</sup> 등의 메서드도 있습니다.  또 다른 유용한 메서드는, 마지막 문자가 공백이 아닐 경우 공백을 추가해서, stream 에서 마지막 문자가 공백이 되도록 보장하는 '''ensureASpace''' 입니다.


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




'''문자(열)의 결합에 대하여.''' WriteStream 에서 문자(열)을 연결하는 작업에 있어 가장 좋은 방법은 nextPut:, nextPutAll: 을 사용하는 것입니다. 콤마연산자(,) 를 이용한 문자(열)의 결합은 메서드를 사용하는것에 비교하면 상당히 비효율적인 방식입니다:


'''연속(Concatenation)에 관하여.''' WriteStream에서 nextPut: 과 nextPutAll을 사용하는 것은 종종 문자를 연속되게 하는 최고의 방법입니다. 콤마 연속 연산자 (,)[the comma concatenation operator (,)]는 훨씬 덜 효과적입니다:
<syntaxhighlight lang="smalltalk">
<syntaxhighlight lang="smalltalk">
[| temp |
[| temp |
Line 164: Line 181:
   (1 to: 100000)
   (1 to: 100000)
     do: [:i | temp nextPutAll: i asString; space].
     do: [:i | temp nextPutAll: i asString; space].
   temp contents] timeToRun −→ 1262 "(milliseconds)"
   temp contents] timeToRun   ⇒  1262 "(milliseconds)"
</syntaxhighlight>
</syntaxhighlight>




스트림을 사용하는 것이 좀더 효과적인 이유는 콤마가 수신자(the receive)와 인수의 연결(the concatenation)을 포함하고 있는 새로운 문자열을 만들어, 그것이 둘 모두를 복사해야 하기 때문입니다. 여러분이 반복적으로 동일한 수신자(receive)에 연결시키면(concatenate), 매번 길이가 더 늘어나게 되므로 문자의 개수들이 틀림없이 기하 급수적으로 복제되게 될 것입니다. 스트림 사용은 또한, 가비지(garbage)를 만들며, 이 가비지는 반드시 모아져야 합니다. 문자열 연결(String concatenation) 대신에 스트림을 사용하는 것은 잘 알려진 최적화 입니다. 실제로, 여러분은 이 작업을 돕기 위해 streamContents (223페이지에서 언급)를 사용하실 수 있습니다:
stream 을 사용하는 것이 보다 효율적인 이유는, 콤마연산자는 수신자와 인수를 연결하는 새로운 문자열을 만들기때문에, 수신자와 인수의 양쪽 모두를 복사하는 작업을 진행하게 되는 상황은 비효율적이기 때문입니다.
 
만약 같은 수신자에 대해서 콤마연산자로 연결을 반복하면 연결할때마다 문자열이 길어지기때문에, 복사해야하는 문자의 개수가 기하급수적으로 증가하게 되겠죠.  
 
그리고 이런식으로 콤마연산자를 이용한 문자열의 연결을 반복하게되면 메모리상에 쓰레기를 더 많이 남기게 되며, garbege collection 의 대상이 됩니다. 콤마연산자를 이용한 문자()연결 대신 stream 을 사용하는 방법은 유명한 최적화 방법입니다. 실제로 이런 작업을 위해서 streamContents<ref name="streamContents">223페이지에서 확인가능</ref> 사용하실 수 있습니다:
 
<syntaxhighlight lang="smalltalk">
<syntaxhighlight lang="smalltalk">
String streamContents: [ :tempStream |
String streamContents: [ :tempStream |
Line 175: Line 197:
</syntaxhighlight>
</syntaxhighlight>


===동시에 읽기와 쓰기===


동시에 읽기와 쓰기를 위해 컬렉션(collection)에 접근하기 위해 스트림(stream)을 사용하는 것이 가능합니다. 여러분이 웹브라우저(web browser)에서 앞으로 버튼(forward button)과 뒤로(backward) 버튼을 관리하기 위해 History class를 만들기를 원한다고 상상해 보십시오. History는 수치 10.5에서 10.11까지, 그림과 같이 반응할 것입니다.


===동시에 읽고 쓰기===


[[image:emptyStef.png|none|541px|thumb|그림 10.5 새로운 history가 비었습니다. 웹브라우저에 디스플레이된 것은 아무것도 없습니다. ]]
콜렉션에 접근해서 동시에 읽고 쓰기를 하려는경우 stream 을 사용하면 이런 작업이 가능합니다. web browser 에서 앞 버전과 뒤 버튼을 관리하기 위해 History 클래스를 만든다고 가정해 보겠습니다. History 클래스는 그림 10.5 에서 그림 10.11 까지의 내용과 같은 동작을 하게 될겁니다.




[[image:page1Stef.png|none|541px|thumb|그림 10.6: 유저는 page 1에 열리게 됩니다.]]
[[image:emptyStef.png|none|541px|thumb|그림 10.5 새로운 History가 비었습니다. 웹브라우저에 표시한 내용은 아무것도 없습니다.]]




이 동작은 ReadWriteStream을 사용하여 실행될 수 있습니다.
[[image:page1Stef.png|none|541px|thumb|그림 10.6: 사용자는 1페이지를 엽니다.]]




[[image:page2Stef.png|none|541px|thumb|그림 10.7: 유저는 page 2에 대한 링크를 클릭합니다.]]
[[image:page2Stef.png|none|541px|thumb|그림 10.7: 사용자는 2페이지에 대한 링크를 클릭합니다.]]




[[image:page3Stef.png|none|560px|thumb|그림 10.8: 유저는 Page 3에 대한 링크를 클릭합니다.]]
[[image:page3Stef.png|none|560px|thumb|그림 10.8: 사용자는 3페이지에 대한 링크를 클릭합니다.]]




[[image:page2_Stef.png|none|541px|thumb|그림 10.9: 유저는 뒤로 가기 버튼(the back button)을 클릭합니다. 그는 지금 page 2를 다시 보고 있습니다.]]
[[image:page2_Stef.png|none|541px|thumb|그림 10.9: 사용자는 뒤로 가기 버튼을 클릭합니다. 2페이지를 다시 보고 있습니다.]]




[[image:page1_Stef.png|none|541px|thumb|그림 10.10: 유저는 뒤로 가기 버튼(the back button)을 다시 클릭합니다. Page 1이 지금 디스플레이 되었습니다.]]
[[image:page1_Stef.png|none|541px|thumb|그림 10.10: 사용자는 뒤로 가기 버튼을 다시 클릭합니다. 1페이지가 지금 나타났습니다.]]




[[image:page4Stef.png|none|541px|thumb|그림 10.11: 1페이지에서 부터, 유저는 page 4에 대한 링크를 클릭합니다. History는 page 2와 3을 잊어버립니다.]]
[[image:page4Stef.png|none|541px|thumb|그림 10.11: 1페이지에서 부터, 사용자는 4페이지에 대한 링크를 클릭합니다. History는 2페이지와 3페이지를 잊어버립니다.]]


위의 그림같은 동작은 ReadWriteStream 을 사용하여 구현할 수 있습니다.


<syntaxhighlight lang="smalltalk">
<syntaxhighlight lang="smalltalk">
Line 217: Line 240:




여기서 어려운 것은 전혀 없습니다. 우리는 스트림(stream)포함하고 있는 새로운 클래스(class)를 정의합니다. 스트림(stream)초기화 메소드(the initialize method)를 실행하는 동안에 만들어 집니다.
여기에서 딱히 어려운 부분은 없습니다. 이제 stream 을 포함하는 새로운 클래스를 정의하도록 하겠습니다. stream 은 initialize 메서드를 통해 만들어집니다.
 
그 다음 앞 과 뒤 양쪽으로 이동하기 위한 메서드가 필요하겠죠:


우리는 앞으로 (forward) 뒤로(backward) 가기 위한 메소드들이 필요합니다:
<syntaxhighlight lang="smalltalk">
<syntaxhighlight lang="smalltalk">
History>>goBackward
History>>goBackward
Line 232: Line 256:




이때까지의, 코드는 꽤 간단하였습니다. 이제 우리는 유저가 링크를 클릭했을 때, 반드시 활성화되어야 할 goTo: 메소드를 다루어야만 합니다. 가능한 솔루션은:
지금까지의 코드는 꽤 간단합니다. 이제 유저가 링크를 클릭했을 때, 동작해야할 goTo: 메서드를 작업하도록 하겠습니다. 아래와 같은 방법도 가능합니다:
 
<syntaxhighlight lang="smalltalk">
<syntaxhighlight lang="smalltalk">
History>>goTo: aPage
History>>goTo: aPage
Line 238: Line 263:
</syntaxhighlight>
</syntaxhighlight>


 
하지만 지금까지의 작업이 완전한것은 아닙니다. 왜냐하면 유저가 링크를 클릭한 이후, 내부에서 앞 에 해당하는 페이지가 있어서는 안되기 때문입니다. 예를들어 앞 에 해당하는 버튼은 비활성화가 되어야 한다는거죠. 이런 작업에 대한 가장 단순한 해결책은 nil 을 사용해서 history 의 끝을 나타내면 됩니다.
그럼에도 불구하고 이 버전은 완전하지 않습니다. 그 이유는 유저가 링크를 클릭할 때, 앞으로 나아가야 할 미래의 페이지들이 더 이상 없어야 하기 때문입니다. 예를 들어 앞으로 버튼(forward button)은 반드시 비활성화 되어야 합니다. 이 작업을 수행하기 위한 가장 단순한 솔루션은 history end를 나타낸 바로 후에 nil을 쓰기 하는 것입니다.  
<syntaxhighlight lang="smalltalk">
<syntaxhighlight lang="smalltalk">
History>>goTo: anObject
History>>goTo: anObject
Line 248: Line 273:




이제 오직 canGoBackward 와 canGoForward만 실행되어야만 합니다.  
이제 canGoBackward 와 canGoForward 의 구현만 남았군요.
 
stream 은 항상 2 개의 요소 사이에 위치하게 됩니다. 뒤로 가는 작업을 하기위해서는 현재의 위치 앞쪽으로 2 개의 페이지가 있어야 합니다. 하나는 현재의 페이지고, 다른 하나는 이동을 원하는 페이지가 되겠죠.


스트림은 항상 2 개의 구성요소 사이에 배치됩니다. 뒤로 가기전에, 현재 위치(position) 전에 반드시 2개의 페이지(page)가 있어야 만 합니다. 한 개의 페이지(page)는 현재 페이지 이고, 다른 페이지는 우리가 가기 원하는 page 입니다.
<syntaxhighlight lang="smalltalk">
<syntaxhighlight lang="smalltalk">
History>>canGoBackward
History>>canGoBackward
Line 260: Line 286:




스트림(the stream)컨텐츠에 있는 peek에 메소드를 추가하겠습니다:
stream 의 내용을 확인하는 메서드를 추가해 보도록 하겠습니다:
 
<syntaxhighlight lang="smalltalk">
<syntaxhighlight lang="smalltalk">
History>>contents
History>>contents
Line 267: Line 294:




History는 알려진 대로 동작합니다:
History 는 예상대로 작동합니다:
 
<syntaxhighlight lang="smalltalk">
<syntaxhighlight lang="smalltalk">
History new
History new
Line 278: Line 306:
   contents    ⇒    #(#page1 #page4 nil nil)
   contents    ⇒    #(#page1 #page4 nil nil)
</syntaxhighlight>
</syntaxhighlight>


==Notes==
==Notes==

Latest revision as of 12:16, 14 September 2013

컬렉션에 대한 stream 처리

stream 은 컬렉션을 다루는데 매우 유용합니다. stream 은 컬렉션에서 요소들을 읽고 쓰는 작업을 위해 사용할 수 있습니다. 이제부터 컬렉션을 다룰때 필요한 stream 의 특징을 자세히 알아보도록 하겠습니다.


컬렉션 읽기

여기서는 컬렉션에 대한 읽기작업을 할 때 사용되는 기능을 소개하겠습니다. 컬렉션을 읽는 작업에 stream 을 사용한다는것은, 본질적으로는 컬렉션의 포인터(요소에 대한 Pointer)를 얻는다는것을 의미합니다. 이 포인터는 읽기를 하면 다음 방향으로 이동되며, 그 포인터를 사용자가 원할 때 원하는 위치로 이동시킬 수도 있습니다. ReadStream 클래스를 사용해서 컬렉션으로부터 요소들의 읽기작업을 할 수 있습니다.

next 메서드를 이용하면 컬렉션으로 부터 요소를 1 개 읽을 수 있으며, 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 에서 다음 구성요소가 무엇인지 알고싶을때 사용합니다.

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'


위의 코드는 stream 에 숫자에 따라 얻어진 결과값이 - 라면 boolen 으로 값을 반환하고 결과값을 무조건 양수로 반환합니다. upToEnd 메서드는 현재 위치부터 stream 의 끝까지 있는 모든것을 반환하며, 위치를 stream 의 마지막 지점으로 설정합니다. 위의 코드는 peekFor: 를 사용해서 단순화 할 수 있으며, peakFor: 메서드는 stream 의 다음차례의 요소와 인수가 같은경우 stream 의 위치를 그 다음으로 이동시키며 진행하고, 인수와 다를때는 stream 의 위치를 이동시키지 않습니다.

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


peekFor: 는 또한 인수가 요소와 동일한 경우, 그 결과를 Boolean 으로 반환합니다.

위의 예제에서 stream 을 만드는 새로운 방법을 눈치채셨나요: 순차 컬렉션에 대해 단지 readStream 을 보내는것 만으로도, 대상이 되는 컬렉션에 대한 read stream 을 만들 수 있습니다.


Positioning(위치선정).

stream 에 대한 포인터의 위치를 설정하기 위한 메서드가 있습니다. 만약 index 를 알고 있으면 position: 메서드를 사용해서 해당되는 위치로 직접 이동할 수 있죠. position 을 이용하면 현재의 위치를 알 수 있습니다. stream 의 position 은 요소 자체가 아니라 2 개의 요소 사이라는것을 꼭 기억해주시기 바랍니다. stream 의 초기 index 상 위치는 0 입니다.

사용자는 아레의 코드를 이용해서 그림 10.4 에 그려진 stream 의 상태를 얻을 수 있습니다.

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


stream 을 처음이나 맨 끝으로 이동시키려면, reset 또는 setToEnd 를 사용하면 됩니다. skip: 과 skipTo: 는 현재의 위치를 기준으로 다음위치로 이동하는 경우 사용합니다. skipTo: 는 건네받은 인수와 같은 요소를 찾을때까지 stream 에 있는 모든 요소를 건너뛰는 반면에, skip: 인 인수로 받은 숫자만큼 요소를 건너뜁니다. 매칭된 요소의 뒤쪽에 stream 의 포인터가 위치한다는걸 잊지 마시기 바랍니다.

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 를 건너뛰었습니다.

contents 메서드는 항상 전체 stream 의 복사본을 반환합니다.


Testing.

몇가지 메서드를 이용하면 현재의 stream 상태를 테스트 할 수 있습니다: isEmpty 는 컬렉션이 가지고있는 요소가 없는경우 true 를 반환하는 반면, atEnd 는 컬렉션의 더이상 읽을 수 있는요소가 없는 경우 true 를 반환합니다.

알고리즘에서 atEnd 를 사용하는 경우를 살펴보도록 하겠습니다. 아래의 코드에서는 2 개의 정렬이 끝난 콜렉션을 인자로 받아, 이것들을 병합해서 정렬이 끝난 새로운 콜렉션으로 만듭니다.

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: 메서드의 구현에서 사용되고 있습니다. 단순히 String 을 생성해야하는 경우, stream 을 사용하게되면 조금 더 단순하고 효과적인 방법을 사용할 수 있습니다.

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


메서드 streamContents: 는 컬렉션과 컬렉션을 이용한 stream 을 만듭니다. 이 메서드는 인자로 블록을 받아 실행하며, 인자로 주어질 블록에는 stream 이 들어가야 합니다. 블록이 종료되면, streamContents: 는 컬렉션의 내용을 반환합니다.

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

nextPut: 인자로 받은 내용을 stream 에 추가합니다.

nextPutAll: 인자로 받은 컬렉션의 각 구성요소를 stream 에 추가합니다.

print: 인자의의 텍스트표현을 stream 에 추가합니다.


특수문자를 stream 에 표시하는데 편리한 space, tab 그리고 CRcarriage return 등의 메서드도 있습니다. 또 다른 유용한 메서드는, 마지막 문자가 공백이 아닐 경우 공백을 추가해서, stream 에서 마지막 문자가 공백이 되도록 보장하는 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)"


stream 을 사용하는 것이 보다 효율적인 이유는, 콤마연산자는 수신자와 인수를 연결하는 새로운 문자열을 만들기때문에, 수신자와 인수의 양쪽 모두를 복사하는 작업을 진행하게 되는 상황은 비효율적이기 때문입니다.

만약 같은 수신자에 대해서 콤마연산자로 연결을 반복하면 연결할때마다 문자열이 길어지기때문에, 복사해야하는 문자의 개수가 기하급수적으로 증가하게 되겠죠.

그리고 이런식으로 콤마연산자를 이용한 문자열의 연결을 반복하게되면 메모리상에 쓰레기를 더 많이 남기게 되며, garbege collection 의 대상이 됩니다. 콤마연산자를 이용한 문자(열)의 연결 대신 stream 을 사용하는 방법은 유명한 최적화 방법입니다. 실제로 이런 작업을 위해서 streamContents[1] 사용하실 수 있습니다:

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


동시에 읽고 쓰기

콜렉션에 접근해서 동시에 읽고 쓰기를 하려는경우 stream 을 사용하면 이런 작업이 가능합니다. web browser 에서 앞 버전과 뒤 버튼을 관리하기 위해 History 클래스를 만든다고 가정해 보겠습니다. History 클래스는 그림 10.5 에서 그림 10.11 까지의 내용과 같은 동작을 하게 될겁니다.


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


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


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


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


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


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


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


위의 그림같은 동작은 ReadWriteStream 을 사용하여 구현할 수 있습니다.

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

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


여기에서 딱히 어려운 부분은 없습니다. 이제 stream 을 포함하는 새로운 클래스를 정의하도록 하겠습니다. stream 은 initialize 메서드를 통해 만들어집니다.

그 다음 앞 과 뒤 양쪽으로 이동하기 위한 메서드가 필요하겠죠:

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.

하지만 지금까지의 작업이 완전한것은 아닙니다. 왜냐하면 유저가 링크를 클릭한 이후, 내부에서 앞 에 해당하는 페이지가 있어서는 안되기 때문입니다. 예를들어 앞 에 해당하는 버튼은 비활성화가 되어야 한다는거죠. 이런 작업에 대한 가장 단순한 해결책은 nil 을 사용해서 history 의 끝을 나타내면 됩니다.

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


이제 canGoBackward 와 canGoForward 의 구현만 남았군요.

stream 은 항상 2 개의 요소 사이에 위치하게 됩니다. 뒤로 가는 작업을 하기위해서는 현재의 위치 앞쪽으로 2 개의 페이지가 있어야 합니다. 하나는 현재의 페이지고, 다른 하나는 이동을 원하는 페이지가 되겠죠.

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


stream 의 내용을 확인하는 메서드를 추가해 보도록 하겠습니다:

History>>contents
   stream contents


History 는 예상대로 작동합니다:

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


Notes

  1. 223페이지에서 확인가능