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)

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)

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)

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)

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)

2018年01月16日

言語処理100本ノックでPython入門 #03 - 正規表現, リスト内包表記

   このエントリーをはてなブックマークに追加 Clip to Evernote
今日は、言語処理100本ノックの問題03です。

■ 問題

03. 円周率
"Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics."

という文を単語に分解し,各単語の(アルファベットの)文字数を先頭から出現順に並べたリストを作成せよ.

■ 初めに書いたコード

import re
text = "Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics." array = re.split(r'\s|,|\.', text) for s in filter(lambda w: len(w) > 0, array): print(len(s))

■ 次に書いたコード

import re
text = "Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics." array = re.split(r'\s|,|\.', text) lens = [len(s) for s in filter(lambda w: len(w) > 0, array)] print(lens)

■ 最終版のコード

import re
text = "Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics." array = re.split(r'\s|,|\.', text) lens = [len(x) for x in array if len(x) > 0] print(lens)

■ 最終版の実行結果

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

■ 今回学習したこと
 

正規表現の機能を使うには、
import re
と importします。

spliteメソッドで、分割できます。C#にも似たようなメソッドありますし、どの言語にもこういった機能は用意されてるんですね。
text = "Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics."
array = re.split(r'\s|,|\.', text)
r'\s|,|\.' が正規表現です。 'r' を前置した文字列リテラルではバックスラッシュを特別扱いしません。
C#の@”..."と同じです。

filter関数は、C#のWhereと同じですね。でもラムダ式書くのが面倒ですね。
for s in filter(lambda w: len(w) > 0, array):
    print(len(s))
これで、空文字を省いてくれます。

リスト内包表現使うと、さらに簡潔に書けます。
lens = [len(s) for s in filter(lambda w: len(w) > 0, array)]
でも、if を使うともっと簡単に書けました。
items = [x for x in array if len(x) > 0]
ちなみに、リスト内包表現のもっとも基本的な書き方は、
lens = [len(x) for x in items]
といった写像処理ですね。

以前、HaskellやF#をすこし勉強したので、リスト内包表現は意外と違和感なく使えました。

でもpythonのラムダ式、これはC#の方が良いですね。アロー式が使えればもっと簡単に書けるのにね。  
Posted by gushwell at 21:30Comments(0)

2018年01月14日

言語処理100本ノックでPython入門 #02 - for, zip, enumerate

   このエントリーをはてなブックマークに追加 Clip to Evernote
言語処理100本ノックの問題02をやりました。

■問題


第1章: 準備運動

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

■書いたコード

s1 = 'パトカー'
s2 = 'タクシー'
s3 = ""
for a, b in zip(s1, s2):
    s3 += a + b
print(s3)

■実行結果

パタトクカシーー

■今回学習したこと


文字列は、シングルクォートで括ってもOK.
実際に、ここ(https://docs.python.jp/3/reference/index.html)を見たら確かにそう書いてありました。

この問題は、C#だったらLINQのZipメソッド使う場面ですね。Zipと似たような機能がないかな〜と探したらありました。

zip関数をfor文とともに使えば、2つのシーケンスから同時に値を取り出すことができます。これ、めちゃくちゃ便利ですね。
pythonのfor文は、C#でいうとforeachにあたるんですね。

実は、初めに書いたのは、以下のようなコード
s1 = 'パトカー'
s2 = 'タクシー'
s3 = ""
for i, e in enumerate(s1):
    s3 += e + s2[i]
print(s3)

zip関数ではなく、enumerate関数使ってます。 こうすると、文字列を1文字ずつ列挙するとともに、インデックスもあわせて列挙できます。
これはこれで便利な機能ですね。どこかで使えそうです。

ちなみに、以下のように書けば、s1から1文字ずつ文字を取り出せます。
for e in s1:
    print(e)

これが、pythonのfor文の基本ですね。

じゃあ、C#でいう普通のfor文はどうやって書くのかなと思ったら、そのような構文は無いみたいです。
どうしても必要ならば、range関数でシーケンスを生成すればOK.
for i in range(10):
    print(i)

これで、0から9までを列挙できます。
次のように書けば、1から9までを列挙できます。

for i in range(1, 10): print(i)

次のような書き方もできます。これでも、s1の中の文字を1文字ずつ列挙できます。
for i in range(len(s1)):
    print(s1[i])

でもこのコードは、以下のように「enumerate使ってね」と警告がでます。pylint良くできてますね。
message: 'C0200:Consider using enumerate instead of iterating with range and len'

今回はいろんなことを学習できました。  
Posted by gushwell at 21:30Comments(0)

2018年01月10日

言語処理100本ノックでPython入門 #01 - スライス

   このエントリーをはてなブックマークに追加 Clip to Evernote
言語処理100本ノックのNo01の問題を解きます。

■問題

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

No00 でやったスライス機能使えばいいんですね。


■コード

s = "パタトクカシーー"
rev = s[::2]
print(rev)
上のコードのように、stepの指定を2にすれば、一つ置きに取り出せます。 デフォルトは1です。


■結果

パトカー

ちなみに、

rev = s[1::2]
print(rev)
とすれば、開始位置が0ではなく、1になるので、以下の結果が得られます。
タクシー


■今回学習したこと
 

特になし。No00で解いた時に学んだ内容で解けました。  
Posted by gushwell at 20:14Comments(0)