(본 게시물은 저작권의 문제 발생시 출판사의 요청에 의해 삭제될 수 있습니다.)
반복
루비에는 원시적 반복구문이 내장되어 있다. 예를 들어 다음 구문은 입력이 끝날 때까지 데이터를 읽는다.
while line = gets
# ...
end
until 반복문은 반대다. 조건이 참이 되기 전까지만(until) 실행한다.
until play_list.duration > 60
play_list.add(song_list.pop)
end
if, unless와 마찬가지로 이 두 가지 반복문 또한 구문 변경자로 이용할 수 있다.
a = 1
a *= 2 while a < 100
a # => 128
a -= 10 until a < 100
a # => 98
범위를 일종의 플립플롭(flip-flop) 처럼 이용할 수 있다. 무언가 이벤트가 일어날 때 참이 되고 두 번째 이벤트가 일어날 때까지 참을 유지한다. 다음 예를 보면 처음 열 개 서수를 포함하는 텍스트 파일을 읽는데, third 인 줄부터 fifth인 줄까지만 출력한다.
file = File.open("ordinal")
while line = file.gets
puts(line) if line =~ /third/ .. line =~ /fifth/
end
실행결과
third
fourth
fifth
펄을 쓰던 사람들은 앞의 코드를 약간 다르게 사용한다.
file = File.open("ordinal")
while file.gets
print if ~/third/ .. ~/fifth
end
실행결과
third
fourth
fifth
gets는 마지막에 읽은 줄을 전역변수 $_에 집어넣고, ~ 연산자는 정규표현식이 $_에 매칭하는지를 판단한다. 아무 매개 변수 없이 쓰인 print는 $_를 추력한다.
논리 표현식에서 사용되는 범위의 끝 요소에 표현식을 쓸 수도 있다. 이것은 전체 표현식을 계산할 때마다 다시 계산한다. 다음 코드는 첫번째 줄에서 세번째 줄까지, 그리고 /eig/와 /nin/이 각각 매치되는 줄 사이의 모든 줄을 출력하기 위해 현재까지 입력받은 줄 수를 나타내는 $.를 사용한다.
File.foreach("ordinal") do | line |
if ( ($. == 1) || line =~ /eig/ ) .. ( ($. == 3) || line =~ /nin/ )
print line
end
end
실행결과
first
second
third
eighth
ninth
while과 until이 구문 변경자로 쓰일 때 문제점이 하나 있다. begin/end 블록을 구문 변경자로 꾸밀 경우, 논리 표현식의 값에 상관없이 블록의 코드가 무조건 한 번은 실행된다.
print "Hello\n" while false
begin
print "Goodbye\n"
end
실행결과
Goodbye
반복자
루비에서는 복잡한 내장 반복문이 필요없다. 예를 들어 루비에는 for 반복문이 없다. 특정 숫자 범위가 주어졌을 때 반복하는 반복문이 없고 여러 내장 클래스에 정의된 메서드들을 이용한다. 다음의 예를 보자.
3.times do
print "Ho! "
end
실행결과
Ho! Ho! Ho!
위와같이 사용하면 범위를 넘어서는 버그와 숫자가 하나씩 크게 나오는 버그를 피하기 쉽다. 정수는 times 외에도 downto, upto를 호출해 특정 범위의 반복문을 실행할 수 있고 모든 숫자는 step을 사용해 반복문을 실행할 수 있다. 전통적인 for 반복문 (i=0; i<10; i++)은 다음과 같이 쓸 수 있다.
0.upto(9) do |x|
print x, " "
end
실행결과
0 1 2 3 4 5 6 7 8 9
0에서 12까지 3씩 증가하는 반복문은 다음과 같이 쓴다.
0.step(12, 3) { |x| print x, " " }
실행결과
0 3 6 9 12
배열이나 다른 컨테이너를 반복하는 것은 그것들의 each 메서드를 사용하면 간단하다.
[ 1, 1, 2, 3, 5].each { | val | print val, " " }
실행결과
1 1 2 3 5
일단 클래스가 each를 지원하면 Enumerable 모듈을 통해 추가적인 메서드를 사용할 수 있다. 예를 들어 File 클래스에는 each 메서드가 정의되어 있으며, 이는 매 반복에서 파일의 각 줄을 반환한다. Enumerable의 grep 메서드를 이용하면 특정 조건에 맞는 줄만 반복한다.
File.open("ordinal").grep(/d$/) do | line |
puts line
end
실행결과
second
thrid
마지막으로 소개할 반복문은 가장 기본적인 형태이다. 루비는 loop라는 내장 반복자를 제공한다.
loop do
# 블록...
end
loop 반복자는 결합된 블록을 영원히 반복한다.
for .. in
루비에서 for은 문법적으로 편리를 주는 문법적 편의이다.
for song in playlist
song.play
end
루비는 이를 다음과 같이 변환한다.
playlist.each do |song|
song.play
end
for 반복문과 each 형태가 갖는 차이점은 내부에 정의된 지역 변수의 범위다. for는 Array나 Range처럼 each에 응답하는 객체에는 어디에서나 사용할 수 있다.
for i in ['fee', 'fi', 'fo', 'fum']
print i, " "
end
for i in 1..3
print i, " "
end
for i in File.open("ordinal").find_all { | line | line =~ /d$/ }
print i.chomp, " "
end
실행결과
fee fi fo fum 1 2 3 second third
클래스를 정의할 때 each 메서드를 정의해 주기만 하면, 그 객체를 탐색하기 위해 for 반복문을 이용할 수 있다.
class Periods
def each
yield "Classical"
yield "Jazz"
yield "Rock"
end
end
periods = Periods.new
for genre in periods
print genre, " "
end
실행결과
Classical Jazz Rock
break, redo, next
반복문 제어를 위한 break, redo, next를 이용하면 반복문이나 반복자의 실행 흐름을 바꿔줄 수 있다. break는 즉시 실행 중인 줄을 싸고 있는 반복문을 종료한다. redo는 반복문을 시작부터 닫시 수행하지만, 다음 원소를 가져오거나(반복자인 경우) 종료 조건을 재평가하지는 않는다. next는 현재 위치에서 반복문의 끝으로 이동해 실제로는 다음 반복문을 시작하게 한다.
while line = gets
next if line =~ /^s*#/ #주석은 건너뛴다.
break if line =~ /^END/ # END를 발견하면 끝
# 역 따옴표 안의 내용을 대체하고, 다시 반복한다.
redo if line.gsub!(/`(.*?)`/) { eval($1) }
# 무언가를 한다. ...
end
위 키워드들은 블록 내에서도 사용할 수 있다. 어떤 블록에서라도 사용할수 있지만 일반적으로 반복자에 넘겨진 블록에서 사용할 때 유용하다.
i=0
loop do
i += 1
next if i < 3
print i
break if i > 4
end
실행결과
345
break와 next에는 매개변수를 넘겨줄 수있다. 기존 반복문을 사용할 때 매개 변수를 받을 경우 의미가 있는 건 break 뿐이다. break에 넘겨진 값이 반복문의 반환값이 된다.(next에 넘겨지는 매개 변수는 실제로는 버려진다.) 이러한 전통적인 반복문이 break를 호출하지 않는다면 반복문 자체의 평가값은 nil이 된다.
result = while line = gets
break(line) if line =~ /answer/
end
process_answer(result) if result
변수 범위, 반복, 블록
내장 반복문인 while, until, for는 새로운 변수 범위를 만들지 않는다. 반복문 안에 새로 만들어진 지역변수는 반복문이 끝나도 사용가능하다. 반복자(loop, each)등에서 사용하는 블록에서는 이 규칙이 조금 다르다. 보통 블록 안에서 만드는 지역 변수는 블록 바깥에서 접근할 수 없다.
[ 1, 2, 3 ].each do | x |
y = x + 1
end
[x, y]
실행 결과
prog.rb:4:in '<main>': undefined local variable or method 'x' for
main:Object
(NameError)
하지만 블록이 실행될 때 블록안에서 이용되는 변수와 같은 이름의 지역변수가 있다면 미리 선언된 지역변수가 블록안에서 이용된다.
x = "initial value"
y = "another value"
[ 1, 2, 3].each do | x |
y = x + 1
end
[x, y ] # => ["initial value", 4]
블록 내의 변수와 외부 변수가 같은지를 판단할 때 변수가 외부 범위에서 반드시 실행되지 않아도 된다는 사실을 인지하기 바란다. 루비 인터프리터는 단지 그 변수가 좌변항에서 대입이 시도되었는지를 확인만 해도 된다고 판단한다.
a = "never used" if false
[99].each do | i |
a = i # 외부에서 사용되지 않은 변수 a에 대입을 한다.
end
a # => 99
블록 로컬(block-local) 변수는 블록의매개 변수 목록에서 이어 세미콜론 뒤에 선언한다. 다음 코드에서는 블록 로컬을 사용하지 않는다.
square = "yes"
total = 0
[ 1, 2, 3 ].each do | val |
square = val * val
total += square
end
puts "Total = #{total}, square = #{square}"
실행결과
Total = 14, square = 9
다음 코드는 블록 로컬 변수를 사용해 블록 바깥의 같은 이름을 가진 변수로부터 영향을 받지 않도록 만든다.
square = "yes"
total = 0
[ 1, 2, 3 ].each do | val; squre |
square = val * val
total += square
end
puts "Total = #{total}, square = #{square}"
실행결과
Total = 14, square = yes
블록에서 사용하는 변수의 스코프가 신경 쓰인다면 루비의 경고를 활성화 하고 블록 로컬 변수를 명시적으로 선언하는 편이 좋다.
끄읕