きょうこログ

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

言語処理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. 暗号文 で詰まったのでちょっとだけ説明多めにしてます。 次も書くぞ!

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

課題研究で言語処理100本ノック 2015に取り組んでいます。使用言語はPython3です。文章を書く練習も兼ねてちょっとずつ記録を残していこうと思います。初めは第1章前半の5つです。

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

00. 文字列の逆順

文字列"stressed"の文字を逆に(末尾から先頭に向かって)並べた文字列を得よ.

ソースコード

f = reversed(list("stressed"))
a = ''.join(f)
print(a)

list()は、シーケンス型のオブジェクト(順序のある要素の集まり)からリストを作成する関数です。文字列も左から順番に読む要素(アルファベット)の集まりだと考えられるので、list()を用いるとリストとして扱うことが出来ます。reversed()はリストを逆順にする関数です。

これをjoin()を使って文字列に戻し、出力します。

実行結果

desserts

01. 「パタトクカシー」

「パタトクカシーー」という文字列の1,3,5,7文字目を取り出して連結した文字列を得よ.

ソースコード

a = "パタトクカシー"[::2]
print(a)

今回はスライスを使いました。スライスとは、シーケンス型のオブジェクトから欲しい要素を抜き出す書き方です。オブジェクトに対して[始まりの位置 : 終わりの位置 : ステップ]を右側に書くことで使えます。始まりの位置を省略すると0、終わりの位置を省略するとオブジェクトの末尾が参照されます。このプログラムでは、始まりの位置 = 0、終わりの位置 = 末尾、ステップ = 2なので、文字列の左端から最後まで順番に1文字飛ばしで要素が抜き出されます。

抜き出した要素はそのまま文字列として合体して返ってくるので、そのまま出力します。

実行結果

パトカー

02. 「パトカー」+「タクシー」=「パタトクカシー」

「パトカー」+「タクシー」の文字を先頭から交互に連結して文字列「パタトクカシーー」を得よ.

ソースコード

a = list("パトカー")
b = list("タクシー")
c = []

for i, j in zip(a, b):
  c += i + j

c = "".join(c)

print(c)

"パトカー"と"タクシー"をlist()でリストに変換した後、2つのリストの先頭から順番に1つずつ要素を呼び出して合体させていきます。zip()は、2つ以上のリストの各要素をタプルにまとめて返す関数です。zip()を用いるとfor文の最初のループで、i = "パ"、j = "タ"が返ってきます。文字列同士は+演算子を使って連結し、+=演算子を使って別に用意したリストに要素として追加します。

これをjoin()を使って文字列に戻し、出力します。

実行結果

パタトクカシーー

03. 円周率

"Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics."という文を単語に分解し,各単語の(アルファベットの)文字数を先頭から出現順に並べたリストを作成せよ.

ソースコード

import re

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

a = re.sub(r'[\.,]+', "", a)
a = a.split()
result = []

for w in a:
  result.append(len(w))

print(result)

この英文には解答に必要のない ", " とか ". " が含まれているので、reモジュールをインポートし正規表現を使って取り除きました。正規表現とは文字列のパターンを照合する表現のことです。例えば[\.,]だと、", " か ". " のどちらかであることを表しています。re.sub()正規表現を使って文字列を置換する関数です。re.sub(正規表現, 置換後の文字列, 置換対象) と記述して使います。このプログラムでは、英文に含まれる ", " や ". " を何も含まない文字列 "" に置換しました。split()は文字列を区切る関数です。引数を省略すると文字列に対してスペース区切りしたものを リストにして返してくれます。

re.sub()split()を使って得られたリストから順番に単語を取り出し、len()を使って文字数を得ます。この文字数をあらかじめ用意した別のリストに要素として追加することで文字数を先頭から出現順に並べたリストが完成します。

実行結果

[3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9]

ちなみに得られたリストには、円周率 3.141592... の数字を順番に並べたものが格納されています。

04. 元素記号

"Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can."という文を単語に分解し,1, 5, 6, 7, 8, 9, 15, 16, 19番目の単語は先頭の1文字,それ以外の単語は先頭に2文字を取り出し,取り出した文字列から単語の位置(先頭から何番目の単語か)への連想配列(辞書型もしくはマップ型)を作成せよ.

ソースコード

import re

a = "Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can."

pull = [1, 5, 6, 7, 8, 9, 15, 16, 19]

a = re.sub(r'[\.,]+', "", a)
a = a.split()
result = {}

for i in range(len(a)):
  if i + 1 in pull:
    result[i + 1] = a[i][:1]
  else:
    result[i + 1] = a[i][:2]

print(result)

range()は連番のリストを作る関数です。range()を使って得たインデックスがリストpullに含まれるかを判別して、取り出す文字数を決定します。先頭文字の取得に始まりの位置 = 0、終わりの位置 = 1または2、ステップ = 1のスライスを使っています。

取得したインデックスと先頭文字を格納する為の連想配列には辞書を使用しました。辞書名[辞書のkey] = 辞書の値で辞書にデータを登録出来ます。

実行結果

{1: 'H', 2: 'He', 3: 'Li', 4: 'Be', 5: 'B', 6: 'C', 7: 'N', 8: 'O', 9: 'F', 10: 'Ne', 11: 'Na', 12: 'Mi', 13: 'Al', 14: 'Si', 15: 'P', 16: 'S', 17: 'Cl', 18: 'Ar', 19: 'K', 20: 'Ca'}

もちろん完全無欠のコードじゃないです。 指摘頂いた所も追記していければと思います!