How to make datas our friends

「エンジニアは発信していくことが責務である」という言葉に感化されて始めた勉強したことを書き留めていく備忘録的なやつ。

Pythonでテンプレートによる文生成をおこなう関数を作成する

背景

言語処理100本ノック 2015を今やっているのでその備忘録的なやつ。

やりたいこと

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

結果

Python3.6から新しく導入されたf-string関数を使用して作成してみました。

def getTemplate(x, y, z) :
    result = f'{x}時の{y}は{z}'
    return result
    
str = getTemplate(12, '気温', 22.4)

# 実行結果
> str
> '12時の気温は22.4'

解説/考察

今まで文字列への変数組み込みは%演算子でおこなっていたのですが、今回はf-string関数なるものが登場したと教えてもらったのでそれを利用しました。
初めて使いましたが、これは楽ちんでした!そしてスマート!

www.python.org

こんな感じで、文章内に直接変数名を入れることができるので可読性も申し分なし。

> age = 28
> f'I am {age} yo'
> 'I am 28 yo'

{}内で演算も可能です。

> age = 28
> f'I was {age-8} yo 8 years ago'
> 'I was 20 yo 8 years ago'

> import datetime
> birthday = datetime.date(1989, 2, 10)
> f'I was born in {birthday:%Y}'
> 'I was born in 1989'

Pythonには他にも文字列フォーマットに%演算子やstr.format()関数等があるのでそちらバージョンも一応作成しました。

# %演算子
def getTemplate(x, y, z) :
    result = '%s時の%sは%s' % (x, y, z)
    return result
str = getTemplate(12, '気温', 22.4)

#実行結果
> '12時の気温は22.4'
# str.format()
def getTemplate(x, y, z) :
    result = '{x}時の{y}は{z}'.format(x=x, y=y, z=z)
    return result
str = getTemplate(12, '気温', 22.4)

#実行結果
> '12時の気温は22.4'

やっぱり、f-staringが一番スマート

Pythonで2つのbi-gramの集合を求める

背景

言語処理100本ノック 2015を今やっているのでその備忘録的なやつ。

やりたいこと

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

結果

set型を使用して意外と簡単にできました。

> x = set(ngram('paraparaparadise', 2, 'char'))
> y = set(ngram('paragraph', 2, 'char'))

# x y の和集合
> x.union(y)
> {'ad', 'ag', 'ap', 'ar', 'di', 'gr', 'is', 'pa', 'ph', 'ra', 'se'}

# x y の積集合
> x.intersection(y)
> {'ap', 'ar', 'pa', 'ra'}

# x の差集合
> x.difference(y)
> {'ad', 'di', 'is', 'se'}

# 'se' がxyに含まれるかの判定
> 'se' in x.union(y)
> True

解説/考察

前記事で作成したn-gramを返す関数ngramをそのまま利用して文字列「paraparaparadise」と「paragraph」のbi-gramをそれぞれ変数x/yに代入してます。

> x = set(ngram('paraparaparadise', 2, 'char'))
> x
> {'ad', 'ap', 'ar', 'di', 'is', 'pa', 'ra', 'se'}

> y = set(ngram('paragraph', 2, 'char'))
> y
> {'ag', 'ap', 'ar', 'gr', 'pa', 'ph', 'ra'}

024minion.hatenablog.jp

集合の理解が曖昧だったのでここを参照しました。図があってわかりやすし。
herb.h.kobe-u.ac.jp

また、for文を回してなんとかしようかなと思ったのですが、Pythonにはset型という便利なやーつがあるらしいのでそれで全部まかなえました。

3.7 set(集合)型 -- set, frozenset

今回はunion/intersection/deffirence関数を使用しましたが他の関数でも代用できそう。
今度、別の関数もいじってみようと思います。

Pythonでn-gramを作成する

背景

言語処理100本ノック 2015を今やっているのでその備忘録的なやつ。

やりたいこと

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

結果

なんか色々イケてない感はあるものの関数ngramに引数で文章と何文字で区切るか、文字か単語かを渡してreturnで返すようにしました。
別にエラー文とかいらなかったな、とか思っていたりw

import re

sentense = "I am an NLPer"

def ngram(sentense, scale, type):
    # 関数の第三引数typeがwordの場合
    if type == 'word':
    
        # 変数を用意
        str = ''
        wordlist = []
    
        # 文章からピリオドを除外して空白で分割
        splited = re.split('\s', sentense.replace('.', ''))
    
        # もし第二引数の値が文章内の単語数より少なければエラーを出す
        if scale > len(splited):
            print("ERROR: The argument, scale must be lawer than the actual number of the words in the argument, sentense.")
        else:
            for i in range (0, len(splited)-(scale-1)):
                for ii in range (0, scale):
                    if ii == 0:
                        str += splited[ii+i]
                    else:
                        str += ' ' + splited[ii+i]
                wordlist.insert(i, str)
                # strを初期化
                str = ''
        
            return wordlist

    # 関数の第三引数typeがcharの場合
    elif type == 'char':
        charlist = []
        # 文字nグラムを作成
        trimed = (sentense.replace(' ', '')).replace('.', '')
        
        if scale > len(trimed):
            print("ERROR: The argument, scale must be lawer than the actual number of the characters in the argument, sentense.")
        else:
            for iii in range (0, len(trimed)-(scale-1)):
                charlist.insert(iii, trimed[iii:iii+scale])
    
            return charlist

    # 関数の第三引数typeがword/charでない場合エラーを出す
    else:
        print("ERROR: The 3rd argument, type must be char or word.")

