Ruby 프로그래밍한글로마자화 한국어정보의전산처리 2017. 5. 17.
jaso_call_me.rb 의함수들을메소드로바꾸기 한글인지아닌지판정, 초성, 중성, 종성알아내기는문자열에대해서만수행하는일이므로, 이들함수를 free function 보다는 String class 내의메소드로정의하는것이합리적임. class String def hangeul? (0xac00..0xd7a3).include? self.ord def onset # 반환값은 0~18, 0: ㄱ. 1: ㄲ,..., 18: ㅎ (self.ord - 0xac00) / 588 def nucleus # 반환값은 0~20, 0: ㅏ, 1: ㅐ,..., 20: ㅣ ( (self.ord - 0xac00) % 588 ) / 28 def coda # 반환값은 0~27, 0: 받침없음, 1: ㄱ, 2: ㄲ,... 27: ㅎ (self.ord - 0xac00) % 28
로마자화프로그램의구조 입력 : 한글문자열 ( 한글아닌문자가중간에들어있어도무방 ) 출력 : 한글을로마자로변환하여출력. 기타문자는그대로출력. String 클래스에 romanize 메소드추가 Syllable 클래스추가 : 초성, 중성, 종성 3 개의멤버변수를포함 음운규칙의적용방식 방법 1: 한글 -> 로마자화 -> 음운규칙 방법 2: 한글 -> 음운규칙 -> 로마자화 : 방법 2 를택함. 음운규칙적용을위해 String 을우선 Syllable 들의 Array 로변환 phon 함수 : Syllable 들의 Array 를입력으로받아, 음운규칙적용한뒤, Syllable 들의 Array 를반환 phon 함수내부에서각종음운규칙함수를호출하여사용 romanize 메소드에서 phon 함수를호출하여음운규칙이적용된 Syllable 들의 Array 를받아서 각 syllable 의초성, 중성, 종성을각각로마자로변환 이렇게로마자로변환된결과를문자열로통합 : join 메소드이용
Syllable class 정의 class Syllable # 음절클래스. 초성, 중성, 종성 3 개의멤버로이루어져있음. attr_accessor :onset, :nucleus, :coda # 이 class 의 3 개의멤버를 class 외부에서도참조할수있도록개방. # 음운규칙적용시필요함. def initialize(onset=nil, nucleus=nil, coda=nil) # 이 class 에속하는 object 를만들때 (new) 멤버들의값을초기화 # 값을주지않으면 nil 로초기화 @onset = onset @nucleus = nucleus @coda = coda
String class 에 to_syls 메소드추가 def to_syls #Syllable 들의 Array 로변환. 나중에음운규칙의적용을위해 ) syls = [] # 결과를저장할 Syllable 들의 Array self.each_char do c # 입력문자열의각문자 c 에대해 c = [@@hanja[c.ord]].pack 'U' if @@hanja.has_key? c.ord #c 가한자이면이것을대응하는한글로바꿔줌 syls << (c.hangeul?? Syllable.new(c.onset, c.nucleus, c.coda) : c) #c 가한글이면한글음절의초성, 중성, 종성값을구하여이셋으로 #Syllable instance 를만들어 syls 에추가 #c 가한글이아니면그냥 c 자체를 syls 에추가 #syls: [ 제 1 음절 [ 초, 중, 종 ], 제 2 음절 [ 초, 중, 종 ],..., 끝음절 [ 초, 중, 종 ]] syls #syls Array 반환
String class 에 romanize 메소드추가 def romanize #romanize 메소드추가 #onsets=[' ㄱ ',' ㄲ ',' ㄴ ',' ㄷ ',' ㄸ ',' ㄹ ',' ㅁ ',' ㅂ ',' ㅃ ',' ㅅ ',' ㅆ ',' ㅇ ',' ㅈ ',' ㅉ ',' ㅊ ',' ㅋ ',' ㅌ ',' ㅍ ',' ㅎ '] onset2alpha=['g','kk','n','d','tt','r','m','b','pp','s','ss','','j','jj','ch','k','t','p','h','l'] # 맨끝에ㄹ l 추가. 보통의초성ㄹ =r, ㄹ뒤의초성ㄹ =l #nuclei=[' ㅏ ',' ㅐ ',' ㅑ ',' ㅒ ',' ㅓ ',' ㅔ ',' ㅕ ',' ㅖ ',' ㅗ ',' ㅘ ',' ㅙ ',' ㅚ ',' ㅛ ',' ㅜ ',' ㅝ ',' ㅞ ',' ㅟ ',' ㅠ ',' ㅡ ',' ㅢ ',' ㅣ '] nucleus2alpha = ['a','ae','ya','yae','eo','e','yeo','ye','o','wa','wae','oe','yo','u','wo','we','wi','yu','eu','ui','i'] #codas = [' 채움 ',' ㄱ ',' ㄲ ',' ㄳ ',' ㄴ ',' ㄵ ',' ㄶ ',' ㄷ ',' ㄹ ',' ㄺ ',' ㄻ ',' ㄼ ',' ㄽ ',' ㄾ ',' ㄿ ',' ㅀ ',' ㅁ ',' ㅂ ',' ㅄ ',' ㅅ ',' ㅆ ',' ㅇ ',' ㅈ ',' ㅊ ',' ㅋ ',' ㅌ ',' ㅍ ',' ㅎ '] coda2alpha = # ㄱ, ㄷ, ㅂ초성은 g, d, b 인데비해종성은 k, t, p ['','k','k','k','n','n','n','t','l','k','m','p','l','l','p','l','m','p','p','t','t','ng','t','t','k','t','p','t'] self.to_syls.map{ syl syl.class()==syllable? onset2alpha[syl.onset] + nucleus2alpha[syl.nucleus] + coda2alpha[syl.coda] : syl }.join("") #Syllable 인것은위의세 Array 에서대응하는로마자로바꿈 #Syllable 이아닌것은원래것그대로
음운규칙적용 국어의로마자표기법에서경음화는반영하지않음. ㅎ축약은명사에서는반영하지않고, 용언에서만반영. 한국어의대개의음운규칙은앞음절의종성과뒤음절의초성사이에서일어남. 다음의순서로반영함. 재음절화 ( 연음법칙 ): 밥이 바비, 숲을 수플 초성ㄹ관련규칙 : 역행유음화, 초성ㄹ제약 순행유음화 비음화 : { ㅂ, ㄷ, ㄱ } { ㅁ, ㄴ, ㅇ } / 비음 ㅎ축약 : { ㅂ, ㄷ, ㅈ, ㄱ } ~ ㅎ { ㅍ, ㅌ, ㅊ, ㅋ } 구개음화일부반영 : 닫히다 / 다치다 /, 묻히다 / 무치다 / 음절말중화 : romanize 메소드에서반영 String class 내의함수로 phon 을만들고, 여러음운규칙에해당하는함수를 phon 이호출하도록함. 이들함수는클래스내부에서만사용. romanize 메소드에서 phon 함수호출
def resyllabification(coda) # 재음절화 coda2resyl = [ # 재음절화가일어날때, 앞음절종성이무엇인가에따라앞음절종성및뒤음절초성이무엇으로바뀔지를정해놓은 Array [0,11], #0 채움 : 채움, ㅇ [0,0], #1 ㄱ : 채움, ㄱ [0,1], #2 ㄲ : 채움, ㄲ [1,9], #3 ㄳ : ㄱ, ㅅ [0,2], #4 ㄴ : 채움, ㄴ [4,12], #5 ㄵ : ㄴ, ㅈ [0,2], #6 ㄶ : 채움, ㄴ않아 -> 아나 # 실제발음이 ' 안하 ' 가아님 ) [0,3], #7 ㄷ : 채움, ㄷ [0,5], #8 ㄹ : 채움, ㄹ [8,0], #9 ㄺ : ㄹ, ㄱ [8,6], #10 ㄻ : ㄹ, ㅁ [8,7], #11 ㄼ, ㄹ, ㅂ [8,9], #12 ㄽ : ㄹ, ㅅ [8,16], #13 ㄾ : ㄹ, ㅌ [8,17], #14 ㄿ : ㄹ, ㅍ [0,5], #15 ㅀ : 채움, ㄹ앓아 아라 # 실제발음이 ' 알하 ' 가아님 [0,6], #16 ㅁ : 채움, ㅁ [0,7], #17 ㅂ : 채움, ㅂ [17,9], #18 ㅄ : ㅂ, ㅅ [0,9], #19 ㅅ : 채움, ㅅ [0,10], #20 ㅆ : 채움, ㅆ [21,11], #21 ㅇ : ㅇ, ㅇ [0,12], #22 ㅈ : 채움, ㅈ [0,14], #23 ㅊ : 채움, ㅊ [0,15], #24 ㅋ : 채움, ㅋ [0,16], #25 ㅌ : 채움, ㅌ [0,17], #26 ㅍ : 채움, ㅍ [0,11], #27 ㅎ : 채움, ㅇ좋아 조아 # 실제발음이 ' 조하 ' 가아님 ] [coda2resyl[coda][0], coda2resyl[coda][1]] # 앞음절종성, 뒤음절초성
비음화메소드 def nasalization(coda) # 앞음절종성과뒤음절초성ㄴ, ㅁ이만났을때일어나는비음화를다룸 # 앞음절종성의변화만다룸. case coda # 앞음절종성이 when 1 ; 21 # ㄱ이면ㅇ으로바꿈. 예 : 독립 독닙 동닙 when 7,27 ; 4 # ㄷ, ㅎ이면ㄴ으로바꿈. # 예 : 묻니 문니, 좋니 졷니 존니 when 17 ; 16 # ㅂ이면ㅁ으로바꿈. 예 : 법라 법나 범나 else ; coda # 기타의경우 coda를그대로둠
ㅎ축약 : 용언어간말자음이ㅎ인경우 def aspiration_verb(onset) # 앞음절종성ㅎ과뒤음절초성의축약을다룸 # 뒤음절초성의변화만다룸. case onset # 뒤음절초성이 when 0 ; 15 # ㄱ이면ㅋ으로바꿈 # 예 : 않고 않코, 앓고 알코, 좋고 좋코 when 3 ; 16 # ㄷ이면ㅌ으로바꿈. 예 : 좋더라 좋터라 when 7 ; 17 # ㅂ이면ㅍ으로바꿈. when 12 ; 14 # ㅈ이면ㅊ으로바꿈. 예 : 좋지 조치 else ; onset # 기타의경우 onset 을그대로둠
ㅎ축약 : 접미사 '- 히 -' 의경우 def aspiration_hi(coda, onset) # 앞음절종성, 뒤음절초성둘다고려해야함 # 앞음절종성과뒤음절초성ㅎ이만났을때의축약을다룸 # 뒤음절초성의변화만다룸. case coda when 1 ; 15 # 앞음절종성이ㄱ이면뒤음절초성을ㅋ으로바꿈 # 예 : 먹히고 머키고 when 7,22 ; 14 # 앞음절종성이ㄷ, ㅈ이면뒤음절초성을ㅊ으로바꿈 # 예 : 묻혀 무텨 무쳐 ( 구개음화 ), 젖힐 저칠 when 17 ; 17 # 앞음절종성이ㅂ이면뒤음절초성을ㅍ으로바꿈 # 예 : 잡혀 자펴 else ; onset # 기타의경우 onset 을그대로둠
def phon(syls) #Syllable 들의 Array 를 input 으로받아서, 음운규칙이적용된결과를 Syllable 들의 Array 로반환 syls.each_cons(2) do ap,dui # 각각의인접한두음절에대해. ap== 앞음절, dui== 뒤음절 next if ap.class()!= Syllable or dui.class()!= Syllable # 둘중하나라도한글음절이아니면그냥통과 case dui.onset # 뒤음절초성이 when 11 # ㅇ이면 ap.coda, dui.onset = resyllabification(ap.coda) # 재음절화 ( 연음법칙 ) when 5 # ㄹ이면초성ㄹ관련음운규칙을적용 if ap.coda == 4 # 앞음절종성이ㄴ이면 ap.coda = 8 # ㄹ로바꿈 ( 역행유음화 ) 예 : 신라 -> 실라 elsif ap.coda!= 0 and ap.coda!= 8 # 앞음절종성이채움, ㄹ이아니면 dui.onset = 2 # 뒤음절초성을ㄴ으로바꿈 ( 초성ㄹ의분포제약 ) 예 : 종로 -> 종노, 독립 -> 독닙 ap.coda = nasalization(ap.coda) dui.onset = 19 if ap.coda == 8 # 종성ㄹ뒤의초성ㄹ은 'l' 로바꿈. when 2 # ㄴ이면 if ap.coda == 8 # 앞음절종성이ㄹ이면순행유음화적용 dui.onset = 19 # 뒤음절초성을ㄹ [l] 로바꿈. 예 : 별내 -> 별래 else ap.coda = nasalization(ap.coda) # 비음화 when 6 # ㅁ이면비음화적용 ap.coda = nasalization(ap.coda)
when 18 # ㅎ이면 if ((dui.nucleus==6 and (dui.coda==0 or dui.coda==20) ) or (dui.nucleus==20 and [0,4,8,16].include? dui.coda)) and [1,7,17,22].include? ap.coda #(( 중성ㅕ and ( 종성무, ㅆ ) ) or ( 중성ㅣ and ( 종성무, ㄴ, ㄹ, ㅁ ) )) and 앞음절종성이ㄱ, ㄷ, ㅂ, ㅈ # 즉, 종성ㄱ, ㄷ, ㅂ, ㅈ뒤에 ' 혀, 혔, 히, 힌, 힐, 힘 ' 이오는경우 dui.onset = aspiration_hi(ap.coda, dui.onset) ap.coda = 0 # 앞음절종성을없앰. 먹힌 -> 먹킨 -> 머킨 # 평장애음과ㅎ의축약 : 피사동접사 '- 히 -' 의경우에만반영함 # 기타경우는반영안함. 예 : 각하 gakha, 법학 beophak else # 뒤음절의초성이ㅇ, ㄹ, ㄴ, ㅁ, ㅎ이아닌경우. 여기서는앞음절종성ㅎ과뒤음절초성의축약만다룸. case ap.coda # 앞음절종성이 when 6, 15 # ㄶ, ㅀ이면 ( 예 : 않, 앓 ) ㅎ과평장애음의축약 dui.onset = aspiration_verb(dui.onset) # 앞음절종성은그대로놔둬도됨. ㄶ은 n 으로, ㅀ은 l 로변환될것이기때문 when 27 # ㅎ이면 ( 예 : 좋 ) ㅎ과평장애음의축약 dui.onset = aspiration_verb(dui.onset) ap.coda = 0 # 앞음절종성을없앰. 좋고 -> 좋코 -> 조코 #case dui.onset 의끝 #syls.each_cons(2) do ap,dui 의끝 syls # 음운규칙적용결과를 Syllable 들의 Array 로반환 #phon 함수의끝
romanize 메소드수정 : 음운규칙반영 romanize 메소드의마지막부분에서 phon 함수호출추가 def romanize phon(self.to_syls).map{ syl syl.class()==syllable? onset2alpha[syl.onset] + nucleus2alpha[syl.nucleus] + coda2alpha[syl.coda] : syl }.join("")
자습문제 : 예일식로마자표기법 한국정부에서고시한로마자표기법과별도로 한국어 / 한글의로마자표기법에는 매퀸 - 라이샤워式표기법과 예일 (Yale) 式표기법이있음. https://en.wikipedia.org/wiki/yale_romanization_of_korean 언어학계에서는예일식표기법이주로쓰임. 한글표기를예일식로마자표기법으로변환하는 ruby 스크립트를작성해보시오.
한자의중국음과한국음비교통계 입력 : 약 4000 자의한자에대해한국음과중국음을대비한자료 음절별, 초성별, 중성별, 초 중성별, 중 종설별 출력 : 한국음을기준으로이에대응하는중국음의빈도, 예시글자들
dic = Hash.new # 결과를저장할 Hash 를만듦. 한국음 =>( 중국음 => 한자 ) File.open(ARGV[0],"r:UTF-8").each do line # 입력파일의각라인에대해 comb, kor, chi = line.chomp.split("\t") # 탭으로분리 한자, 한국음, 중국음 dic[kor] = Hash.new #dic[kor] 가 nil 이면빈 Hash 를만듦 dic[kor][chi] = Array.new #dic[kor][chi] 가 nil 이면빈 Array 를만듦 dic[kor][chi] << comb #dic[kor[chi] 에한자추가 File.open(ARGV[1], "w:utf-8") do of # 출력파일을엶 dic.sort.each do k, c_combs #dic 을소트 ( 한국음가나다순 ). k= 한국음 of << k << "\t" # 한국음출력. c_combs == Hash ( 중국음 => 한자목록 ) c_combs.sort{ x,y y[1].length<=>x[1].length }.each do c, combs # 한자목록의길이 ( 한자수 ) 로소트. c= 중국음, combs= 한자목록 of << c << "\t" << combs.length << "\t" << combs.join("/") << "\n" # 중국음, 탭, 한자수, 탭, 한자 / 한자 / 한자 /, 줄바꿈