2018年02月04日

言語処理100本ノックでPython入門 #09 - リスト操作、文字列操作など

   このエントリーをはてなブックマークに追加 Clip to Evernote

本日は、言語処理100本ノック 2015の問題09です。

これが第1章の最後の問題です。

■ 問題

09. Typoglycemia

スペースで区切られた単語列に対して,各単語の先頭と末尾の文字は残し,それ以外の文字の順序をランダムに並び替えるプログラムを作成せよ.ただし,長さが4以下の単語は並び替えないこととする.適当な英語の文(例えば"I couldn't believe that I could actually understand what I was reading : the phenomenal power of the human mind .")を与え,その実行結果を確認せよ.

■ 書いたコード
import random

def Typoglycemia(word):
    length = len(word)
    if length < 5:
        return word
    s = word[0]
    e = word[-1:]
    m = list(word[1:-1])
    random.shuffle(m)
    return s + "".join(m) + e

def main():
    text = "I couldn't believe that I could actually understand what I was reading : the phenomenal power of the human mind ."
    words = text.split(' ')
    result = []
    for word in words:
        result.append(Typoglycemia(word))
    print(" ".join(result))

if __name__ == '__main__':
    main()

単語の先頭と末尾の文字は残し,それ以外の文字の順序をランダムに並び替える関数Typoglycemiaを定義しました。ただし,長さが4以下の単語は並び替えないように制御しています。

これができれば、main関数で、文を空白文字で区切り、Typoglycemia関数を呼び出して得られた単語を連結させればOK.


■ 結果
I c'oulndt bevleie that I colud alactluy utdnrnsead what I was rneaidg : the peeaohnnml poewr of the hmaun mind .

Typoglycemia とは、最初と最後の文字が合っていれば、途中の文字を並べ替えても、読めてしまう現象のことを言うらしいです。昔、ひらがなで同様のプログラムを書きました。


■ 今回のトピック(文字列操作)

文字列の最後の文字
word[-1:]

文字列の先頭と最後を除いた文字列
word[1:-1]

文字列の長さ
len(word)

文字列を空白文字で分割
line.split(' ')

複数の文字列を連結(間にカンマ)
",".join(words)


■ 今回のトピック(リスト操作)


文字列を文字リストに変換
list(word)

文字リストを文字列へ
"".join(chars)                

リストのシャッフル
import random

random.shuffle([0, 1, 2, 3, 4, 5])

リストの連結
result = []
for word in words:
    result.append(word)


■ 今回のトピック(その他)


モジュール名 __main__

以下のように書くと、そのモジュールが直接実行された場合にのみ、main()が呼び出されます。 モジュールが直接実行された場合にのみ、__name__ というシステム変数に '__main__'というモジュール名が設定されます。
if __name__ == '__main__':
    main()        


これで、「第1章: 準備運動」のすべての問題を解くことができました。

変数、条件分岐、繰り返し、文字列、辞書、集合、リスト、リスト内包表記、関数など、結構いろんなことを学べました。  

Posted by gushwell at 22:00Comments(0)Python

2018年01月31日

言語処理100本ノックでPython入門 #08 - 文字と数字の相互変換

   このエントリーをはてなブックマークに追加 Clip to Evernote


言語処理100本ノック 2015
の問題08を解きました。

■ 問題 
08. 暗号文
与えられた文字列の各文字を,以下の仕様で変換する関数cipherを実装せよ.
・英小文字ならば(219 - 文字コード)の文字に置換
・その他の文字はそのまま出力
この関数を用い,英語のメッセージを暗号化・復号化せよ.



■ 書いたコード
def cipher(s):
    r = ''
    for c in s:
        r += chr(219 - ord(c)) if c.islower() else c
    return r

def main():
    x = 'Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics.'
    r = cipher(x)
    print(r)
    print(cipher(r))

main()

■ 実行結果
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.

なるほど。
'a'のコードが10進数で97, 'z'のコードが10進数で122なので、219(=97+122)から引けば、a-zの英小文字が逆転して、z-aの英小文字に変換されるんですね。
で、同じ操作をすれば、元に戻るということですね。
まあ、すぐに解読できちゃうのでまったく実用的ではないですが...


■ 今回学んだこと

文字を数字に変換
ord(c)

数字を文字に変換
chr(n)
文字列の連結

C#と同じですね。
s += additional

条件式

他の言語の三項演算子の代わりですね。条件式と言うらしいです。
x if c else y
上のコードで条件Cが成り立てばx、成り立たなければyがその式の値になります。
   
Posted by gushwell at 22:00Comments(0)Python

2018年01月28日

言語処理100本ノックでPython入門 #07 - 文字列のフォーマット

   このエントリーをはてなブックマークに追加 Clip to Evernote


