Ruby 프로그래밍 1 문자열 입출력 제어구조 looping 함수 정의 한국어 정보의 전산 처리 2017. 4. 24.
코드, 컴파일 기계 코드(machine code): 컴퓨터가 직접 실행할 수 있는 코드. 이진수로 되어 있어서 이진 코드(binary code)라고도 함. 파일로 저장할 때 흔히 .exe라는 확장자를 가짐. executable이라고도 함. 하드웨어에 따라 차이가 있음. 인간이 직접 작성하기가 매우 힘듦. 소스 코드(source code): 인간이 이해하고 작성하기 쉽게 고안한 코드. 컴파일(compile): 소스 코드를 기계 코드로 바꾸는 일. 컴파일러(compiler): 컴파일해 주는 소프트웨어 어셈블리 코드(assembly code): 소스코드와 기계 코드의 중간쯤 되는 코드. 기계 코드로 1대1로 번역 가능한 명령들로 이루어짐. 어셈블러(assembler): 어셈블리 코드를 기계 코드로 번역해 주는 소프트웨어.
기계 코드와 어셈블리 코드의 예 x86/IA-32 프로세서에게 8비트 값 01100001을 메모리의 특정 레지스 터 AL(000)로 옮기라(10110)는 기계 코드 10110 000 01100001 = B0 61 같은 의미를 나타내는 8086 family의 어셈블리 코드 MOV AL, 61h (메모리 주소 AL에 16진수 61을 로드하라) 어셈블리 코드는 기계 코드에 직접 대응하는 명령을 사용하기 때문에, 전자를 후자로 매우 쉽게 번역할 수 있음. 실행 속도가 매우 빠름. 매크로(macro)를 이용하여, 복잡한 코드를 간단하게 나타낼 수 있음. 동 일하게 반복되는 부분은 매크로로 처리하면 효율적. 구조적 프로그래밍을 가능케 함. 인간 친화적이 아니라, 기계 친화적 인간이 기계에게 익숙한 층위까지 내려가서 프로그래밍하는 셈.
컴파일형 언어와 인터프리트형 언어 컴파일형 언어: C, C++, Fortran 인간이 소스 코드를 작성. 컴파일러가 이 소스 코드를 기계 코드로 번역. 실행 파일(executable)을 만들어 저장함. 사용자는 이 실행 파일을 실행함으로써 특정 과제를 수행함. 컴파일은 1번만 하고, 컴파일 결과를 여러 차례 반복 실행함. 인터프리트형 언어: Perl, Python, Ruby 인간이 소스 코드를 작성하여 인터프리터가 이 소스 코드를 기계 코드로 번역하여 이 기계 코드를 곧장 실행함. (실행 파일 단계를 건너뜀) 실행할 때마다 컴파일을 다시 함. 중간형 언어: Java, C# 인간이 작성한 소스 코드를 바이트 컴파일러가 바이트 코드로 번역하여 저장. 가상 기계(virtual machine)가 바이트 코드를 기계 코드로 번역하여 실행함. 즉 소스코드를 절반쯤 컴파일한 뒤, 나머지 절반쯤의 컴파일은 그때그때 함.
각 유형의 장단점 컴파일형 인터프리트형 중간형 컴파일 비용 컴파일을 1번만 하므로 비용이 적게 듦 실행할 때마다 다시 컴파일해야 하므로 비용이 많이 듦 절반의 컴파일은 미리 해 두고 절반의 컴파일만 그때그때 하므로 드는 비용이 중간 실행 속도 빠름 느림 중간 실행을 위한 전제 조건 프로그래머의 컴퓨터에는 컴파일러가 있어야 하나, 사용자의 컴퓨터에는 컴파일러가 필요 없음 사용자의 컴퓨터에 인터프리터가 설치되어 있어야 함. 비용이 가장 큼 앞 절반의 컴파일은 프로그래머의 컴퓨터에서 함. 사용자 컴퓨터에는 뒤 절반의 컴파일을 해 줄 가상 기계가 설치되어 있어야 함. 소스 코드 보안 프로그래머는 소스 코드를 감출 수 있음. 컴파일 결과인 실행 파일만 배포하면 됨 소스 코드를 감출 수 없음. 프로그래머가 사용자에게 소스 코드를 배포해야 함. 소스 코드는 감출 수 있음. 절반쯤 컴파일한 바이트 코드를 배포함. 개인적 사용시 컴파일, 실행 두 단계 거쳐야 하므로 번거로움 컴파일 단계 거치지 않고 곧장 실행하므로 간편함 역시 컴파일, 실행의 두 단계를 거쳐야 하므로 번거로움
object와 type physical object 물리적 개체, 물체 (programming) object 프로그래밍 개체 시공간상의 일정한 부분을 차지하는 존재 (programming) object 프로그래밍 개체 컴퓨터 메모리상의 일정한 부분(공간)을 차지하는 존재 프로그래밍에서 가장 기초적이고 중요한 개념 프로그래밍이란, 메모리상에 개체를 만들고 이 개체를 조작하는 일 object-oriented programming 개체(객체) 지향 프로그래밍 프로그래밍의 초기에는 개체에 주목하지 않고 기계가 수행할 연산에 주목하다가 점차 개체를 전면에 내세우는 프로그래밍 언어와 기법이 주류가 됨. 모든 개체는 일정한 type(class)에 속함. 개체가 속하는 type에 따라 그 개체에 대해 할 수 있는 일이 정해져 있음. 문자열: 프린트하기, 두 문자열을 이어 붙이기, 문자열 내에서 하위문자열 찾기 정수: 사칙연산(더하기, 빼기, 곱하기, 나누기), 나머지, 제곱, ... 실수: 사칙연산, 제곱, ... enumerable: 자신이 포함한 각 개체 각각에 대해 어떤 일을 함(iterator: each, map, collect 등)
변수(variable)와 type 메모리상에 만든 개체에 대해 조작을 하고 이 개체에 일어나는 변화를 추 적하려면, 개체를 가리키는 이름이 필요함. 이 이름이 바로 변수임. named object: 변수와 연결된, 즉 이름을 가진 개체 unmamed object: 이름을 갖지 않는 개체. literal이라고도 함. 만들어서 한 번만 쓰고 말 개체는 굳이 이름을 가질 필요가 없음. 하나의 변수가 A라는 개체를 가리키는 이름으로 쓰이다가, 나중에 B라는 개체를 가리키는 이름으로 쓰일 수도 있음. (그래서 변수라고 함) 달리 말하면, 변수가 가리키는 개체의 값(value)이 변할 수 있음. cf. 상수(constant): 값이 변할 수 없는 개체. immutable이라고도 함. strongly typed language: 개체뿐 아니라 모든 변수가 type을 가짐.예:C++ weakly typed language: 개체는 type을 가지나, 변수는 type을 가지지 않 음. 하나의 변수가 가리키는 개체의 type이 바뀌어도 됨. 예: Ruby
함수(function) 특정 임무를 수행하는 프로그래밍 단위 일정한 입력에 대해 일정한 조작을 하여 일정한 출력을 내놓음. 함수 없이 프로그래밍을 하다가 동일한 코드가 반복되게 된다면, 반 복되는 부분을 함수로 독립시킬 것을 고려해야 함. free function: 특정 type의 개체에 매여 있지 않고, 자유롭게 호출할 수 있는 함수 class function=method: 특정 type의 개체에 대해서만 호출할 수 있 는 함수 non-mutating method : 개체에 변화를 초래하지 않는 method mutating method : 개체에 변화를 초래하는 method Ruby에서 method명 뒤에 !를 붙여서 구별함.
type system: built-in type Boolean: 참(true)과 거짓(false)의 두 가지 값만 가질 수 있는 개체 String: 문자열. “ “ 또는 ‘ ‘와 같이 하여 만듦. Integer: 정수 Fixnum: 1 (native machine) word로 표시될 수 있는 값을 가짐. Bignum: Fixnum의 범위를 넘어서는 값을 가짐. Fixnum 개체에 대한 어떤 연산 결과가 Fixnum의 범위를 넘으면 자동으로 Bignum으로 변환됨 (IRB를 통해 예시) Float: 실수. 컴퓨터의 실수 표시 메커니즘을 이용하여 나타냄. 정확하지 않음. 루트 2(Math::sqrt(2))의 제곱이 2.0이 되지 않을 수도 있음. Enumerable: 개체들의 집합체 Range: (n..m) n부터 m까지의 개체들의 집합체. 순서가 중요함. Array: [a,b,c] 임의의 요소들을 차례로 모아 놓은 연쇄. 순서가 중요함. 상이한 type에 속하는 개체들이 하나의 Array에 속하는 것도 가능. Hash: {a=>20, b=>35, c=>”xyz”} key-value 쌍들을 모아 놓은 집합체. 순서는 중요하지 않음.
type system: user-defined type 사용자가 원하는 type(class)을 정의해서 사용할 수 있음. class Student def initialize(name, serial, age) @name = name @serial = serial @age = age end 새로운 type을 정의할 때, 반드시 initialize라는 method를 만들어야 함. 이 type에 속하는 개체를 만들 때 이 method가 호출됨. 위의 Student class의 경우 a = Student.new(“John”,372,21)이라고 하면 Student type에 속하는 개체가 만들어져 변수 a가 이 개체를 가리키며 이 개체에 딸린 3개의 변수에 “John”, 372, 21이라는 값이 할당됨.
statement와 expression a = 10 #a라는 변수에 10의 값을 할당하라는 statement print b #b라는 변수에 저장되어 있는 값을 출력하라는 statement expression: 하나의 value로 evaluate될 수 있는 표현 (a+b)*c #a와 b를 더하고 거기에 c를 곱하여 그 결과값이 얻어짐. Math::sqrt(a) #a라는 변수에 저장된 값을 입력으로 하여 제곱근 함수를 적용하 여 그 결과값이 얻어짐. x.abs #x라는 변수에 저장된 값에 대해 abs라는 method를 적용하여 그 결과값 이 얻어짐 x == y #변수 x에 저장된 값과 변수 y에 저장된 값이 같다고 하는 명제. 참 또 는 거짓이라는 값으로 evaluate됨. 프로그래밍을 할 때 둘 중 어느 하나만 올 수 있는 위치가 있으므로 개 념적으로 구분할 필요가 있음. 그러나 최근에는 statement도 어떤 값으로 evaluate되는 경향이 있음. 둘의 경계가 점차 모호해지고 있음.
제어구조(control flow): if문 실행할 명령들 1 elsif 조건2 실행할 명령들 2 elsif 조건3 실행할 명령들 3 …… else 실행할 명령들 n end elsif 블록은 얼마든지 추가 가능 x ? y : z #x가 참이면 y, 거짓이면 z 로 evaluate되는 expression if 조건 실행할 명령들 end 조건: 참, 거짓으로 evaluate될 수 있는 expression 조건이 참일 때만 블록이 실행됨. 실행할 명령 if 조건 명령이 1개일 때 편리 실행할 명령들 1 else 실행할 명령들 2
제어구조: case문 case문은 if ~ elsif ~ … else ~ end 형태로 치 환 가능하기는 하나 case문은 statement뿐 아니라 expression에 도 적용될 수 있음. 변수 = case expression when value1 expression 1 when value2 expression 2 …… else expression n end expression의 값에 따라 변수에 상이한 값 할당 제어구조: case문 case expression when value1 실행할 명령들 1 when value2 실행할 명령들 2 …… else 실행할 명령들 n end
반복문(loop) while 조건 do end until 조건 do 정수.times do 실행할 명령들 end 조건이 참인 동안 블록을 반복 실행 until 조건 do 조건이 거짓인 동안 블록을 반복 실행 정수.times do 블록을 ‘정수’번 반복 실행 for 변수 in Enumerable do 실행할 명령들 end Enumerable 안에 들어 있는 각 개체 를 ‘변수‘라는 이름으로 지칭하면서 이 개체에 대해 명령을 실행함. Ruby에서는 같은 일을 하는 더 Ruby다운 syntax가 있기 때문에 for loop를 잘 쓰지 않음. Enumerable.each do |변수|
새로운 개체를 만드는 방법 new method를 이용하는 방법 literal을 변수에 할당하는 방법 a = String.new #String type에 속하는 비어 있는 개체, 즉 빈 문자열을 만듦. b = Array.new #Array type에 속하는 비어 있는 개체, 즉 빈 Array를 만듦. c = Hash.new #Hash type에 속하는 비어 있는 개체, 즉 빈 Hash를 만듦. literal을 변수에 할당하는 방법 a = 10 # 정수의 경우 new 메소드 이용 불가 b = 2.5 # 실수의 경우 new 메소드 이용 불가 c = "xyz“ # c = String.new(“xyz”)와 같은 효과를 가짐 d = "" # d = String.new와 같은 효과를 가짐 e = [] # e = Array.new와 같은 효과를 가짐 f = {} # f = Hash.new와 같은 효과를 가짐
unnamed object의 활용 프로그래밍의 초보 단계에서는, 명령을 하나하나 실행할 때마다 만들어 지는 개체에 이름(변수)을 부여하는 것이 좋으나 익숙해짐에 따라, 한 번 사용하고 말 개체는 이름을 부여하지 않아도 됨. 방식 1 x = -5 #정수 개체를 만들어 그 값을 -5로 하고 이 개체를 가리키는 변수를 x로 지정 y = x.abs #변수 x가 가리키는 개체에 abs 메소드를 적용하여 생기는 개체를 가 리키는 변수로 y를 지정 print y #이 y가 가리키는 개체를 출력 방식 2 print -5.abs # -5 값을 갖는 정수 개체(unnamed object 1)를 만들어 이 개체에 abs() 메소드를 적용하여 생기는 개체(unnamed object 2)를 출력 command line에서 작업의 중간 결과를 파일로 저장하지 않고 여러 명 령을 파이프 |로 연결하여 단번에 실행하는 것, R에서 파이프 %>%로 여 러 함수를 연결해서 사용하는 것과 비슷한 취지
Ruby script의 shebang Unix/Linux/Cygwin 환경에서 인터프리터로 실행할 script의 첫 라인에 서 어떤 인터프리터를 사용할 것인지 명시해 줘야 함. 아래의 두 방식이 흔히 쓰임. 후자를 더 권장함. #!/usr/bin/ruby /usr/bin 디렉토리에 있는 ruby라는 이름의 executable을 사용하라. #!/usr/bin/env ruby /usr/bin 디렉토리에 있는 ruby라는 이름의 executable을 사용하되 만약 그 디렉토리에 ruby executable이 없으면 환경변수 path에 지정된 디렉 토리들에서 찾아 보라. ruby script의 첫 라인에 shebang이 있으면, 스크립트파일명만으로 실 행할 수 있음: ./script script가 들어 있는 path/script ruby script의 첫 라인에 shebang이 없으면, ruby script 실행시 인터프 리터를 명시해 줘야 함: ruby 스크립트파일명
출력 print, puts, 입력 gets, String method “Hello!”를 출력하는 스크립트 print “Hello!” #”Hello!”라는 문자열을 표준출력장치에 출력 사용자로부터 이름을 입력받아, “Hello, 이름!”을 출력 print “이름을 입력하세요: “ name = gets #사용자가 입력한 문자열을 읽어서 name 변수에 저장 #사용자가 타이핑한 뒤 엔터를 치면 gets가 실행됨 #사용자가 마지막에 타이핑한 줄바꿈문자까지 포함됨. name = name.chomp #name 문자열 끝의 줄바꿈문자를 떼어내어 다시 저장 #name.chomp!와 같은 효과 #위의 두 줄은 name = gets.chomp와 같은 효과 print “Hello, “, name, “!” #3개의 문자열을 연달아 출력 puts는 print와 같되, 문자열 끝에 줄바꿈문자를 추가하여 출력함. 사용자가 입력한 문자열을 뒤집어서 출력: reverse method 이용
command line arguments ruby script를 실행할 때 script 파일명 뒤에 추가로 정보를 써 줄 수 있 는데 이를 command line argument라고 함. 공백으로 구분된 문자열들이 ARGV라는 이름의 Array에 저장됨. 첫째 요소는 ARGV[0], 둘째 요소는 ARGV[1], …… 사용자로부터 입력받은 문자열 뒤집어 출력 print ARGV[0].reverse 사용자로부터 2개의 문자열을 입력받아, 둘째 문자열을 뒤집어서 첫째 문자열 뒤에 붙여 출력 print ARGV[0], ARGV[1].reverse 사용자로부터 문자열을 입력받아 그 문자열의 길이를 출력 print ARGV[0].length UTF-8 문자열에서, 각 문자가 몇 바이트를 차지하든 하나의 문자로 침.
loop 이용 기초: range 1부터 100까지, 한 라인에 하나씩 출력 (1..100).each do |i| puts i end (1..100).each { |i| puts i } do ~ end와 { ~ }는 동일함. 대개 ~가 one-liner일 때 후자 사용 사용자로부터 시작값과 끝값을 입력받아 그 사이의 정수들 출력 유의점: gets로 입력받든 command line argument로 입력받든 사용자로부터 입력받은 데이터는 기본적으로 String임. 필요한 경우 type 변환을 해야 함. x.to_i #x의 type을 정수(integer)로 변환함. 크기에 따라 Fixnum, Bignum이 자동적으로 결정됨. x.to_f #x의 type을 실수(floating number)로 변환함. x.to_s #x의 type을 문자열(string)로 변환함. (ARGV[0].to_i..ARGV[1].to_i).each { |i| puts i } 문자열 range도 가능. 사용자로부터 입력받은 시작문자열, 끝문자열 사이의 문자열들 출력하기: (ARGV[0]..ARGV[1]).each { |s| puts s }
loop 내에서 if문으로 조건 검사 사용자로부터 입력받은 정수 range 내에서 짝수만 출력 (ARGV[0].to_i..ARGV[1].to_i).each do |i| puts i if i % 2 == 0 # i를 2로 나눈 나머지가 0이면 i 출력. i.even?을 써도 됨 end 홀수만 출력하기, 3의 배수만 출력하기 i % 2 == 1 또는 i.odd? i % 3 == 0 제곱근이 정수인 것(정수의 제곱인 것)만 출력하기 sqrt_of_i = Math::sqrt(i) #Math::sqrt 함수를 이용하여 i의 제곱근을 구함. 결과값은 실수. puts i if sqrt_of_i == sqrt_of_i.to_i #제곱근 결과값에 .to_i method를 적용(소수점 이하를 버림)한 결과가 제곱근 자체와 같으면, 즉 소수점 이하가 0이면 i 출력 end #방법 1 i = ARGV[0].to_i #첫째 command line argument(시작값)를 정수로 변환하여 i에 저장 while i**2 <= ARGV[1].to_i do # until i**2 > ARGV[1].to_i와 같음. # i의 제곱이 상한선 이하이면 아래 블록을 반복 실행함. puts i**2 # i의 제곱을 출력 i += 1 # i = i +1과 같음. i의 값을 1 증가시킴. end #방법 2
더하기, 평균 구하기 사용자로부터 입력받은 시작값부터 끝값까지의 정수들의 합, 평균 구하기 sum = 0 #정수들을 합을 저장할 변수 sum을 0으로 초기화 range = (ARGV[0].to_i..ARGV[1].to_i) #하한선..상한선 range를 저장 range.each do |i| #range 내의 각 정수 i에 대해 sum += i # sum = sum + i와 동일. sum에 i를 더함. end print sum, “\t”, sum.to_f / range.size #sum과 평균을 출력 평균을 낼 때 sum을 우선 실수로 변환해야 함에 유의할 것. 정수와 정수의 연산은 결과값도 정수가 됨. (소수점 이하 버림) 실수와 실수의 연산은 결과값도 실수가 됨. 실수와 정수의 연산은 결과값이 실수가 됨. 같은 type에 속하는 개체들끼리의 연산은 결과값도 같은 type의 개체가 됨. 정보량이 많은 type의 개체와 정보량이 적은 type의 개체 사이에 연산을 할 때, 후 자가 전자로 자동으로 변환되어 연산을 수행함.
사용자로부터 입력받은 정수들의 합, 평균 1 print "정수들의 합, 평균을 구해 드립니다. 입력할 정수의 개수: " n = gets.chomp.to_i #입력할 정수의 개수를 n에 저장 sum = 0 #합을 저장할 변수 sum을 0으로 초기화 n.times do #아래의 블록을 n번 반복 실행 i = gets.chomp.to_i #사용자가 입력한 것을 정수로 변환, i에 저장 sum += i #sum에 i를 더함 #위의 두 줄은 sum+=gets.chomp.to_i라고 할 수도 있음. unnamed object 이용. end print sum, “\t”, sum.to_f / n #합과 평균 출력
사용자로부터 입력받은 정수들의 합, 평균 2 print "정수들의 합, 평균을 구해 드립니다. 입력할 정수의 개수: " n = gets.chomp.to_i a = [] # a = Array.new와 같음. 빈 Array를 만듦. n.times do i = gets.chomp.to_i a << i #Array a에 i를 추가 end sum = a.inject(:+) # + 연산자를 이용하여 a의 요소들을 집계 #ruby 2.4 이상에서는 a.sum도 가능 print sum, "\t", sum.to_f / a.length
소수(prime number) 판정 사용자로부터 상한선을 입력받아, 2부터 상한선 사이의 소수 출력 (2..ARGV[0].to_i).each do |i| #2부터 사용자가 제공한 상한선까지 is_prime = true #is_prime 변수를 일단 참으로 초기화 (2..i/2).each do |x| #2부터 i/2(정수)까지, 각 x에 대해 if i % x == 0 #i를 x로 나눈 나머지가 0이면 is_prime = false # is_prime 변수를 거짓으로 하고 break # 가장 하위의 loop를 종료 end puts i if is_prime #is_prime이 참이면 i를 출력
소수 판정 함수 어떤 정수가 소수인지 판정하는 부분을 별도의 함수로 독립시 키면 프로그램의 logic이 더 명확해짐. prime_func.rb (called code) def is_prime?(i) is_prime = true (2..i/2).each do |x| if i % x == 0 is_prime = false break end is_prime # is_prime의 값 반환 prime2.rb (calling code) #!/usr/bin/env ruby require './prime_func.rb‘ #호출할 함수를 정의한 파일을 require로 불러옴. (2..ARGV[0].to_i).each do |i| puts i if is_prime?(i) #is_prime? 함수를 사용 end