티스토리 뷰

프로그래밍/Ruby

Ruby - 기본문법

1q 2016. 12. 22. 00:33

gsub(문자열 치환)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
print "Thtring, pleathe!: "
 
user_input = gets.chomp
 
user_input.downcase!
 
 
 
if user_input.include? "s"
 
  user_input.gsub!(/s/"th")
 
else
 
  puts "Nothing to do here!"
 
end
 
  
 
puts "Your string is: #{user_input}"
cs


Hash

해시는 자바스크립트의 객체(objects), 또는 파이썬의 사전형(dictionaries)과 비슷합니다. 만약 해당 프로그래밍 언어들을 배운 적이 없다면, 여러분이 아셔야 할 건 해시란 키-값 쌍(key-value pair)의 조합물이라는 것 뿐입니다. 해시의 문법은 다음과 같습니다. 해시에서는 =>를 사용하여 값을 키에 할당하며, 루비 객체를 키나 값으로 사용할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
my_hash = { "name" => "Eric",
 
  "age" => 26,
 
  "hungry?" => true
 
}
 
 
 
puts my_hash["name"]
 
puts my_hash["age"]
 
puts my_hash["hungry?"]
 
 
cs


Hash.new 이용하기

방금 여러분에게 보여드렸던 것은 해시 리터럴 표기법(literal notation)이라는 겁니다. 이렇게 부르는 이유는 해시 안에서 원하는 것을 문자 그대로(literally) 묘사할 수 있기 때문입니다: 해시에 이름을 지정하고, 몇 개의 키 => 값 쌍을 중괄호({}) 안에 추가하는 방법 말이죠.

또한 다음과 같이 Hash.new를 이용하여 해시를 생성할 수도 있습니다:

my_hash = Hash.new

변수에 Hash.new를 할당하면 비어있는 상태의 새로운 해시가 생성됩니다; 이는 변수에 비어있는 중괄호({})를 할당하는 것과 똑같습니다.


해시(Hash)에 추가하기

두 가지 방법을 통해 해시에 요소들을 추가할 수 있습니다: 만약 리터럴 표기법으로 생성했다면, 간단하게 중괄호 안에 직접 키-값 쌍을 추가하면 됩니다. 만약 Hash.new를 사용했다면, 대괄호 표기법을 사용하여 요소를 추가할 수 있습니다:

pets = Hash.new
pets["Stevie"] = "cat"
# 해시 안에 키 "Stevie"를
# 값 "cat"과 함께 추가


해시(Hashes) 안의 값에 접근하기

배열에서와 마찬가지로, 해시 안의 값 역시 오프셋(즉, 색인을 이용)을 통해 접근할 수 있습니다. 해시 안의 각 색인은 할당된 키(key)입니다; 간단하게 대괄호 사이에 키의 이름을 넣음으로써, 해당 키와 관련된 값에 접근할 수 있습니다.

예를 들어, 다음과 같은 해시가 있다고 할 때,

pets = { "Stevie" => "cat",
  "Bowser" => "hamster",
  "Kevin Sorbo" => "fish"
}

아래 처럼 작성함으로써 키 "Stevie"의 값을 얻을 수 있습니다.

pets["Stevie"]
# 값으로 "cat"을 반환


반복(Iteration) (재)입문

반복문과 반복자에 관해 다뤘던 것, 기억하시나요? 반복을 위해 여러가지 다른 메소드들을 사용했었습니다. 우리가 배열이나 해시를 반복시킬 때, 말 그대로 반복(iterate)한다고 표현합니다.

이번 과정에서 .each 반복자를 사용하여 배열과 해시를 반복시킬 겁니다. 에디터 안의 코드를 살펴보세요. 앞으로 우리가 배울 내용에 관한 예제입니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
friends = ["Milhouse""Ralph""Nelson""Otto"]
 
 
 
family = { "Homer" => "dad",
 
  "Marge" => "mom",
 
  "Lisa" => "sister",
 
  "Maggie" => "sister",
 
  "Abe" => "grandpa",
 
  "Santa's Little Helper" => "dog"
 
}
 
 
 
friends.each { |x| puts "#{x}" }
 
family.each { |x, y| puts "#{x}: #{y}" }
 
 
cs
배열(Arrays) 반복시키기

배열을 반복시키는 건 보기보다 쉽습니다. 배열의 요소들은 0부터 시작하여 1씩 증가하는 색인에 저장되어 있으며, 반복문은 이렇게 특정 숫자로부터 시작하여 매 반복마다 1씩 증가하는 것에 사용하기 좋습니다. 다음과 같이 작성하면,

array.each { |element| 실행할 코드 }

루비는 이렇게 전달받습니다: "배열 array를 받아 매 요소 element 마다 코드를 실행". 언제나 그렇듯, | | 사이에 어떤 이름으로든 자리표시자(placeholder)를 나타낼 수 있습니다.


다차원 배열(Multidimensional Arrays) 반복시키기

다차원 배열을 반복시키는 건 조금 난해할 수도 있습니다.

에디터 안에 샌드위치를 뜻하는 이차원 배열 s를 만들어 두었습니다. 이제 배열 s를 반복시켜 ["ham", "swiss"]와 같이 각 요소들을 출력하는 것이 아니라, 각 요소 안의 요소들을 출력하고자 합니다.

만약 "swiss"에 접근하려 한다면, 다음과 같이 작성하면 됩니다:

s[0][1]

이는 "배열 s안에 있는 첫 번째 요소의 두 번째 요소를 가져와"라는 뜻으로, "swiss"를 나타냅니다. 일반적인 배열들은 아래와 같은 코드를 이용해서 반복할 수 있습니다.

array.each { |element| action }

그렇다면 배열의 배열같은 경우엔 어떻게 반복시킬 수 있을까요?


1
2
3
4
5
6
7
8
9
10
11
12
13
= [["ham""swiss"], ["turkey""cheddar"], ["roast beef""gruyere"]]
 
 
 
s.each do | x |
 
    x.each do | y |
 
        puts y
 
    end
 
end
cs


해시(Hashes) 반복시키기

해시를 반복시킬 땐, 하나 이상의 자리표시자 변수를 사용해야 합니다. 다차원 배열에서는 .each반복자를 중첩하여 중첩된 배열들의 각 색인을 반복했던 반면, 해시의 반복에 있어선 두 개의 자리표시자를 사용하여 요소의 두 부분, 즉 키와 값을 얻어야 합니다.

해시의 키와 값을 출력하고자 한다면, 다음과 같이 작성하면 됩니다:

hash.each { |key, value| puts "#{key}:  #{value}" }

위의 코드에서 | | 사이의 key와 value는 언제나 그래왔듯, 자리표시자 이름입니다: x나 y가 될 수도 있고, 심지어 hamburgers나 hotdogs 등, 여러분이 이름붙이는 건 무엇이든 자리표시자가 될 수 있습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
secret_identities = { "The Batman" => "Bruce Wayne",
 
  "Superman" => "Clark Kent",
 
  "Wonder Woman" => "Diana Prince",
 
  "Freakazoid" => "Dexter Douglas"
 
}
 
 
 
secret_identities.each do
 
    |x, y| puts "#{x}: #{y}"
 
end
cs

해시(Hash) 정렬하기

잘하셨습니다! 이제 모든 단어와 빈도수가 키-값 쌍으로 짝 지어진 해시를 얻게 되었습니다. 이제 해시를 원하는 순서대로 정렬하는 방법을 알아봅시다.

좋지 않은 소식은 이 과정이 두 단계를 거쳐야 한다는 점이며, 좋은 소식은 루비 내장 메소드를 통해 어렵지 않게 진행할 수 있다는 것입니다.

해시 frequencies에 .sort_by 메소드를 사용하여, 해당 해시를 특정 매개변수(paramether)에 따라 분류할 수 있습니다. 만약 아래와 같이 입력했다면,

h = h.sort_by {|a, b| b }

위의 코드에서 해시 h는 b의 오름차순으로 정렬될 것이며, 여기서 b는 키-값 쌍의 값을 의미합니다.

슬프게도, 이는 단어들이 가장 작은 빈도부터 시작해서 큰 빈도 순으로 정렬된다는 뜻입니다. 만약 빈도수가 큰 단어부터 정렬되도록 만들고 싶다면, .reverse 메소드를 사용하여 해시의 순서를 반대로 만들어야 합니다.



해시(Hashes) 반복시키기

거의 다 됐습니다! 마지막으로 배열을 반복시켜 각각의 키-값 쌍을 console에 출력해 봅시다.

여기서 한 가지 주의할 점이 있습니다. 루비는 문자열과 숫자값끼리는 연결(concatenate)할 수가 없는데요, 현재 키는 모두 문자열이고, 값은 모두 숫자값인 상태입니다. 따라서 각각의 값에 .to_s메소드(to string의 약자)를 호출하여 다음과 같이 숫자값을 문자열로 바꾼 뒤 연결해야 합니다:

puts word + " " + value.to_s


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
puts "Input your code please!"
 
 
 
text = gets.chomp
 
 
 
words = text.split(" ")
 
 
 
frequencies = Hash.new(0)
 
 
 
words.each { |word| frequencies[word] += 1 }
 
 
 
frequencies = frequencies.sort_by { |a, b| b }
 
 
 
frequencies.reverse!
 
 
 
frequencies.each { |word, frequency| puts word + " " + frequency.to_s }
cs


메소드(Method) 문법

메소드는 def 키워드(define의 약자)를 사용하여 정의하며, 크게 세 가지 부분으로 이루어져 있습니다.

  1. 헤더(header), 이 부분은 def 키워드와 메소드의 이름, 그리고 메소드가 갖는 인자(arguments)를 포함합니다. (다음 과제에서 인자에 관해 다룰 것입니다.)
  2. 바디(body), 이 부분은 메소드가 진행하는 과정을 나타내는 코드 블록입니다. 바디 부분은 관례상 두 칸 만큼 들여쓰기해야 합니다. (ifelifelse 명령문의 코드 블록과 마찬가지입니다.)
  3. end 키워드를 사용하여 메소드의 끝을 명시합니다.

console에 문자열 "Welcome to Ruby!"를 출력하는 간단한 메소드 welcome를 통해 메소드의 문법 예제를 보여드리겠습니다:

def welcome
  puts "Welcome to Ruby!"
end


매개변수(Parameters)와 인자(Arguments)

어떤 메소드가 인자(arguments)를 가질 때, 우리는 해당 메소드가 인자를 받아들인다(accepts)고 말합니다. 다음과 같이, 메소드 square가 하나의 인자를 갖도록 정의할 수 있습니다:

def square(n)
  puts n ** 2
end

그런 다음, 다음과 같이 해당 메소드를 호출할 수 있습니다:

square(12)
# ==> 결과로 "144"를 출력

엄밀히 말하자면, 인자(argument)는 함수를 호출할 때 함수의 소괄호 속에 집어넣는 코드 부분이며, 매개변수(parameter)는 함수를 정의할 때 함수의 소괄호 속에서 인자 대신 자리를 차지하는 이름을 뜻합니다. 이를 테면 위에서 메소드 square를 정의할 땐 매개변수로 n ("number"를 뜻함)를 지정했지만, 함수를 호출할 때에는 여기에 인자로 숫자 12을 전달했었습니다.

메소드를 정의할 때 인자에 붙이는 일종의 별명, 또는 가명이 매개변수라고 생각하셔도 됩니다. 이후 메소드를 호출할 때 매개변수 위치에 들어갈 인자가 무엇이 될 지, 메소드를 정의하는 단계에선 알 수 없기 때문입니다.

루비에서 소괄호는 일반적으로 반드시 필요한 것이 아닌 선택사항이지만, 가독성을 고려하여 매개변수나 인자는 소괄호 안에 넣어주시는 것이 좋습니다.


가변 인자(Splat argument)

무엇을 값으로 받을지 모를 때: 때로는 작성한 메소드가 어떤 인자를 받을지 알 수 없을 뿐만 아니라, 얼마나 많은 인자를 받을지 알 수 없을 때도 있을 겁니다.

예를 들어 friend라는 메소드가 있고, 이 메소드는 다음과 같이 사용자로부터 값을 받아 출력하는 기능을 한다고 가정합시다:

def friend(name):
  puts "My friend is " + name + "."
end

위의 코드는 인자로 친구 한 명의 이름을 받도록 되어 있습니다. 하지만 만약 여러 친구들의 이름을 출력하고 싶다면 어떻게 해야할까요? 그것도 얼마나 많은 친구를 출력할지도 모른다면 말이죠.

해결책은 가변 인자(splat arguments)를 사용하는 것입니다. 가변 인자는 이름 앞에 *를 붙인 인자로써, 다음과 같은 뜻입니다: "얼마나 많은 인자를 받을 지는 모르겠지만 분명 하나보다는 많은 인자를 받을 것"


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def what_up(greeting, *bros)
 
  bros.each { |bro| puts "#{greeting}, #{bro}!" }
 
end
 
 
 
what_up("What up""Justin""Ben""Kevin Sorbo")
 
 
 
[결과]
 
What up, Justin!
 
What up, Ben!
 
What up, Kevin Sorbo!
cs

복합 비교 연산자(Combined Comparison Operator)

또한 루비의 객체를 비교하기 위해 복합 비교 연산자(Combined Comparison Operator)라고 불리우는 새로운 연산자를 사용할 수 있습니다. 복합 비교 연산자는 다음과 같이 생겼습니다: <=>. 이 연산자는 첫번째 피연산자가 두번째 피연산자와 같다면 0을, 첫번째 피연산자가 크다면 1을, 반대로 첫번째 피연산자가 작다면 -1을 반환합니다.

sort 메소드에 전달된 블록은 1, 0, -1 중 하나의 값만을 반환해야 합니다. 만약 첫번째 블록 매개변수가가 두번째 블록 매개변수보다 먼저 와야 한다면 -1을, 그 반대라면 1을, 두 블럭이 동등하다면(즉, 두 값이 같다면) 0을 반환해야 합니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def alphabetize(arr, rev=false)
 
  if rev
 
    arr.sort { |item1, item2| item2 <=> item1 }
 
  else
 
    arr.sort { |item1, item2| item1 <=> item2 }
 
  end
 
end
 
 
 
books = ["Heart of Darkness""Code Complete""The Lorax""The Prophet""Absalom, Absalom!"]
 
 
 
puts "A-Z: #{alphabetize(books)}"
 
puts "Z-A: #{alphabetize(books, true)}"
cs


a = [ "d", "a", "e", "c", "b" ]
a.sort                    #=> ["a", "b", "c", "d", "e"]
a.sort { |x,y| y <=> x }  #=> ["e", "d", "c", "b", "a"]


Nil: 정식 소개

존재하지 않는 키에 접근하려 하면 어떤 일이 발생할까요?

여러 프로그래밍 언어에서 이 경우, 대부분은 오류가 발생할 것입니다. 하지만 루비에서는 그렇지 않습니다: 오류 대신 특별한 값인 nil을 얻게될 겁니다.

nil 은 false와 함께 루비에서 참이 아닌(non-true) 값을 나타냅니다.(다른 모든 객체들은 "truthy,"로 여겨지는데, 이는 기본적으로 참의 성질을 갖습니다. 즉, if 2나 if "bacon"라고 작성하면 if 명령문의 코드 블록이 실행될 거란 뜻입니다)

false 와 nil은 같은 것이 아니다는 걸 인지하는 게 중요합니다: false는 "참이 아님"을 뜻하는 반면, nil은 "아무 것도 아님(nothing at all)"이라는 루비식 표현입니다.


여러분 만의 기본값(default) 설정

해시의 기본값으로 nil을 지정할 필요는 없습니다. 만약 Hash.new 구문을 통해 해시를 만들고자 할 때, 다음과 같이 작성하여 기본값을 지정할 수 있습니다:

my_hash = Hash.new("Trady Blix")

이제 my_hash 안에 존재하지 않는 키에 접근하려 하면, 결과로 "Trady Blix"를 얻게 될 겁니다.


심볼(Symbol)은 무엇인가요?

루비의 심볼을 일종의 이름이라고 생각하셔도 좋습니다. 다만 심볼은 문자열이 아니다란 것을 기억하는 건 중요합니다:

"string" == :string # 거짓(false)

위 아래의 다른 구문을 통해, 심볼이 문자열과는 다른 중요한 특징이 있음을 알 수 있습니다: 문자열의 경우, 같은 값을 가지는 다른 문자열이 여러 개 있을 수 있는 반면, 심볼은 주어진 시간에 단 하나의 복사본만이 존재합니다.


puts "string".object_id

puts "string".object_id


puts :symbol.object_id

puts :symbol.object_id


메소드 .object_id는 객체의 ID를 얻는 기능을 합니다. 이렇게 얻은 ID를 통해 두 개의 객체가 정확히 같은 객체인지 여부를 알아낼 수 있습니다. 에디터 안의 코드를 실행하여 두 개의 문자열 "strings"가 실제로는 다른 객체라는 점과, 두 개의 심볼 :symbol이 사실은 같은 객체가 복사된 것이라는 점을 확인해 보세요.


심볼(Symbol) 문법

심볼은 항상 콜론(:)으로 시작합니다. 이들은 루비에서 유효한 변수 이름을 가져야 하므로, 콜론 다음의 첫 글자는 반드시 문자이거나 밑줄(_)이어야 합니다; 그 뒤 부터는 문자, 숫자, 또는 밑줄 등을 섞어 쓰셔도 상관없습니다.

작성한 심볼의 이름에는 어떤 빈 공간도 넣지 않도록 주의하세요. 그렇지 않으면 루비가 혼란을 겪게 될 겁니다.

