Ruby 프로그래밍 3 Array와 Hash 이용 한국어 정보의 전산 처리 2017. 5. 1.
Array 기초 1: 문자열 소팅 사용자가 command-line에 입력한 문자열들을 소팅하여 출력 if ARGV.length < 1 print “입력한 문자열들을 소팅해 드립니다.” exit end ARGV.sort.each do |s| puts s
Array 기초 2: 정수 소팅 사용자가 command-line에 입력한 정수들을 소팅하여 출력 if ARGV.length < 1 print “입력한 정수들을 소팅해 드립니다.” exit end ARGV.map{ |s| s.to_i }.sort.each do |i| puts i # map 메소드: Array의 각 요소에 대해 블록 안에서 규정한 조 작에 따라 변형하여 그 결과 값으로 이루어진 새로운 Array를 만들어 return함.
Array 기초 3: 문자열 역순 소팅 역순 소팅: 문자열의 뒤에서부터 비교 if ARGV.length < 1 내림차순 소팅과 다름. if ARGV.length < 1 print "입력한 문자열들을 역순 소팅해 드립니다." exit end ARGV.map{ |s| s.reverse }.sort.each do |s| #Array 안의 요소들을 뒤집어서 소팅한 뒤 puts s.reverse #Array 안의 요소들을 다시 뒤집어(즉 본래대로 하여) 출력
String 메소드 reverse 활용: palindrome 검사 사용자가 command line에 입력한 문자열이 palindrome인지 판정 if ARGV[0] == ARGV[0].reverse print "palindrome입니다." else print "palindrome 아닙니다." end #방법 1 print ( ARGV[0] == ARGV[0].reverse ) ? “Yes” : “No” # 방법 2 ~ ? A : B ~가 참이면 A로, ~가 거짓이면 B로 evaluate되는 expression
Array 메소드 include? (합격자) 명단에 이름이 들어 있는지 검사 list = ["강감찬","공형진","김갑순","김구라", …… ,"홍길동"] if ARGV.length < 1 print "이름이 명단에 있는지 검사합니다. 이름을 입력하세요." end print ( list.include? ARGV[0] ) ? “Yes” : “No“ 어떤 값이 어떤 범위에 드는지 알아볼 때도 include? 메소드가 유용 하한선 <= 값 and 값 <= 상한선 (하한선..상한선).include? 값 전자보다 후자가 더 간결함.
Array 메소드 each Enumerable에 대해 가장 많이 사용되는 메소드는 each임. 사용자가 입력한 정수들을 각각 제곱하여 출력 ARGV.each do |s| #사용자가 command line에 입력한 각 문자열에 대해 puts s.to_i**2 #문자열 s를 정수로 변환하여, 제곱하여, 출력 end #방법 1 ARGV.map{ |s| s.to_i**2 }.each do |i| # map 메소드로 정수 변환과 제곱을 수행하고 puts i # each 메소드 블록 안에서는 출력만 수행 end #방법 2
파일 입출력: nl 흉내내기 File 클래스의 open 또는 new 메소드로 파일 object 생성 이 메소드의 첫째 인자: 파일 이름 둘째 인자: “r:UTF-8” (읽기 모드), “w:UTF-8” (쓰기 모드) File object에 대해 each 메소드 호출: 한 라인씩 읽음. n=1 File.open(ARGV[0], “r:UTF-8”).each do |line| #사용자가 제공한 이름의 파일을 열어서, 각 라인에 대해 print n, ARGV[1], line #n, 구분자, line 출력 n+=1 # n을 1 증가시킴 end
redirection redirection을 사용하여 입력 파일이 마치 표준입력으로부터 오는 것처 럼 처리할 수도 있음. while line = STDIN.gets do #표준입력으로부터 읽어들여 line에 저장 #읽어들일 문자열이 있으면, 즉 읽기가 성공하면 #위의 assignment statement는 true를 반환 #읽어들일 문자열이 있는 동안 블록이 반복 실행됨. line.chomp! #line 맨 끝의 줄바꿈 문자을 떼어냄. mutating 메소드 print n, ARGV[0], line, "\n" #n, 구분자, line 출력 n+=1 # n을 1 증가시킴 end
합격자 명단 파일 읽어 이름 검사 if ARGV.length < 2 print "이름이 명단에 있는지 검사. 명단파일, 이름을 입력" exit end names = [] File.open(ARGV[0], "r:UTF-8").each do |line| names << line.chomp #line에서 줄바꿈 문자 떼어낸 뒤, #names Array에 추가. print ( names.include? ARGV[1] ) ? "Yes" : "No"
Hash 기초 학생 이름을 입력하면 점수를 알려주기 data = {} #비어 있는 Hash를 만듦. data = Hash.new와 동일. File.open(ARGV[0], "r:UTF-8").each do |line| name, score = line.chomp.split(",") #line을 쉼표로 구분 data[name] = score #data에 name-score라는 key-value 쌍을 추가 # data[name]은 data Hash에 들어 있는 key name과 연결된 # value를 반환함. end print (data.has_key? ARGV[1] ) ? data[ ARGV[1] ] : "이름이 없습니다."
테이블 구조 문서 처리: 키, 체중의 합, 평균 내기 hsum = 0.0 # 키 합을 저장할 변수를 실수로 초기화 wsum = 0.0 # 체중 합을 저장할 변수를 실수로 초기화 count = 0 # 학생 수를 저장할 변수를 정수로 초기화 File.open(ARGV[0], "r:UTF-8").each do |line| name, height, weight = line.chomp.split(",") #쉼표로 분리 height = height.to_f #문자열을 실수로 변환 weight = weight.to_f #문자열을 실수로 변환 hsum += height # 키 합에 더함 wsum += weight #체중 합에 더함 count += 1 # 학생 수 1 증가시킴 end print "키 합: ", hsum, "\n" print "키 평균: ", hsum/count , "\n" print "체중 합: ", wsum, "\n" print "체중 평균: ", wsum/count , "\n"
테이블 구조 문서 처리: 성적 처리 국,영,수,물,화 5과목 총점, 평균 추가. 총점순 소팅 data = [] #학생들을 요소로 하는 Array File.open(ARGV[0], "r:UTF-8").each do |line| name, kor, eng, math, phy, che, job = line.chomp.split(",") sum = kor.to_i + eng.to_i + math.to_i + phy.to_i + che.to_i avg = sum.to_f / 5 student = [name, kor, eng, math, phy, che, sum, avg, job] #입력의 필드 7개와 추가된 필드 2개를 원하는 순서대로 배열하여 학생 1인을 나타내는 Array를 만듦 data << student #이 학생 object를 data Array에 추가 end data.sort{ |x,y| y[6]<=>x[6] }.each do |stu| #data의 각 요소의 7번째 필드(총점)를 기준으로 내림차순 소팅 stu.each do |x| #학생 object를 구성하는 각 필드에 대해 print x, "," #그 필드와 쉼표를 출력 print "\n" # 학생 1인에 대한 정보 출력이 끝나면 줄바꿈문자 출력
다중 기준 소팅: 단어 등급 자료 words.csv: 빈도, 단어, 품사, 의미, 등급 제1 소팅 기준: 등급, 2: 빈도, 3: 품사, 4: 단어, 5: 의미 dic = [] #단어 object들을 저장할 Array를 만듦 File.open(ARGV[0], "r:UTF-8").each do |line| freq, form, pos, meaning, grade = line.chomp.split(",") #쉼표로 구분 freq = freq.to_i #빈도는 정수로 변환 word = [grade, -freq, pos, form, meaning] #소팅 기준 순서대로 배열하여 하나의 word object를 나타내는 Array 만듦. #빈도는 내림차순 소팅을 위해 - 추가. dic << word #이 word object를 dic에 추가 end dic.sort.each do |grade, freq, pos, form, meaning| #dic을 소팅한 뒤 #각 word를 구성하는 필드를 변수로 지칭 가능 print -freq, ",", form, ",", pos, ",", meaning, ",", grade, "\n" #출력은 원 순서대로
텍스트 빈도 통계 <input tr “ “ “\n” | sort | uniq –c | sort –gr 을 ruby로 구현 data = Hash.new # data = {} 와 동일 File.open(ARGV[0], "r:UTF-8").each do |line| line.chomp.split(" ").each do |word| #공백으로 구분된 단어들 각각에 대해 if data.has_key? word # data에 word라는 key가 있으면 data[word] += 1 #그 word와 연결된 수를 1 증가시킴 else #없으면. 즉 이 단어를 처음 만났으면 data[word] = 1 #그 word와 연결된 수를 1로 설정 end data.sort{ |x,y| y[1]<=>x[1] }.each do |word, freq| #빈도 내림차순으로 소팅 print word, ",", freq, "\n"
텍스트 빈도 통계 2 data = Hash.new(0) #key의 default value를 0으로 설정 File.open(ARGV[0], "r:UTF-8").each do |line| line.chomp.split(" ").each do |word| data[word] += 1 #data를 만들 때 default value를 0으로 설정했으므로 #word를 처음 만나는 경우에도 #이와 연결된 value가 자동으로 0으로 설정됨. #word를 처음 만나든 아니든 연결된 value를 1 증가시키면 됨 end programming idiom: 문법에 맞고 의미(기능)도 동일한 여러 표현 중에서 사용자(프로그래머)들이 관습적으로 흔히 사용하는 표현 예: Hash의 default value를 설정함으로써 key 포함 검사를 생략할 수 있게 하는 것
텍스트 빈도 통계 3 공백을 구분자로 하여 split을 하면, “Alice”, “Alice,”, “Alice.”, “Alice?” 등이 모두 별개의 단어로 간주됨. 정규표현 /\b/를 구분자로 하면, 일반 문자와 문장부호 사이에 단 어 경계가 있으므로 “Alice? The”가 “Alice”, “? “, “The”로 구분됨. File.open(ARGV[0], "r:UTF-8").each do |line| line.chomp.split(/\b/).each do |word| #정규표현 /\b/로 구분된 단어들 각각에 대해 data[word] += 1 end
파일들 join하기 joined = Hash.new #join한 결과는 { key => [각 파일의 나머지 필드들을 요소로 갖는 Array] } ARGV.each do |fn| #사용자가 제공한 파일이름들에 대해 File.open(fn, "r:UTF-8").each do |line| #파일을 열어 각 라인에 대해 key, residue = line.chomp.split(",", 2) #쉼표를 구분자로 split하되, 2개로만 구분 joined[key] ||= Array.new #short-circuiting을 이용한 ||= idiom joined[key] << residue #key와 연결된 Array에 residue를 추가함. end joined.sort.each do |key, residues| #joined를 key로 소팅 #residues: 각 파일의 residue들로 이루어진 Array임 print key #key 출력 residues.each do |residue| #residues Array의 각 요소 residue에 대해 print ",", residue #쉼표와 residue 출력 print "\n" #하나의 key에 대한 value의 출력이 끝나면 줄바꿈문자 출력
short-circuited evaluation (P and Q), (P && Q)의 값을 알아내려 할 때, P가 true이면 Q의 값을 evaluate해야 하나 P가 false이면 Q의 값을 evaluate할 필요가 없음. (P or Q), (P || Q)의 값을 알아내려 할 때, P가 false이면 Q의 값을 evaluate해야 하나 P가 true이면 Q는 evaluate할 필요가 없음. 연산자에 의해 결합된 두 표현의 값을 다 evaluate하지 않고 앞 의 것만 evaluate하여 전체 표현을 evaluate하는 것을 short-circuited evaluation이라 함. boolean이 기대되는 자리에서 nil은 false로, nil 이외의 값은 true로 간주됨. C 언어에서는 0도 false로 간주됨.
||= idiom 어떤 변수의 값이 nil인지 검사하여, nil이 아니면 그냥 내버려두고 nil 이면 어떤 값을 할당하는 일이 흔히 있음. 이 logic을 곧이곧대로 표현하면 x = a if x == nil || 연산자의 short-circuited evaluation 성질을 이용하면 이것을 다음과 같이 표현할 수 있음: x ||= a 이것을 ||= idiom이라 함. x||=a는 x = (x||a)와 같은 의미임. cf. x+=1은 x=x+1과 같은 의미 x가 non-nil이면 a는 evaluate 안 함. x=x이므로 아무 일도 안 하는 셈 x가 nil이면 a가 evaluate되고 x=a가 됨. joined[key] ||= Array.new joined Hash에 key가 있었으면(joined[key]가 nil이 아님) 아무 일도 안 함. key가 없었으면, 즉 joined[key]가 nil이면, 빈 Array를 만들어 이 key의 value로 삼음.
Array의 join 메소드 Array의 join 메소드를 이용하면 아래 코드를 더 단순화할 수 있음. residues.each do |residue| print ",", residue end join 메소드는, Array의 각 요소(문자열)들을 이어붙여 하나의 문자열로 통합하되, 각 요소 사이에 구분자를 넣음. [“ab”, ”cd”, ”ef”].join(“:”) # “ab:cd:ef” 구분자를 null string으로 할 수도 있음. [“ab”, ”cd”, ”ef”].join(“”) # “abcdef” 위의 3줄의 코드는 print “,”, residues.join(“,”)와 동일 학생 성적 처리할 때의 아래 코드도 마찬가지로 간략화 가능 stu.each do |x| print x, "," # print stu.join(“,”)