言語処理100本ノック 2015
の問題07を解きました。

■ 問題
07. テンプレートによる文生成

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


■ 書いたコード


3つの方法で、実装してみました。
from string import Template

def formatText(x, y, z):
    return "{hour}時の{target}は{value}".format(hour=x, target=y, value=z)

def formatText2(x, y, z):
    s = Template('$hour時の$targetは$value')
    return s.substitute(hour=x, target=y, value=z)

def formatText3(x, y, z):
    return '%s時の%sは%s' % (x, y, z)

def main():
    x = 12
    y = "気温"
    z = 22.4
    print(formatText(x, y, z))
    print(formatText2(x, y, z))
    print(formatText3(x, y, z))

main()

■ 実行結果
12時の気温は22.4
12時の気温は22.4
12時の気温は22.4
3つとも同じ結果になりました。

■ 今回学んだこと


文字列のフォーマット その1
def formatText(x, y, z):
    return "{hour}時の{target}は{value}".format(hour=x, target=y, value=z)
C#の挿入文字列に似ています。でも、以下のようには書けません。
    return "{x}時の{y}は{z}".format(x, y, z)

文字列のフォーマット その2

Templateクラスを使うと、以下のように書けます。
from string import Template
...

def formatText2(x, y, z):
    s = Template('$hour時の$targetは$value')
    return s.substitute(hour=x, target=y, value=z)

文字列のフォーマット その3

以下のような書き方もあるようです。すこしC言語っぽいです。
def formatText3(x, y, z):
    return '%s時の%sは%s' % (x, y, z)



変数のスコープに関して
def formatText(x, y, z):
    return "{hour}時の{target}は{value}".format(hour=x, target=y, value=z)

x = 12
y = "気温"
z = 22.4
print(formatText(x, y, z))

と書いたら、関数の定義している行で、
W0621:Redefining name 'x' from outer scope (line xx)

のような警告が出ました。

グローバルの変数と関数の仮引数の名前が同じだと、関数の中で、グローバル変数が参照できなくなるよ、という警告っぽいです。
確かにその通り。

なので、以下のようにmain関数を定義することで、回避しました。
def main():
    x = 12
    y = "気温"
    z = 22.4
    print(formatText(x, y, z))

main()
  
Posted by gushwell at 21:30Comments(0)Python

2018年01月23日

言語処理100本ノックでPython入門 #06 - 集合演算

   このエントリーをはてなブックマークに追加 Clip to Evernote

今日は言語処理100本ノックの問題06です。

■ 問題
06. 集合
"paraparaparadise"と"paragraph"に含まれる文字bi-gramの集合を,それぞれ, XとYとして求め,XとYの和集合,積集合,差集合を求めよ.さらに,'se'というbi-gramがXおよびYに含まれるかどうかを調べよ.
■ 書いたコード
def ngram(s, n):
    items = []
    last = len(s) - n + 1
    for i in range(last):
        items.append(s[i: i+n])
    return items

text1 = "paraparaparadise"
text2 = "paragraph"
x = set(ngram(text1, 2))
y = set(ngram(text2, 2))

print("x:", x)
print("y:", y)

union = x | y
print("\n和集合(x | y):")
print(union)

intersection = x & y
print("\n積集合(x & y):")
print(intersection)

difference1 = x - y
print("\n差集合(x - y):")
print(difference1)

print('')
print('seがxに含まれる:', ('se' in x))

issubset = {'se'} <= y
print('seがyに含まれる:', issubset)

■ 実行結果
x: {'se', 'ap', 'is', 'di', 'pa', 'ra', 'ar', 'ad'}
y: {'gr', 'ap', 'ag', 'pa', 'ra', 'ar', 'ph'}

和集合(x | y): {'se', 'gr', 'ra', 'ar', 'ad', 'ap', 'is', 'di', 'ag', 'pa', 'ph'}
積集合(x & y): {'ra', 'ar', 'ap', 'pa'}
差集合(x - y): {'se', 'is', 'di', 'ad'}
seがxに含まれる: True seがyに含まれる: False

■ 今回学んだこと


集合演算を行うには、配列をset関数で、集合に変換します。
x = set([1, 2, 3, 4, 5])
集合演算は、演算子を使う方法と、メソッド呼び出しの2種類あるようです。

和集合
union1 = x | y
union2 = x.union(y)
積集合
intersection1 = x & y
intersection2 = x.intersection(y) 
差集合
difference1 = x - y
difference2 = x.difference(y)
包含
subset1 = {'se', 'ap'} <= x
subset2 = {'se', 'ap'}.issubset(x)
# 単独の要素ならば、in 演算子が使える
subset3 = 'se' in x
<= 演算子って、ちょっと違和感ありますね。
今まで触った言語の中で、集合演算子で使われているのってたぶんこれは初めてかも。 