data1 = ngram(sentense, 2, "word")
data2 = ngram(sentense, 2, "char")

実行結果

> data1
> ['I am', 'am an', 'an NLPer']
> data2
> ['Ia', 'am', 'ma', 'an', 'nN', 'NL', 'LP', 'Pe', 'er']

解説/考察

そもそもnグラムってなによってところから始まりました。無知ですみません。
で、色々ググりました。

kotobank.jp

要は、n文字/単語単位で文字列を1文字/単語ずつスライドさせていったやつの集合体という認識です。
今回はbiグラムなので2文字/2単語ですね。

ab cd ef gh を単語bi-gramにすると、ab cd/cd ef/ef gh に、文字bi-gramにすると ab/bc/cd/de/ef/fg/gh になる。
え、この認識で合ってますよね?w

で、今回は関数を作って単語bi-gramと文字bi-gramを作れってお題だったのでこんな感じの関数を作ってみました。

def ngram(sentense, scale, type):

第一引数に文章を、第二引数に何文字で区切るか(今回だとbi-gramなので2を渡す)、で最後に文字/単語どっちのn-gramがほしいか指定して関数を実行すると戻り値で帰ってくる的なやつです。

単語bi-gramを作る
splited = re.split('\s', sentense.replace('.', ''))
    
for i in range (0, len(splited)-(scale-1)):

 for ii in range (0, scale):
  if ii == 0:
   str += splited[ii+i]
  else:
   str += ' ' + splited[ii+i]
 
 wordlist.insert(i, str)
 str = ''

まず例のごとく文章を空白単位で分割して変数splitedにぶっこんでます。
そのあとfor文を回してリスト型の変数wordlistにデータを流し込んでいます。

for文を回す回数は「単語の数 - (何個単位かの値 - 1)」で計算させてみました。
例えば、bi-gramで3単語の場合 = 3 - (2 -1) で 2 になります。
文章が「AA BB CC」なら「AA BB / BB CC」にしたいので上の計算法を使えばぴったりリスト内に収まる計算です。

for i in range (0, 単語の数 - (何個単位かの値 - 1)):

続いてリスト内に格納する文字列の作成を行っています。

for ii in range (0, scale):
 if ii == 0:
  str += splited[ii+i]
 else:
  str += ' ' + splited[ii+i]

この部分です!単語単位で分割した文字列を、for文を回した回数に応じて横スライドさせ結合させてみました。
[ii+i]なので「ここでのfor文の回数+1つ上のfor文の回数」番目の単語をリストから取り出しています。

で最後にリストに生成した文字列を入れて文字列変数を初期化しています。

文字bi-gramを作る
trimed = (sentense.replace(' ', '')).replace('.', '')
        
for iii in range (0, len(trimed)-(scale-1)):
 charlist.insert(iii, trimed[iii:iii+scale])

こっちはだいぶシンプルです。

まず文章の空白を詰める作業をしています。
最初は以下の関数でやっていたのですがうまくいかず、結局replace関数で置換しています。
途中で気がついたのですが、この関数文章中の空白はトリムできないんですねw

sentense. strip()

あとは単語bi-gramと同じ原理でfor文を回してリストにデータを入れています。
文字列の結合は str[i:j:k] を使用してやってみました。

charlist.insert(iii, trimed[iii:iii+scale])

リストのiii番目に空白詰めした文字列のiii番目から「iii+何番目の文字まで取るかの値」番目までの文字列を切り出して格納しています。

最後に

今回のお題は今までで一番時間かかりました。

きっと僕がもっとPythonをマスターしたら、この記事のコードを書き換えたいと思うんだろうな〜とか思いつつGWが終わってしまったので明日からお仕事がんばります。笑

Pythonで文章を単語ごとに分解してn番目の単語は最初の1文字をそれ以外は2文字を切り出して連層配列を作成する

背景

言語処理100本ノック 2015を今やっているのでその備忘録的なやつ。

やりたいこと

"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文字を取り出し,取り出した文字列から単語の位置(先頭から何番目の単語か)への連想配列(辞書型もしくはマップ型)を作成する。

結果

ifで条件分岐をおこないfor文で辞書型のdictに値を流し込む方法で対応しました。

import re
sentense = 'Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can.'
words = re.split('\s', sentense.replace('.', ''))