:my symbol # 이렇게 하지 마세요!
:my_symbol # 대신 이렇게 하세요.


심볼(Symbols)은 무엇을 위해 사용되나요?

심볼은 루비의 많은 곳에서 나타나는데, 주로는 해시 안의 키나 메소드 이름의 참조에 사용됩니다. (이후 수업에서 심볼이 어떻게 메소드를 참조할 수 있는지 다룰 것입니다.) 심볼이 해시 키가 되기에 좋은 이유는 다음과 같습니다:

  1. 심볼은 한 번 생성된 뒤에는 변경이 불가능합니다.
  2. 심볼은 주어진 기간에 오직 하나의 복사본 만이 존재하기 때문에, 메모리를 절약할 수 있습니다.
  3. 위의 두가지 이유로 인해, 문자열 대신 심볼을 키로 사용하는 해시는 더 빨리 작동합니다.(만약 관심이 있으시면 Hint를 통해 더 많은 내용을 확인해 보세요.)


1
2
3
4
5
6
7
8
9
10
11
symbol_hash = {
 
    :one => "1",
 
    :two => "2",
 
    :three => "3"
 
}
 
 
cs
심볼(Symbols)과 문자열(Strings) 사이의 변환

문자열과 심볼 사이의 변환은 수월한 일인데요, 다음과 같이 .to_s 메소드를 사용하여 심볼을 문자열로 변환할 수 있습니다:

:sasquatch.to_s
# ==> "sasquatch"

그리고 .to_sym 메소드를 사용하여 반대로 문자열을 심볼로 변환할 수 있습니다:

"sasquatch".to_sym
# ==> :sasquatch


.each 메소드를 사용하여 배열을 반복시킬 수 있고, .to_sym 메소드를 사용하여 문자열을 심볼로 변환할 수 있습니다. 기억하세요, .push 메소드를 사용하면 배열의 끝에 요소들을 추가시킬 수 있습니다!


정상에 도달하는 여러 방법

기억하세요, 루비에서 무언가를 달성하는데는 항상 한 가지 외에도 여러가지 방법들이 존재합니다. 문자열을 심볼로 변환시키는 것도 마찬가지입니다!

.to_sym 메소드 외에, .intern 메소드 또한 사용할 수 있습니다. 이 메소드는 .to_sym와 마찬가지로 문자열을 심볼로 변환하는 기능을 합니다:

"hello".intern
# ==> :hello

다른 사람이 작성한 코드를 보면 문자열을 심볼로 변환시킬 때, .to_sym 메소드나 .intern 메소드(또는 둘 다!)를 사용하는 것을 볼 수 있을 겁니다.


1
2
3
4
5
6
7
8
9
movies = Hash.new("suspect")
 
 
 
movies = {
 
    :memento => "Hi Your Movie is Suck ma dick"
 
}
cs

-------------------------------------------------------------------------  위 아래 둘 다 같은 문법임. 1.9 버전 이후로 아래가 많이 쓰임

1
2
3
4
5
6
7
8
9
movies = Hash.new("suspect")
 
 
 
movies = {
 
    memento: "Hi Your Movie is Suck ma dick"
 
}
cs

--------------------------------------------------------------------  해시에서 문자열 키 vs 심볼 키 속도 비교

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
require 'benchmark'
 
 
 
string_AZ = Hash[("a".."z").to_a.zip((1..26).to_a)]
 
symbol_AZ = Hash[(:a..:z).to_a.zip((1..26).to_a)]
 
 
 
string_time = Benchmark.realtime do
 
  100_000.times { string_AZ["r"] }
 
end
 
 
 
symbol_time = Benchmark.realtime do
 
  100_000.times { symbol_AZ[:r] }
 
end
 
 
 
puts "String time: #{string_time} seconds."
 
puts "Symbol time: #{symbol_time} seconds."
cs

--------------------------------------------------------------------

우리 모두 특정 키를 명시함으로써, 해당 키와 관련된 값을 해시로부터 얻는 방법을 알고 있습니다. 하지만 만약 특정 기준을 만족하는 모든 값을 얻기 위해 해시를 걸러내야 한다면 어떻게 해야할까요? 이같은 경우에 .select 메소드를 사용할 수 있습니다.

.select 메소드는 하나의 블록을 가지며, 이는 키/값 매개변수, 그리고 일치하는 키와 값을 선택하는 수식으로 구성됩니다. 다음의 예제를 살펴보세요:

grades = { alice: 100,
  bob: 92,
  chris: 95,
  dave: 97
}

grades.select { |k,v| k > :c }
# ==> {:chris=>95, :dave=>97}

grades.select { |k,v| v < 97 }
# ==> {:bob=>92, :chris=>95}

(여기서는 "key"를 상징하는 "k"와 "value"를 상징하는 "v"를 매개변수로 사용하고 있습니다. 하지만 언제나 그렇듯, 매개변수의 이름은 여러분이 원하는 무엇으로든 지정할 수 있습니다.)


루비는 .each_key와 .each_value라는 두 개의 해시 메소드를 포함하고 있으며, 이들은 우리가 예상하는 그대로 작동합니다:

my_hash = { one: 1, two: 2, three: 3 }

my_hash.each_key { |k| print k, " " }
# ==> one two three

my_hash.each_value { |v| print v, " " }
# ==> 1 2 3


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
movies = {
  Memento: 3,
  Primer: 4,
  Ishtar: 1
}
 
puts "무엇을 도와드릴까요?"
puts "-- 영화를 추가하려면 'add' 를 입력해 주세요."
puts "-- 영화 등급을 갱신하려면 'update'를 입력해 주세요."
puts "-- 모든 영화의 등급을 보려면 'display'를 입력해 주세요."
puts "-- 영화를 제거하려면 'delete'를 입력해 주세요."
 
choice = gets.chomp.downcase
case choice
when 'add'
  puts "추가할 영화 제목을 입력해 주세요."
  title = gets.chomp
  if movies[title.to_sym].nil?
    puts "등급을 평가해 주세요. (0부터 4 사이의 숫자를 입력해 주세요.)"
    rating = gets.chomp
    movies[title.to_sym] = rating.to_i
    puts "영화 #{title} (등급: #{rating}) 가 추가되었습니다."
  else
    puts "해당 영화는 이미 존재합니다! 해당 영화의 등급은 #{movies[title.to_sym]}입니다."
  end
when 'update'
  puts "갱신할 영화 제목을 입력해 주세요."
  title = gets.chomp
  if movies[title.to_sym].nil?
    puts "해당 영화를 찾을 수 없습니다!"
  else
    puts "새로운 등급을 평가해 주세요. (0부터 4 사이의 숫자를 입력해 주세요.)"
    rating = gets.chomp
    movies[title.to_sym] = rating.to_i
    puts "영화 #{title} (등급: #{rating}) 가 갱신되었습니다."
  end
when 'display'
  movies.each do |movie, rating|
    puts "#{movie}: #{rating}"
  end
when 'delete'
  puts "제거할 영화 제목을 입력해 주세요."
  title = gets.chomp
  if movies[title.to_sym].nil?
    puts "해당 영화를 찾을 수 없습니다!"
  else
    movies.delete(title.to_sym)
    puts "영화 #{title} 가 제거 되었습니다."
  end
else
  puts "죄송합니다, 입력하신 내용을 이해할 수 없습니다."
end
cs


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Case 명령문
    case language
    when "JavaScript"
      puts "Websites!"
    when "Python"
      puts "Science!"
    when "Ruby"
      puts "Web apps!"
    else
      puts "I don't know!"    
end
 
cs


더 단순한 'If'

일전에 우리는 루비의 if 명령문을 살펴봤었습니다:

if 조건 수식
  # 실행할 코드!
end

하지만 만약 "실행할 코드"가 짧고 간단한 수식이라면, if 명령문 전체를 한 줄로 나타낼 수 있습니다(지난 과제에서처럼 말이죠). 문법은 다음과 같습니다:

실행할 코드 if 조건 수식

즉, 한 줄로 된 if 명령문은 실행할 코드, if, 조건 수식 순으로 작성하며, 이 순서가 굉장히 중요합니다. 따라서 다음과 같이 코드를 작성할 수 있습니다:

puts "It's true!" if true

하지만 아래와 같이는 안됩니다:

if true puts "It's true!"

또한 한 줄 if 명령문을 작성할 때, 명령문의 끝 지점에 end 키워드를 사용하지 않는다는 점도 중요합니다.


다음과 같이 간단하게 한 줄 if 명령문을 작성할 수 있습니다:

puts "Hello!" if true

위의 코드를 일반적인 if 명령문으로 나타내면 다음과 같습니다:

if true
  puts "Hello!"
end


한 줄 Unless 명령문