print関数は、複数の引数を受け取れる

print関数は、print(a, b) のように複数の引数を受け取れるんですね。便利です。
この場合は、引数の間に一つの空白文字が挿入されるようです。


先頭行のブレークポイントを解除する

それと、Visual Studio Codeでデバッグする時に、いつも先頭行で実行が一時停止になってしまうんですが、これをやめるには、launch.jsonで、次のような設定をすればOKです。
    "configurations": [
        {
            "name": "Python",
            "type": "python",
            "request": "launch",
            "stopOnEntry": false,    // ココをfalseに変更
           ...
  
Posted by gushwell at 22:00Comments(0)Python

2018年01月21日

言語処理100本ノックでPython入門 #05 - 関数

   このエントリーをはてなブックマークに追加 Clip to Evernote

今日は、言語処理100本ノックの問題05に挑戦です。

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

■ 書いたコード
def ngram(s, n):
    items = []
    last = len(s) - n + 1
    for i in range(last):
        items.append(s[i: i+n])
    return items

text = "I am an NLPer"
x = ngram(text, 2)
print(x)
x = ngram(text.split(), 2)
print(x)

ngramの第2引数の2を渡すことで、bi-gramが求まります。 最初のngramの呼び出しで、文字bi-gram、2つめのngramの呼び出しで単語bi-gramを求めています。


■ 実行結果
['I ', ' a', 'am', 'm ', ' a', 'an', 'n ', ' N', 'NL', 'LP', 'Pe', 'er']
[['I', 'am'], ['am', 'an'], ['an', 'NLPer']]


■ 今回学んだこと


リストへの追加
items = []
items.append("a")    
items.append("b")    
Pythonの [] は配列というよりも、C#でいうリストそのものですね。


関数の定義

関数の定義は、def キーワードを使います。
def ngram(s, n):
引数は、仮引数の名前だけを指定します。 C#と違って戻り値の方は指定する必要はないです。

はじめは、wordngram, charngram のような2つの関数を作るのかなと思ったのですが、そうする必要はありませんでした。

文字ngarmに対応するコードを書いたあと、単語ngramへのコードを書いたら、まったく同じコードになりました。

なので、ngramという関数一つでOKです。

C#のジェネリックのようなことが、特別なコードを書かなくてもできるのは面白いですね。
これは、明示的な型が無い言語の利点ではありますね。

関数の書き方覚えたので、これでもうすこし複雑なプログラムも書けそうです。
   
Posted by gushwell at 20:17Comments(0)Python

2018年01月18日

言語処理100本ノックでPython入門 #04 - 辞書型

   このエントリーをはてなブックマークに追加 Clip to Evernote

今日は、言語処理100本ノックの問題04に挑戦です。

■問題
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

text = "Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can."
array = re.split(r'\s|,|\.', text)
words = [s for s in filter(lambda w: len(w) > 0, array)]
worddict = {}
for i, e in enumerate(words, 1):
    length = 1 if i in [1, 5, 6, 7, 8, 9, 15, 16, 19] else 2
    worddict[e[0:length]] = i
print(worddict.items())

■実行結果
[('Be', 4), ('C', 6), ('B', 5), ('Ca', 20), ('F', 9), ('S', 16), ('H', 1), ('K', 19), ('Al', 13), ('Mi', 12), ('Ne', 10), ('O', 8), ('Li', 3), ('P', 15), ('Si', 14), ('Ar', 18), ('Na', 11), ('N', 7), ('Cl', 17), ('He', 2)]

■今回学んだことなど


今回は、辞書の使い方を学びました。

辞書の初期化
dict = {}  
辞書に値を設定
doct[key] = value
辞書の中身を表示する
print(dict.items())

その他

・for文
for i, e in enumerate(words, 1):
とすれば、enumerateの最後の引数でインデックスの初期値を決められます。


・リストに含まれるかを調べる
num in [1, 5, 6, 7, 8, 9, 15, 16, 19] 

・条件演算子

C#だと、? : 使うけど、pythonだと、次のように書けます。でもちょっと読みにくいです。
len = 1 if i in [1, 5, 6, 7, 8, 9, 15, 16, 19] else 2

・長い文字列を複数行に分けて書く

文字列の初期化で、以下の警告が出ます。
message: 'C0301:Line too long (103/100)'
これを以下のようにバックスラッシュを最後に書くと複数行にかけるので、警告が消えます。
text = "Hi He Lied Because Boron Could Not Oxidize Fluorine. "\
       "New Nations Might Also Sign Peace Security Clause. Arthur King Can."
  
Posted by gushwell at 20:45Comments(0)Python