for i in range (0, len(words)):
    str = words[i]
    if i+1 in (1, 5, 6, 7, 8, 9, 15, 16, 19):
        dict[i + 1] = str[0]
    else:
        dict[i + 1] = str[:2]        

dict
> {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'}

解説/考察

基本的な考え方は前記事で書いた内容そのままです。
024minion.hatenablog.jp

何番目の単語かによって切り取る文字数が違うのでシンプルにif文で対象となる数字を直接手打ちしています。
前回はリスト型を使用していますが、指定が連層配列型だったので今回はpythonの辞書型を使用しています。

if i+1 in (1, 5, 6, 7, 8, 9, 15, 16, 19):
 dict[i + 1] = str[0]
else:
 dict[i + 1] = str[:2]

for文のiが0から始まるのでi+1にして実際の数字と合わせるようにしています。

辞書型についてはここを参照しました。

www.pythonweb.jp

Pythonで文章を単語に分解し各単語の文字数を先頭から出現順に並べたリストを作成する

背景

言語処理100本ノック 2015を今やっているのでその備忘録的なやつ。

やりたいこと

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

結果

文章を単語ごとに分割して分割した単語の文字数をfor文でリスト内にinsertして対応しました。

import re
sentense = 'Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics.'
splited = re.split('\s', sentense.replace('.', ''))
cntlist = []
for i in range (0, len(splited)):
    cntlist.insert(i, len(splited[i]))
splited
> [3, 1, 4, 1, 6, 9, 2, 7, 5, 3, 5, 8, 9, 7, 9]

解説/考察

Pythonの標準ライブラリのreを使用しました。
6.2. re — 正規表現操作 — Python 3.6.1 ドキュメント

reを使用して単語を分割するとコンマが残ってしまったのでreplaceを使用してピリオドを文章内から抹消しています。他にも方法あるんですかね?

> sentense = 'Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics.'
> sentense.replace('.', '')
> 'Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics'

reのsplit関数を使用して文章を分割しました。
今回は任意の空白文字とマッチする部分で分けたいのでpatternに\sを指定してます。
\W+でも問題ないっぽいです。

正規表現はここにまとまっています。
正規表現 HOWTO — Python 3.6.1 ドキュメント

> import re
> sentense = 'Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics.'
> re.split('\s', sentense.replace('.', ''))
> ['Now', 'I', 'need', 'a', 'drink,', 'alcoholic', 'of', 'course,', 'after', 'the', 'heavy', 'lectures', 'involving', 'quantum', 'mechanics']

文章を単語ごとに分解できたので、分割した単語数分for文でリストにデータを流し込む作業をしています。

# 空のリストを用意
cntlist = []
# len(splited)で分割した単語数を取得して値を渡してあげる
for i in range (0, len(splited)):
    #リストのi番目に分割したi番目の単語の文字数をinsertする
    cntlist.insert(i, len(splited[i]))

以上!

Pythonで2つの文字列の文字を先頭から交互に連結する

背景

言語処理100本ノック 2015を今やっているのでその備忘録的なやつ。

やりたいこと

Pythonで文字列"パトカー"と"タクシー"の文字を先頭から交互に連結して文字列"パタトクカシーー"にする。

結果

以下の方法で2つの文字列を先頭から交互に連結しました。

> str1 = "パトカー"
> str2 = "タクシー"
> str3 = ""
> for i in range(0,4):
>      str = (str1 + str2)[i::4]
>      str3 += str
> str3
> 'パタトクカシーー'

解説/考察

一番最初に書いたやつがこれ。

> str1 = "パトカー"
> str2 = "タクシー"
> str3 = (str1 + str2)[0::4]
> str4 = (str1 + str2)[1::4]
> str5 = (str1 + str2)[2::4]
> str6 = (str1 + str2)[3::4]
> str3 + str4 + str5 + str6
> 'パタトクカシーー'

s[i:j:k] を使用して"パトカー"と"タクシー"を合体させた文字列のi番目から4文字おきに文字を取ってきて最終的に合体。
でも、なんかいけていないってことでfor文で単純作業の部分を書き直しました。(やってることは同じですが)

他にも方法がありそう。
どう書くのがイケてる方法なのだろう。。。

pythonで「パタトクカシーー」という文字列の1,3,5,7文字目を取り出して連結した文字列にする

背景

言語処理100本ノック 2015を今やっているのでその備忘録的なやつ。

やりたいこと

Pythonで文字列"パタトクカシーー"の1,3,5,7文字目を取り出して連結した文字列にする。
(つまり"パトカー"になれば成功)

結果

以下の方法で1,3,5,7文字目を切り出して文字を連結しました。

> str = 'パタトクカシーー'
> str[::2]
> 'パトカー'

解説/考察

記事にするまでもないほど簡単でした。
s[i:j:k] の k に2を指定して文字列を2文字ごとに切り出せばOK。

詳細は前記事でまとめています。
024minion.hatenablog.jp

ちなみに str[1::2] にしてあげれば"タクシー"になりました。

> str = 'パタトクカシーー'
> str[1::2]
> 'タクシー'