앞서 if 명령문에서와 마찬가지로 unless 명령문 역시 똑같이 한 줄로 나타낼 수 있습니다. 명령문의 순서 또한 똑같습니다: 먼저 실행할 코드를 쓰고, unless 키워드를 쓴 다음, 마지막으로 참/거짓으로 판명될 수 있는 조건 수식을 써 넣으면 됩니다.

기억하세요, if 또는 unless 명령문을 한 줄로 작성할 때에는 end 키워드가 필요하지 않습니다!


일석 삼조(Ternary)의 기회

루비를 탐험하는 동안, 우리는 어떤 한 가지 목표를 달성하는 데에는 여러 방법이 있다는 것을 봐왔습니다. 물론 if 명령문 또한 예외는 아닙니다!

좀더 간결한 형태의 if/else 명령문은 삼항 조건 수식(ternary conditional expression)이라고 합니다. "삼항(ternary)"이라고 불리는 이유는 이 수식이 세 개의 인자를 갖기 때문입니다: 이 세 개의 인자는 각각 조건 수식, 조건 수식이 참인 경우 실행할 수식, 그리고 조건 수식이 거짓일 경우 실행할 수식으로 이루어 집니다.

문법은 다음과 같습니다:

조건 수식 ? 조건이 참일 경우 실행 : 조건이 거짓일 경우 실행

간단한 예제는 다음과 같습니다:

puts 3 < 4 ? "3은 4보다 작다!" : "3은 4보다 작지않다."

기억하세요: 각 인자들의 순서가 중요하며, 이 형태의 if/else 명령문에서는 end 키워드가 필요하지 않습니다.


When과 Then: 간단한 형태의 Case 명령문

if/else 명령문은 강력한 기능을 제공하지만, 많은 조건을 확인해야 할 땐 넘쳐나는 if와 elsif 속에서 길을 잃을 수도 있습니다. 다행히도 루비는 case 명령문이라는 간결한 대안책을 제공하는데요, 문법은 아래와 같습니다:

case language
when "JavaScript"
puts "Websites!"
when "Python"
puts "Science!"
when "Ruby"
puts "Web apps!"
else
puts "I don't know!"
end

하지만 이는 다음처럼 정리해서 사용할 수도 있습니다:

case language
  when "JavaScript" then puts "Websites!"
  when "Python" then puts "Science!"
  when "Ruby" then puts "Web apps!"
  else puts "I don't know!"
end


축약하여 추가하기 ('<<')

루비에는 일반적인 메소드 이름을 짧게 줄여쓸 수 있는 몇 가지 축약형이 있습니다. 이제부터 이야기해 볼 .push 메소드 또한 축약형을 가지고 있습니다!

.push라고 메소드의 이름을 전부 다 적어넣는 대신, 간단히 연결 연산자(concatenation operator)라고 불리는 <<를 사용하여 (생김새 때문에 "삽"으로 불리기도 합니다) 요소들을 배열의 끝에 추가해 넣을 수 있습니다.

[1, 2, 3] << 4
# ==> [1, 2, 3, 4]

좋은 소식: 이 연산자는 또한 문자열에도 사용할 수 있습니다! 다음과 같이 말이죠:

"Yukihiro " << "Matsumoto"
# ==> "Yukihiro Matsumoto"


문자열 보간(String Interpolation)

다음과 같이 언제든지 + 또는 << 연산자를 이용하여 변수값을 문자열에 추가할 수 있습니다:

drink = "espresso"
"I love " + drink
# ==> I love espresso
"I love " << drink
# ==> I love espresso

하지만 문자열이 아닌 값을 가지고 이와 같은 작업을 하려면 .to_s 메소드를 사용하여 값을 문자열로 바꿔야 합니다:

age = 26
"I am " + age.to_s + " years old."
# ==> "I am 26 years old."
"I am " << age.to_s << " years old."
# ==> "I am 26 years old."

위와 같은 방법은 복잡할 뿐더러, 복잡함은 루비의 방식이 아닙니다. 이 문제를 해결할 더 나은 방법은 문자열 보간(string interpolation)을 사용하는 것입니다. 문법은 다음과 같습니다:

"I love #{drink}."
# ==> I love espresso.
"I am #{age} years old."
# ==> I am 26 years old.

문자열과 문자열이 아닌 변수를 함께 쓰려면 간단히 문자열 안에 #{}를 사용하고, 변수의 이름을 그 사이에 넣기만 하면 됩니다!

리팩토링(refactoring)이란, 코드의 전체 기능을 변화시키지 않고 코드의 내부 구조나 가독성을 향상시키는 과정을 뜻합니다.


조건부 할당(Conditional Assignment)

변수에 값을 할당하기 위해 = 연산자를 사용한다는 건 이미 알고 계실겁니다. 하지만 만약 이미 할당되지 않은 변수에만 값을 할당하고 싶다면 어떻게 해야할까요? 이런 경우에는 조건부 할당 연산자(conditional assignment operator)인 ||= 를 사용할 수 있습니다. 이는 OR(논리합) 연산자(||)와 일반 할당 연산자 =를 결합하여 만듭니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
favorite_book = nil
 
puts favorite_book
 
 
 
favorite_book ||= "Cat's Cradle"
 
puts favorite_book # Cat's Cradle
 
 
 
# favirite_book 변수에 이미 값이 할당이 되어있기 때문에 값이 변하지 않음.
 
favorite_book ||= "Why's (Poignant) Guide to Ruby"
 
puts favorite_book # Cat's Cradle 
 
 
 
favorite_book = "Why's (Poignant) Guide to Ruby"
 
puts favorite_book  # Why's (Poignant) Guide to Ruby
 
 
cs

* require prime => 배열 모듈


루비의 블록은 실행가능한 코드 덩어리입니다. 블록은 다음과 같이 do..end를 사용하거나, 중괄호({})를 써서 나타낼 수 있습니다:

[1, 2, 3].each do |num|
  puts num
end
# ==> 한 줄에 하나씩 1, 2, 3 출력

[1, 2, 3].each { |num| puts num }
# ==> 한 줄에 하나씩 1, 2, 3 출력

블록은 .each나 .times와 같은 메소드와 결합되어, 집합(해시나 배열) 속의 각 요소들을 가지고 특정 작업을 실행할 수 있습니다.


모두 모으기(.collect)

좋아요! 루비에는 블록을 사용하면서도 정말 유용한 메소드가 꽤 있습니다. 이 중 우리가 아직 다뤄보지 않은 것으로, collect라는 메소드가 있습니다.

collect 메소드는 배열 안에 있는 모든 요소들을 대상으로 블록 안의 수식을 적용합니다. 아래의 코드를 확인해 보세요:

my_nums = [1, 2, 3]
my_nums.collect { |num| num ** 2 }
# ==> [1, 4, 9]

하지만 만약 변수 my_nums의 값을 살피려 하면, 해당 변수가 아직 변하지 않은 것을 볼 수 있습니다:

my_nums
# ==> [1, 2, 3]

이는 .collect 메소드가 my_nums의 복사본을 반환하고 원래 my_nums의 배열을 변경하지 않았기 때문입니다. 만약 원래의 값을 변경하고 싶다면 .collect!처럼 메소드 이름 뒤에 느낌표를 달아주면 됩니다:

my_nums.collect! { |num| num ** 2 }
# ==> [1, 4, 9]
my_nums
# ==> [1, 4, 9]

기억하세요, 루비에서 !의 의미는 다음과 같습니다: "해당 메소드는 뭔가 위험하거나 예상치 못한 결과를 초래할 수 있음!" 이번과 같은 경우에는, 해당 메소드가 배열의 복사본을 변경하는 것이 아니라 원래 값을 변경한다는 의미가 되겠죠.



def method_name(매개변수)
  yield 매개변수
end

method_name(인자) { 수식이 포함된 블록 }


1
2
3
4
5
6
7
8
9
10
11
#  double 메소드 안에 yield를 두어 매개변수를 전달할 수가 있음.
 
def double(num) 
 
    puts "start"
 
    yield num   
 
end
 
double(13) { |number| number * 2 }
cs


