きょうこログ

初心忘れず、最後まで全力で頑張ります。よろしくお願いします。

言語処理100本ノック【第1章 後編】

課題研究で言語処理100本ノック 2015に取り組んでいます。前回の続きです。使用言語はPython3です。今回は第1章後半の5つを説明します。

作ったプログラムはGitHubに公開しています。指摘等あれば是非よろしくお願いします!

05. n-gram

与えられたシーケンス(文字列やリストなど)からn-gramを作る関数を作成せよ.この関数を用い,"I am an NLPer"という文から単語bi-gram,文字bi-gramを得よ.

ソースコード

a = "I am an NLPer"

word = a.split()
char = list(a)

def ngram(target, n):
  result = []
  if len(target) >= n:
    for i in range(len(target) - n + 1):
      result.append("".join(target[i:i + n]))
  return result  

print(ngram(word, 2))
print(ngram(char, 2))

n-gramというのは、ある文字列に対してn文字(もしくはn単語)ごとに切り出す操作のことです。n = 1 の時 uni-gram、n = 2 の時 bi-gram、n = 3 の時 tri-gramという名前が付いています。例えば "私は学生です。" という文字列に対して 文字bi-gramを考えると以下のように文字列が切り出されます。

私は / は学 / 学生 / 生で / です / す。

これは文字列中の単語に関しても、同じように考えることができます。

このプログラムでは与えられた文字列に対して、文字や単語を要素とするリストを作成した後、自作のngram()を用いてbi-gramの操作を行いました。ngram()は引数として与えられたリストに対して必要なn要素をスライスで切り出します。切り出された要素はjoin()を用いて文字列に戻し、結果を保有するリストに順次追加しました。

ngram()を用いたbi-gramは、文字と単語について別々に行います。

実行結果

['Iam', 'aman', 'anNLPer']
['I ', ' a', 'am', 'm ', ' a', 'an', 'n ', ' N', 'NL', 'LP', 'Pe', 'er']

06. 集合

"paraparaparadise"と"paragraph"に含まれる文字bi-gramの集合を,それぞれ, XとYとして求め,XとYの和集合,積集合,差集合を求めよ.さらに,'se'というbi-gramがXおよびYに含まれるかどうかを調べよ.

ソースコード

a = "paraparaparadise"
b = "paragraph"

charA = list(a)
charB = list(b)

def ngram(target, n):
  result = []
  if len(target) >= n:
    for i in range(len(target) - n + 1):
      result.append("".join(target[i:i + n]))
  return result  

setA = set(ngram(charA, 2))
setB = set(ngram(charB, 2))

print(setA.union(setB))
print(setA.intersection(setB))
print(setA.difference(setB))
print(setB.difference(setA))

print("se" in setA)
print("se" in setB)

このプログラムでは文字列の文字bi-gramを求めたあと、セットを用いた集合演算を行いました。セットとは集合を表すデータ型です。リストのように複数の要素を持つことができますが、集合を表すので、要素に順番は無く重複する値を持ちません。set()を用いることでリストをセットに変換することが出来ます。

集合演算に用いたメソッドは以下の表の通りです。

メソッド名 構文 構文の意味
union() A.union(B) 集合Aと集合Bの和集合
intersection() A.union(B) 集合Aと集合Bの積集合
difference() A.difference(B) 集合Aから集合Bを引いた差集合

集合にある要素が含まれるかどうかを調べる為には、in演算子を用いました。in演算子は右側に示すデータの集まりに、左側に示す要素が含まれているとき True を返します。

実行結果

{'ar', 'ag', 'ad', 'di', 'is', 'ph', 'ra', 'se', 'gr', 'pa', 'ap'}
{'ra', 'pa', 'ap', 'ar'}
{'ad', 'se', 'di', 'is'}
{'gr', 'ph', 'ag'}
True
False

07. テンプレートによる文字生成

引数x, y, zを受け取り「x時のyはz」という文字列を返す関数を実装せよ.さらに,x=12, y="気温", z=22.4として,実行結果を確認せよ.

ソースコード

def template(time, factor, value):
  print("{0}時の{1}は{2}".format(time, factor, value))
  
template(12, "気温", 22.4)

format()は文字列の内部の変数に対して後から値を埋め込むことができる関数です。Pythonでは+演算子を使って直接文字列や変数を連結させることもできますが、format()は複数の変数を文字列の後ろでまとめられるのが特徴です。(C言語printf()っぽい使い方ができます)format()の引数は左から順番に文字列内の{0}、{1}、{2}…に対応します。

実行結果

12時の気温は22.4

08. 暗号文

与えられた文字列の各文字を,以下の仕様で変換する関数cipherを実装せよ.

  • 英小文字ならば(219 - 文字コード)の文字に置換
  • その他の文字はそのまま出力

この関数を用い,英語のメッセージを暗号化・復号化せよ.

ソースコード

a = "Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics."

def cipher(text):
  encr = ""
  for char in text:
    if char.islower():
      encr = "".join([encr, chr(219 - ord(char))])
    else:
      encr = "".join([encr, char])
  return encr

print(a)
print(cipher(a))
print(cipher(cipher(a)))

コンピュータ上に表される全ての文字は、Unicodeと呼ばれる文字の規格上で整数に変換することができ、この文字を変換した後の整数を文字コードといいます。

このプログラム内で用いられている ord()は文字を文字コードに変換する関数で、chr()文字コードを文字に変換する関数です。また、ある文字に対して小文字であるかどうかの判別をする際にはislower()を用いました。islower()は左側に接続された文字が小文字であれば True を返します。

実行結果

Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics.
Nld I mvvw z wirmp, zoxlslorx lu xlfihv, zugvi gsv svzeb ovxgfivh rmeloermt jfzmgfn nvxszmrxh.
Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics.

少しややこしいですが、この問題で求められているのは、暗号化対象の文字を文字コードに変換した後、適当な整数を足し引きすることで架空の文字コードを生成し、これに対応する文字を暗号化された文字とすることです。暗号化された文字に対してこの逆向きの処理を行うと復号化することができます。

f:id:f_respectful12:20180221170251p:plain

09. Typoglycemia

スペースで区切られた単語列に対して,各単語の先頭と末尾の文字は残し,それ以外の文字の順序をランダムに並び替えるプログラムを作成せよ.ただし,長さが4以下の単語は並び替えないこととする.適当な英語の文を与え,その実行結果を確認せよ.

ソースコード

import random

a = "I couldn't believe that I could actually understand what I was reading : the phenomenal power of the human mind ."

a = a.split(" ")
chimera = []

for word in a:
  if 4 < len(word):
    chars = list(word[1:-1])
    random.shuffle(chars)
    chimera.append(word[0] + "".join(chars) + word[-1])
  else:
    chimera.append(word)

print(" ".join(chimera))

文字の順序をランダムに並び替える為に、randomモジュールのrandom.shuffle()を使いました。 random.shuffle()は与えられたリストの中身をランダムに並び替えることができます。

このプログラムでは先頭と末尾以外の文字は、要素の2番目から要素の後ろから2番目までをスライスを使うことで抜き出しました。要素の後ろから2番目を表すスライスは負の数を使って [:-1] で表現することができます。先頭と末尾以外の文字をランダムに並び替えたら、+演算子を使って先頭と末尾を加えました。

実行結果

I cul'dnot bveeile that I cluod actually undntaesrd what I was rinedag : the panohmeenl pewor of the hmuan mind .

ここまでが第1章で自然言語処理100本ノックの準備運動です。 私は 08. 暗号文 で詰まったのでちょっとだけ説明多めにしてます。 次も書くぞ!