DRY(Don't Repeat Yourself), 반복하지 마세요.

루비 안의 모든 것은 객체(object)라고 말했던 것, 기억하시나요? 음, 사실은 거짓말이었습니다. 블록은 객체가 아니며, 이는 "모든 것은 객체"라는 루비의 규칙에서 몇 안되는 예외 중 하나입니다.

이 때문에 블록은 변수에 저장될 수 없으며, 실제 객체와 같은 능력을 가질 수 없는 것입니다. 이를 가능하게 하려면… 프록(Proc)이 필요합니다!

프록이란 일종의 "저장된" 블록이라고 생각하셔도 됩니다: 코드 몇 줄에 이름을 부여하여 메소드로 만드는 것처럼, 블록에 이름을 부여하여 프록으로 만들 수 있습니다. 프록은 여러분의 코드를 DRY(Don't Repeat Yourself의 줄임말로, 같은 내용을 반복해서 작성하지 말라는 뜻) 상태로 유지하는데 매우 유용합니다. 블록을 사용하면 필요할 때마다 매번 코드를 작성해야 하지만, 프록을 사용하면 코드를 한 번 작성한 뒤 여러번 사용할 수 있습니다!


1
2
3
4
5
6
7
multiples_of_3 = Proc.new do |n|
 
  n % 3 == 0
 
end
 
(1..100).to_a.select(&multiples_of_3)
cs


프록(Proc) 문법

프록은 쉽게 정의할 수 있습니다! 단지 Proc.new를 호출하고 저장하고자 하는 블록을 전달하면 끝입니다. 다음과 같이 작성하여 인자로 받은 숫자를 세제곱하는 기능의 프록 cube를 생성할 수 있습니다:

cube = Proc.new { |x| x ** 3 }

그런 뒤, 해당 프록을 메소드(평소라면 블록을 갖겠지만)에 전달함으로써 블록을 매번 다시 쓸 필요가 없게 됩니다!

[1, 2, 3].collect!(&cube)
# ==> [1, 8, 27]
[4, 5, 6].map!(&cube)
# ==> [64, 125, 216]

(.collect! 메소드와 .map! 메소드는 정확히 같은 기능을 합니다.)

& 기호는 프록 cube를 블록으로 변환(.collect! 메소드와 .map! 메소드는 일반적으로 블록을 갖기 때문에)하는데 사용됩니다. 블록을 갖는 메소드에 프록을 전달할 때면 언제든 이와 같은 변환 작업을 진행해야 합니다.

.floor 메소드는 소수점 아래의 숫자를 버림하여 숫자를 거의 정수형으로 만듭니다.


왜 프록(Procs)을 사용하죠?

왜 블록을 프록으로 저장하느라 고생해야 하나요? 두 가지 장점 때문입니다:

  1. 프록은 객체다운 객체이기 때문에, 객체로써 가져야 할 기능과 능력을 가질 수 있습니다. (블록은 가지지 못합니다.)
  2. 블록과 달리, 프록은 재차 작성할 필요없이 계속해서 호출할 수 있습니다. 이는 여러분이 블록을 통해 특정 작업을 수행하기 위해 코드를 매번 다시 작성하는 것을 방지합니다.


다른 방법으로 출력하기(.call)

잘하셨습니다! 프록을 이용하여 메소드를 호출하는 게 그리 어려운 것도 아니지만, 심지어 더욱 간단한 방법이 있습니다.

블록과 달리 .call 메소드를 사용하여 프록을 직접 호출할 수 있습니다. 확인해 보세요!

test = Proc.new { # 실행할 코드 }
test.call
# 해당 코드를 실행!

기억하세요: 루비 안에서 무언가를 하는데엔 언제나 하나 이상의 방법이 존재합니다.


심볼(Symbols), 프록(Procs)을 만나다

루비 언어의 더 복잡한 부분들을 배우는 중인데요, 이제 이들을 한 데 결합하여 코드가 마치 진짜 마법처럼 작동하도록 만들 수도 있습니다. 예를 들어, 루비 메소드의 이름을 심볼로 전달할 수 있다고 말씀드렸던 것, 기억하시나요? 여기에 &를 사용하여 또한 심볼 형태의 메소드를 프록으로 변환할 수도 있습니다.

다음 내용을 확인해 보세요:

strings = ["1", "2", "3"]
nums = strings.map(&:to_i)
# ==> [1, 2, 3]

위의 코드에서는 map 메소드를 통해 배열 strings의 모든 요소에 &:to_i를 반복시킴으로써, 배열 안의 각 문자열들을 정수형으로 만들었습니다!

루비의 람다(Lambda)

프록과 마찬가지로 람다 역시 객체입니다. 유사성은 여기서 멈추지 않습니다: 문법적 차이와 몇 가지 특이한 기능을 제외하면, 람다는 프록과 동일합니다.

에디터 안의 코드를 확인해 보세요. lambda 부분이 보이시나요?

lambda { puts "Hello!" }

위의 코드는 아래의 코드와 동일한 기능을 합니다:

Proc.new { puts "Hello!" }

오른쪽의 예제 안에서 lambda_demo에 람다를 전달할 때, 해당 메소드는 람다를 호출하여 그 안의 코드를 실행합니다.


람다(Lambda) 문법

람다는 다음과 같은 문법으로 정의할 수 있습니다:

lambda { |매개변수| 블록 }

프록을 사용하고자 하는 상황에서 마찬가지로 람다 역시 유용합니다. 다음 과제에서는 람다와 프록의 차이점을 다뤄볼 텐데요, 일단 그 전에 이번 과제에서는 lambda 문법을 연습해 봅시다.


람다(Lambdas) vs 프록(Procs)

만약 람다와 프록이 매우 비슷해 보인다고 생각하신다면, 맞습니다. 둘은 실제로 비슷합니다! 오직 두 개의 차이점만 제외하고 말이죠.

첫 번째 차이점으로, 람다는 전달받은 인자의 갯수를 확인하지만 프록은 그렇지 않다는 점입니다. 즉, 람다는 잘못된 갯수의 인자를 전달받으면 오류를 일으키지만, 프록은 예상치 못한 인자를 받으면 무시하거나, 필요한 인자가 없을땐 nil을 할당한다는 뜻입니다.

두 번째로, 람다는 값을 반환할 때 호출된 메소드에 통제권을 전달한다는 점입니다; 프록은 이런 과정 없이 즉시 값을 반환합니다.

이 둘의 차이점이 어떻게 작동하는지 확인하기 위해 에디터 안의 코드를 살펴보세요. 첫 번째 메소드는 프록을 호출하며, 두 번째 메소드는 람다를 호출합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def batman_ironman_proc
  victor = Proc.new { return "배트맨 승리!" }
  victor.call
  "아이언 맨 승리!"
end
 
puts batman_ironman_proc
 
def batman_ironman_lambda
  victor = lambda { return "배트맨 승리!" }
  victor.call
  "아이언 맨 승리!"
end
 
puts batman_ironman_lambda
 
배트맨 승리!
아이언 맨 승리!
cs



.is_a? 메소드는 호출된 대상이 확인하고자 하는 자료형과 일치하면 true를, 그렇지 않으면 false

를 반환하는 기능을 합니다.

1
2
3
4
5
6
7
8
9
10
my_array = ["raindrops", :kettles, "whiskers", :mittens, :packages]
 
 
# procedure가 Symbol 자료형인지 확인하는 코드
symbol_filter = lambda{ |procedure| procedure.is_a? Symbol }
 
# my_array에서 심볼인 자료형만 symbols에 저장됨.
symbols = my_array.select(&symbol_filter)

Fixnum - 정수, new 사용 불가, 포인터 사용 불가

Float - 실수, new 사용 불가, 포인터 사용 불가

Range - 범위

String - 문자열

Array - 배열

Hash - 해쉬

Regexp - 정규표현식

NilClass - nil, new 사용 불가, 포인터 사용 불가

TrueClass - true, new 사용 불가, 포인터 사용 불가

FalseClass - false, new 사용 불가, 포인터 사용 불가

Proc - 블록

 



cs


블록은 do..end 또는 {} 사이에 위치한 코드입니다. 그 자체로는 객체가 될 수 없지만 .each나 .select 같은 메소드에 전달될 수 있습니다.

  1. 프록은 저장된 형태의 블록으로, 여러번에 걸쳐 사용할 수 있습니다.
  2. 람다는 프록과 거의 같지만, 유일하게 전달받는 인자의 갯수를 따지며, 값을 곧바로 반환하지 않고 호출된 메소드에 전달하여 반환합니다.

블록, 프록, 람다 모두 비슷한 기능을 하므로 이를 통해 거의 같은 작업을 할 수 있지만, 작성한 프로그램의 정확한 정황에 따라 그 중 무엇을 사용할지가 결정될 겁니다.


왜 클래스(Classes)를 사용하나요?

루비는 객체 지향 프로그래밍 언어로, 이는 루비가 객체(object)라 불리는 프로그래밍 구조를 다룬다는 뜻입니다. 루비 안의 (거의) 모든 것이 객체입니다! 지금까지 여러분은 여러 객체들을 사용해 왔으므로, 매우 익숙하게 느껴질 겁니다. 객체는 이전에 다뤘던 메소드와, 데이터를 나타내는속성(attributes)을 가지고 있습니다. 예를 들어,

"Matz".length
# ==> 4

위의 코드에서 "Matz" 객체는 문자열로, .length 메소드와 길이 속성인 4를 가지고 있습니다. 이제부터 진행할 몇 가지 과제를 통해 고유의 메소드와 내부 변수를 가진 여러분 만의 객체를 만드는 방법을 배워볼 것입니다.

하지만 과연 무엇이 "Matz"를 문자열로 만드는 걸까요? 해당 문자열은 정확히 말하자면 String(문자열) 클래스(class)의 인스턴스입니다. 클래스는 비슷한 속성과 메소드를 갖는 객체들을 관리하고 생산하는 방법이라고 생각하시면 됩니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Language
  def initialize(name, creator)
    @name = name
    @creator = creator
  end
 
  def description
    puts "이 언어는 #{@name} 이며, #{@creator}에 의해 개발되었습니다!"
  end
end
 
ruby = Language.new("루비""Yukihiro Matsumoto")
python = Language.new("파이썬""Guido van Rossum")
javascript = Language.new("자바스크립트""Brendan Eich")
 
ruby.description
python.description
javascript.description
cs


클래스(Class) 문법

기본적으로 클래스는 class 키워드와 클래스의 이름으로 구성됩니다. 확인해 보세요:

class NewClass
  # 여기에 클래스의 기능, 정보 작성
end

이렇게 생성된 클래스 NewClass는 해당 클래스에 속한 루비 객체를 만들 수 있는 능력을 갖습니다. (마치 "Hello!"가 String 클래스에, 4가 Fixnum 클래스에 속하는 것처럼) 프로그래머들간의 관례상, 클래스의 이름은 대문자로 시작하고, 단어와 단어 사이를 나타낼 때 밑줄을 사용하는 대신(즉, relyingonunderscores) 카멜 케이스(CamelCase, 주: 단어를 전부 붙여쓰는 대신 각 단어의 앞 글자를 대문자로 써서 마치 낙타등처럼 보이는 방법)를 사용합니다.

initialize 메소드는 일종의 클래스가 생성하는 객체를 "부팅하는" 기능을 담당합니다.


루비에서는 변수 앞에 @를 사용하여 해당 변수가 인스턴스 변수(instance variable)임을 나타낼 수 있습니다. 이는 해당 변수가 클래스의 인스턴스(instance)에 부여된다는 뜻이지요. 예를 들어,

class Car
  def initialize(make, model)
    @make = make
    @model = model
  end
end

kitt = Car.new("Pontiac", "Trans Am")

위의 코드는 Car 클래스의 인스턴스 kitt를 생성합니다. kitt는 고유의 속성, @make("Pontiac") 그리고 @model ("Trans Am")을 갖습니다. 이 고유의 속성을 나타내는 변수들은 kitt인스턴스에만 속하기 때문에 인스턴스 변수라고 불립니다.


첫 번째 객체(Object) 인스턴스화

클래스 이름에 .new를 붙여 호출하는 것으로 해당 클래스의 인스턴스를 생성할 수 있습니다:

me = Person.new("Eric")


클래스의 범위(Scope)

루비의 클래스에서 또다른 중요한 요소는 바로 범위(scope)입니다. 변수의 범위는 프로그램 내에서 해당 변수가 어느 맥락에서 사용 가능한지를 나타냅니다.

모든 변수가 언제나 루비 프로그램 안의 어떤 곳에서든지 접근 가능한 게 아니라는 걸 알면 놀라실 겁니다. 클래스에 관해 다룰 때, 어디서든 사용 가능한 변수(전역변수. Global variables), 특정 메소드에서만 사용할 수 있는 변수(지역변수. Local variables), 특정 클래스의 맴버로만 사용 가능한 변수(클래스 변수. Class variables), 클래스의 특정 인스턴스에만 사용할 수 있는 변수(인스턴스 변수. Instance variables)들이 존재합니다.

메소드 역시 똑같습니다: 몇몇은 어디서든 사용가능하고, 몇몇은 오직 특정 클래스의 맴버로만, 또 몇몇은 클래스의 특정 인스턴스 객체에만 사용할 수 있습니다.


$@, 또는 @@로 시작하는 몇몇 변수? 이 기호들은 각각의 변수들이 차례대로 전역 변수, 인스턴스 변수, 그리고 클래스 변수임을 나타내는 역할을 합니다. 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Computer
  $manufacturer = "Mango Computer, Inc."
  @@files = {hello: "Hello, world!"}
  
  def initialize(username, password)
    @username = username
    @password = password
  end
  
  def current_user
    @username
  end
  
  def self.display_files
    @@files
  end
end
 
# Computer의 새로운 인스턴스 생성:
hal = Computer.new("Dave"12345)
 
puts "사용자 이름: #{hal.current_user}"
# @username(인스턴스 변수)는 hal 인스턴스에 속함.
 
puts "생산 업체: #{$manufacturer}"
# $manufacturer(전역 변수)는 어디서든 접근 가능.
 
puts "파일: #{Computer.display_files}"
# @@files(클래스 변수)는 Computer 클래스에 속함.
cs


변수(Variables) 이름 부여하기

인스턴스 변수는 이름 앞에 @로 시작한다는 것, 기억하세요. 이는 관례가 아니라 반드시 지켜야 할 문법의 한 부분입니다! 항상 인스턴스 변수의 이름 앞에서는 @를 붙이셔야 합니다.

클래스 변수(Class variables)는 인스턴스 변수와 같지만 클래스의 인스턴스에 속하는 대신, 클래스 자체에 속합니다. 클래스 변수는 언제나 이름 앞에 두 개의 @를 붙여 만듭니다: @@files

전역 변수(Global variables)는 두 가지 방법으로 선언될 수 있습니다. 첫째 방법은 이미 다뤄본 익숙한 방법입니다: 클래스나 메소드의 밖에서 변수를 정의하면, 짜잔! 전역 변수가 됩니다. 만약 메소드나 클래스 안에서 전역 변수를 만들고 싶다면, 이름 앞에 $를 붙여주면 됩니다: $matz

인스턴스를 위한 변수(Instance Variable)

좋습니다! 전역 변수는 프로그램 안의 어디서든 변경될 수 있으며, 이들을 사용하는 건 일반적으로 그리 좋은 생각은 아닙니다. 특정 공간에서만 변경될 수 있는 제한된 범위의 변수를 생성하는게 훨씬 더 좋은 생각입니다! 예를 들어, 인스턴스 변수는 특정 객체(또는 인스턴스)에 속해 있습니다.


클래스 변수(Class variable) 활용하기

변수의 이름 앞에 @ 기호를 두 개 붙임으로써 클래스 변수를 생성할 수 있습니다. 클래스 변수는 해당 클래스의 인스턴스가 아니라, 다음과 같이 클래스 전체에 부여됩니다:

class MyClass
  @@class_variable
end

오직 하나의 클래스 변수를 해당 클래스의 모든 인스턴스들이 공유하기 때문에, 이를 이용하면 몇 가지 속임수를 고안할 수도 있습니다. 예를 들어, 클래스 변수를 이용하여 해당 클래스가 생성한 인스턴스의 숫자를 계속 기록할 수도 있습니다.


상속(Inheritance)은 헷갈리기 쉬운 개념이므로, 하나씩 천천히 살펴보겠습니다.

상속은 하나의 클래스가 또 다른 클래스로부터 속성과 메소드를 받는 과정으로, is-a 관계라고 표현됩니다. 예를 들어, 판다는 곰이므로, (Panda is a bear), Panda 클래스는 Bear 클래스로부터 상속받을 수 있습니다. 하지만 도요타 자동차는 트랙터가 아니므로, Toyota 클래스는 Trector 클래스로부터 상속받을 수 없습니다. (속성이나 메소드 등, 둘 사이의 많은 부분이 비슷하다고 하더라도 말이죠.) 대신 Toyota와 Trector 클래스 모두 자동차이므로, 같은 Vehicle 클래스로부터 상속받을 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
class ApplicationError
  def display_error
    puts "Error! Error!"
  end
end
 
class SuperBadError < ApplicationError
end
 
err = SuperBadError.new
err.display_error
cs


상속(Inheritance) 문법

루비에서 상속은 다음과 같이 작동합니다:

class DerivedClass < BaseClass
  # 클래스 바디 부분
End

위의 코드에서 DerivedClass(파생 클래스) 부분은 생성하고자 하는 새로운 클래스의 이름을 나타내며, < 기호는 상속을, BaseClass(베이스 클래스) 부분은 새로운 클래스가 상속받고자 하는 기존의 클래스 이름을 나타냅니다.


덮어 쓰기(Override)!

종종 부모 클래스로부터 상속받는 클래스가 단순히 메소드와 속성을 받기만 하는 것이 아니라, 그 위에 덮어 쓰도록(오버라이드. Override) 만들고 싶을 때가 있습니다.

예를 들어, Message 클래스로부터 상속받는 Email 클래스가 있다고 합시다. 두 클래스 모두 메시지나 메일을 전송하는 메소드 send를 가져야 할텐데요, 하지만 메시지와 달리 메일의 send메소드는 발신대상의 이메일 주소가 유효한지부터 이메일 프로토콜 사용 등의 기능이 추가되어야 합니다. 그렇다고 파생 클래스에 send_email이란 새로운 메소드를 추가하고, 상속받은 send메소드를 사용하지 않는 비합리적인 방법 대신, Email 클래스 안에 새로운 기능을 포함하도록 send 메소드를 재정의할 수 있습니다.

이렇게 새로이 정의된 메소드 send는 기존의 상속받은 send 메소드를 오버라이드(즉, 대체)할 것이며, Email 클래스의 모든 객체들은 오버라이드 된 메소드를 사용하게 됩니다.


좋은게 좋지 않을 때

반면에 파생 클래스(또는 서브클래스(subclass))를 가지고 작업을 하다보면, 종종 베이스가 되는 클래스(부모(parent) 클래스, 또는 슈퍼클래스(superclass))로부터 상속받은 속성이나 메소드를 덮어 썼는데, 알고 봤더니 상속받은 값이 필요하다는 걸 깨달을 때도 종종 있습니다. 겁먹지 마세요! 루비의 내장 키워드인 super를 호출해서 슈퍼클래스의 속성이나 메소드에 직접 접근할 수 있습니다.

문법은 다음과 같습니다:

class DerivedClass < Base
  def some_method
    super(추가적인 인자)
      # 코드
    end
  end
end

메소드 안에 super를 호출하면, 루비는 해당 클래스의 슈퍼클래스로부터 같은 이름을 가진 메소드를 찾습니다. 만약 찾으면 루비는 오버라이드된 메소드 대신 해당 슈퍼클래스의 메소드를 실행합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Creature
  def initialize(name)
    @name = name
  end
  
  def fight
    return "Punch to the chops!"
  end
end
 
class Dragon < Creature
    def fight
        puts "Instead of breathing fire..."  
        super
    end
end
cs


오직 하나만 가능합니다!

루비 안의 모든 클래스는 오직 하나의 슈퍼 클래스를 가질 수 있습니다. 다른 프로그래밍 언어 중에서는 다중 상속(multiple inheritance)을 통해 하나 이상의 부모 클래스를 갖도록 허용하는 경우도 있습니다. 이를 이용하면 코드 작성이 정말 빨라지지만 동시에 지저분해지므로, 루비는 이를 허용하지 않습니다.

그럼에도 필요에 따라 다중 상속을 사용해야 할 때도 있는데요, 루비에서는 믹스인(mixin)을 통해 다중 상속을 이용할 수 있습니다. 하나 더, 오른쪽의 예제 코드 안에는 유용하게 사용할 수 있는 요령이 하나 포함되어 있습니다: 세미콜론(;)을 사용하면 새로운 줄을 사용하지 않고도 루비 명령문을 한 줄에 끝낼 수 있습니다. 이 말은 즉,

class Monkey
end

위와 같은 코드를 class Monkey; end 와 같이 한 줄로 만들 수 있다는 뜻입니다. 이는 여러분이 비어있는 클래스나 메소드 정의 같이 매우 짧은 코드를 작성할 때 시간을 절약하도록 돕습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Machine
  @@users = {}
  
  def initialize(username, password)
    @username = username
    @password = password
    @@users[username] = password
    @files = {}
  end
  
  def create(filename)
    time = Time.now
    @files[filename] = time
    puts "#{filename} 파일이 생성되었습니다. (작성자: #{@username} 일시: #{time})"
  end
  
  def Machine.get_users
    @@users
  end
end
 
my_machine = Machine.new("eric"01234)
your_machine = Machine.new("you"56789)
 
my_machine.create("groceries.txt")
your_machine.create("todo.txt")
 
puts "Users: #{Machine.get_users}"
cs


루비는 작성한 메소드를 public(공개)와 private(비공개) 상태로 만들 수 있도록 하고 있습니다. 퍼블릭 메소드(Public Method)는 프로그램의 모든 부분에서 접근할 수 있게 인터페이스(interface)를 허용하는데요, 이는 다음처럼 말하는 것과 같습니다: "이봐! 만약 이 클래스나 여기서 생성된 인스턴스에 관해 알아야 할 게 있으면 나에게 물어봐."

반면에 프라이빗 메소드(Private Method)는 해당 클래스가 작업을 수행하는데 방해받지 않도록 합니다. 이들과 관련된 정보는 물을 수 없기 때문에, 해당 메소드는 접근불가능한 메소드가 됩니다!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Person
  def initialize(name, age)
    @name = name
    @age = age
  end
  
  public    # 이 메소드는 클래스 밖에서도 호출 가능합니다.
  
  def about_me
    puts "제 이름은 #{@name} 이고 나이는 #{@age} 입니다!"
  end
  
  private   # 이 메소드는 클래스 안에서만 호출 가능합니다!
  
  def bank_account_number
    @account_number = 12345
    puts "제 은행 계좌번호는 #{@account_number} 입니다."
  end
end
 
eric = Person.new("Eric"26)
eric.about_me
eric.bank_account_number
cs


퍼블릭 메소드(Public Method)

루비에서 메소드의 기본값은 퍼블릭으로 지정되어 있습니다. 그러므로 따로 public이나 private을 명시하지 않으면 메소드는 자동적으로 퍼블릭이 됩니다. 하지만 여기서는 코드를 읽는 사람들을 위해 어떤 메소드가 퍼블릭인지를 명확하게 표시하려고 합니다. 메소드를 퍼블릭으로 만들려면 다음과 같이 메소드 정의 이전에 public이라고 작성하면 됩니다:

 class ClassName
  # 클래스 정의 부분
  public
  def public_method
    # 메소드 정의 부분
  end
end

위와 같이 작성할 경우, 따로 프라이빗을 명시하지 않는 한 public 키워드부터 클래스 정의의 end 키워드까지 모든 내용은 퍼블릭으로 공개된다는 점, 명심하세요.


프라이빗 메소드(Private Method)

public 키워드를 사용하여 퍼블릭 메소드를 알렸던 것처럼, private 키워드를 사용하여 프라이빗 메소드를 지정할 수 있습니다:

class ClassName
  # 클래스 정의 부분

  public
  # 여기서부터 퍼블릭 메소드
  def public_method; end

  private
  # 여기서부터는 프라이빗 메소드
  def private_method; end
end

(세미콜론을 이용, 비어있는 메소드 정의를 한 줄에 작성하는 요령에 대해 지난 수업에서 다룬 적이 있었습니다.)

프라이빗 메소드는 해당 메소드가 정의된 객체 외에는 비공개로, 해당 객체 안에서만 이 메소드들을 호출할 수 있다는 뜻입니다. 다른 말로 표현하자면, 이 메소드는 명시적 수신자(explicit receiver)로 호출할 수 없습니다. 여러분은 지금까지 줄곧 수신자(receiver)를 사용해 왔는데요, 수신자란 메소드가 호출되는 객체를 말합니다! 즉, object.method라고 호출할 때, object는 method의 수신자가 되는 셈입니다.

프라이빗 정보에 접근하기 위해선, 여기에 접근하는 법을 아는 퍼블릭 메소드를 생성해야 합니다. 이는 퍼블릭 인터페이스(interface)로부터 프라이빗 임플리멘테이션(implementation)을 구분짓습니다.


attr_reader, attr_writer

속성에 접근하기 위해선 메소드가 필요하다고 클래스 관련 수업에서 살펴보았었습니다. 예를 들어, 만약 인스턴스 변수 @name에 접근하려면 다음과 같이 작성해야만 했었죠:

def name
  @name
end

하지만 더 이상은 아닙니다! attr_reader를 사용하여 변수에 접근할 수 있고, 또 attr_writer를 사용하여 변수를 변경할 수 있습니다. 만약 다음과 같이 작성한다면,

class Person
  attr_reader :name
  attr_writer :name
  def initialize(name)
    @name = name
  end
end

루비는 자동으로 아래의 코드와 똑같은 기능을 실행할 겁니다:

def name
  @name
end

def name=(value)
  @name = value
end

이를 통해 마치 마법처럼 원하는 대로 변수를 읽고 쓸 수 있습니다! 단지 원하는 인스턴스 변수를 (심볼 형태로) attr_reader나 attr_writer에 전달하면 됩니다.

(name=라는 코드가 웃기게 보일 수도 있습니다. 하지만 이는 메소드 이름에 = 기호를 사용할 수 있다는 뜻으로, 관례상 "이 메소드는 값을 지정"한다는 의미를 나타냅니다!)


attr_accessor

만약 특정 변수를 읽고 써야 한다면(즉, 접근과 변경 모두 가능하도록 해야한다면), attr_reader와 attr_writer를 사용하는 것보다 더 짧고 손쉬운 방법이 있습니다. attr_accessor를 사용해서 변수를 읽고 쓰기 모두 가능하도록 만들 수 있습니다.


모듈(Module)이 무엇인가요?

모듈(Module)이란 메소드나 상수 등이 담겨있는 일종의 도구상자로 생각하시면 됩니다. 루비에는 사용할 수 있는 도구가 정말 수도 없이 많습니다. 하지만 매번 이 도구들을 모두 간직한다면 인터프리터가 복잡하고 어수선해질 겁니다. 이런 이유로 인해 모듈 안에 몇몇 도구들(메소드나 상수 등)을 보관해 두었다가 필요할 때에만 꺼내 쓰는 것입니다!

모듈을 클래스와 매우 비슷하다고 생각하셔도 되지만, 모듈은 인스턴스를 생성할 수 없으며 서브 클래스를 가질 수도 없습니다. 모듈은 오로지 도구들을 보관하는 데에만 사용됩니다!

1
2
3
4
5
6
7
8
9
10
11
12
module Circle
 
  PI = 3.141592653589793
  
  def Circle.area(radius)
    PI * radius**2
  end
  
  def Circle.circumference(radius)
    2 * PI * radius
  end
end
cs


모듈(Modules) 문법

이미 존재하는 모듈을 가져올 수도 있지만, 개인용인 모듈 또한 만들 수도 있습니다. 모듈을 만드는 법은 정말 쉽습니다! 다음과 같이 그냥 module 키워드를 사용하기만 하면 됩니다:

module ModuleName
  # 모듈에 포함될 코드
end

클래스 이름처럼, 모듈의 이름 역시 첫 글자는 대문자로 작성해야 하며 카멜 케이스(CamelCase. 주: 단어를 전부 붙여쓰는 대신 각 단어의 앞 글자를 대문자로 써서 마치 낙타등처럼 보이는 방법)로 작성해야 합니다. (예: CapitalizedCamelCase)

모듈 안에 변수(variables)를 저장한다는 건 말이 되지 않습니다. 왜냐하면 변수는 정의에 따라서 변경될 수 있기 때문입니다. 하지만 상수(constants)는 언제나 변하지 않고 값을 유지하기 때문에, 모듈 안에 상수를 저장하는 게 훨씬 좋은 생각입니다.

물론 상수의 값이 한 번 초기화 된 이후부터 무조건 변경될 수 없는 것은 아니지만, 변수와 달리 값을 바꾸려고 한다면 경고를 표시할 것입니다. 루비에서 상수는 전부 대문자로 작성하며, 만약 두 단어 이상일 경우엔 밑줄로 단어 사이를 구분합니다. (예: ALL_CAPS)

루비 상수의 예시 중 하나는 PI로, Math 모듈 안에 담겨져 있으며 값은 얼추 3.141592653589793 쯤 됩니다. 이전 과제에서 또 다른 PI 상수를 생성했지만 걱정하지 마세요: 이들은 서로 다른 모듈 안에 담겨져 있기 때문에, 루비는 둘 사이에서 충돌을 일으키지 않습니다.


명칭 공간(Namespace)과 영역 결정(Scope Resolution)

모듈의 주된 목적 중 하나는 메소드와 상수들을 이름지어진 공간에 따로 분리시켜 놓는 것입니다. 이를 가리켜 명칭 공간(namespace)이라 부르며, 루비가 Math::PI와 Circle:PI를 구별할 수 있는 이유도 이 때문입니다.

방금 설명에서 사용했던 콜론 두개(::)가 보이시나요? 이는 영역 결정 연산자(scope resolution operator)로, 루비에게 어느 공간에서 해당 코드를 찾을지 알려주는 역할을 담당합니다. 따라서 Math::PI라고 작성한다면 루비는 다른 곳에서 PI(이를 테면 Circle 안에 만들어 둔 PI)를 찾지 않고, Math 모듈 안에서 PI를 찾을 것입니다.

상수 뿐만 아니라 메소드에 접근하는 데에도 역시 영역 결정 연산자를 사용할 수 있습니다. 여기서 기억해 두셔야 할 점: 모듈은 인스턴스화될 수 없기 때문에 def some_name과 같은 방법을 사용할 수 없다는 것입니다. 이는 인스턴스 메소드를 만들 뿐입니다! 대신에 두 가지 방법을 통해 클래스/모듈 단위에서 메소드를 생성할 수 있습니다: 첫 번째 방법은 다음과 같이 모듈에 메소드를 호출하는 것이고:

module Circle
  def Circle.area(radius)
    PI * radius**2
  end

두 번째 방법으로는 self를 이용하는 것입니다. 루비에서 self는 항상 현재 객체를 나타냅니다; 현재 코드에선 Circle을 이야기하고 있으므로, 다음과 같이 간단히 작성할 수 있습니다:

module Circle
  def self.area(radius)
    PI * radius**2
  end

클래스 메소드 정의에도 이 방법을 똑같이 활용할 수 있습니다. 예, 여러분의 생각이 맞습니다: 모듈과 클래스 모두 결국에는 객체(objects)입니다. 정말 이상하죠?


Math와 같은 몇몇 모듈들은 인터프리터 안에 미리 주어져 있습니다. 하지만 그 외의 모듈들은 사용하기 위해 직접 불러와야 하며, require를 통해 다음과 같이 불러올 수 있습니다:

require 'module'

위의 코드에서 'module' 부분 안에 불러올 모듈의 이름을 넣어주시면 됩니다. 이번 과제에서는 Date 모듈을 사용하여 오늘의 날짜를 표시하려 하는데요, 아직 해당 모듈을 불러올 것을 요구(require)하지 않았습니다!


모듈 포함시키기(Include)

require를 통해 모듈을 불러오는 것 외에도, include를 통해 모듈을 포함시킬 수도 있습니다!

require를 통해 불러온 모듈을 include를 통해 포함시키면, 모듈 안의 모든 메소드와 상수들을 해당 인스턴스 단계에 불러올 수 있습니다. 즉, 어떤 클래스든지 특정 모듈을 포함(include)시키기만 하면 해당 모듈의 메소드를 똑같이 사용할 수 있는 객체를 생성할 수 있다는 뜻입니다!

모듈을 포함시킴으로써 얻는 좋은 효과로는, 모듈 이름을 상수와 메소드 앞에 더 이상 붙이지 않아도 된다는 점입니다; 모듈 안의 모든 것들을 불러왔기 때문에, Math::PI 대신 간단히 PI라고만 적어도 된다는 것이죠.

지금와서 밝히지만, 사실 여러분에게 거짓말을 했었습니다. 직접적으로 모듈을 인스턴스화 할 수 없음에도 불구하고, 모듈 안에 인스턴스 메소드를 만들기도 합니다: 이는 해당 인스턴스 메소드를 클래스 안에 포함(include)시키기 위해서입니다. 여러분이 인스턴스화할 수 있는 바로 그 클래스에 말입니다.


모듈(Modules)과 클래스(Classes)의 결혼

클래스에 추가적인 기능이나 정보를 섞는데 모듈이 사용될 때, 이를 가리켜 믹스인(mixin)이라고 부릅니다. 믹스인은 우리가 다시 코드를 작성할 필요없이도 클래스를 커스터마이징할 수 있도록 합니다!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
module Action
  def jump
    @distance = rand(4+ 2
    puts "앞을 향해 #{@distance} 피트 점프!"
  end
end
 
class Rabbit
  include Action
  attr_reader :name
  def initialize(name)
    @name = name
  end
end
 
class Cricket
  include Action
  attr_reader :name
  def initialize(name)
    @name = name
  end
end
 
peter = Rabbit.new("Peter")
jiminy = Cricket.new("Jiminy")
 
peter.jump
jiminy.jump
cs


지식의 확장(Extend)

include가 모듈의 메소드들을 인스턴스 단계에서 섞는 (즉, 특정 클래스의 인스턴스들이 해당 메소드들을 사용할 수 있도록 하는) 반면에, extend키워드는 모듈의 메소드들을 클래스 단계에서 섞습니다. 이는 즉 클래스 자체가 해당 메소드를 사용할 수 있다는 뜻입니다.

1
2
3
4
5
6
7
8
9
10
11
module ThePresent
  def now
    puts "현재 시각: #{Time.new.hour > 12 ? Time.new.hour - 12 : Time.new.hour}:#{Time.new.min} #{Time.new.hour > 12 ? 'PM' : 'AM'} (GMT)."
  end
end
 
class TheHereAnd
  extend ThePresent
end
 
TheHereAnd.now
cs


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Account
  attr_reader :name, :balance
  def initialize(name, balance=100)
    @name = name
    @balance = balance
  end
  
  def display_balance(pin_number)
    puts pin_number == pin ? "잔액: $#{@balance}." : pin_error
  end
  
  def withdraw(pin_number, amount)
    if pin_number == pin
      @balance -= amount
      puts "출금: #{amount}. 출금 후 잔액: $#{@balance}."
    else
      puts pin_error
    end
  end
  
  private
  
  def pin
    @pin = 1234
  end
  
  def pin_error
    "접근 불가: 잘못된 PIN."
  end
end
 
my_account = Account.new("Eric", 1_000_000)
my_account.withdraw(11, 500_000)
my_account.display_balance(1234)
my_account.withdraw(1234, 500_000)
my_account.display_balance(1234)
cs


참고 : https://www.codecademy.com/learn/ruby

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함