脳筋プログラミング

筋トレ大好きな初心者プログラマーの備忘録です。

言語処理100本ノック(15〜19)

15.末尾のN行を出力

問題:自然数Nをコマンドライン引数などの手段で受け取り,入力のうち末尾のN行だけを表示せよ.確認にはtailコマンドを用いよ.

解答(Python):

import sys

args = sys.argv

basefile = open('hightemp.txt')
basefile_line = basefile.readlines()
basefile_line_len = len(basefile_line)
displayline = basefile_line_len - int(args[1]) + 1

for x, y in reversed(list(enumerate(basefile_line, start=1))):
    print(y)
    if x == displayline:
        break


basefile.close()

##########結果##########

(コマンドラインで3を入力した場合)

愛知県   名古屋   39.9   1942-08-02
山形県   鶴岡  39.9   1978-08-03
山梨県   大月  39.9   1990-07-19

解答(Unixコマンド):

$ tail -n 3 hightemp.txt

##########結果##########

山梨県   大月  39.9    1990-07-19
山形県   鶴岡  39.9    1978-08-03
愛知県   名古屋   39.9    1942-08-02

考察:Pythonではreversedしていますので、逆順です。なので、Unixコマンドの結果とは逆になっています。あんまり綺麗ではない...スライスで切り出して表示とかの方がいいのでしょうが、ひとまずこれで完了とします。

16.ファイルをN分割する

問題:自然数Nをコマンドライン引数などの手段で受け取り,入力のファイルを行単位でN分割せよ.同様の処理をsplitコマンドで実現せよ.

解答(Python):

import sys
import itertools

args = sys.argv

basefile = open('hightemp.txt')
basefile_line = basefile.readlines()

split_after = [x for x in itertools.zip_longest(*[iter(basefile_line)]*int(args[1]))]

for x, y in enumerate(split_after, start=0):
    flopen = open('split_{0}.txt'.format(x + 1), 'w')
    for z in split_after[x]:
        if z is not None:
            flopen.write(z)
    flopen.close()

basefile.close()

##########結果##########

(コマンドラインで7を入力した場合)

◯split_1.txt
高知県   江川崎   41 2013-08-12
埼玉県   熊谷  40.9   2007-08-16
岐阜県   多治見   40.9   2007-08-16
山形県   山形  40.8   1933-07-25
山梨県   甲府  40.7   2013-08-10
和歌山県    かつらぎ    40.6   1994-08-08
静岡県   天竜  40.6   1994-08-04

◯split_2.txt
山梨県   勝沼  40.5   2013-08-10
埼玉県   越谷  40.4   2007-08-16
群馬県   館林  40.3   2007-08-16
群馬県   上里見   40.3   1998-07-04
愛知県   愛西  40.3   1994-08-05
千葉県   牛久  40.2   2004-07-20
静岡県   佐久間   40.2   2001-07-24

(以下、split_4.txtまで繰り返しのため省略)

解答(Unixコマンド):

$ split -l 7 hightemp.txt split_

##########結果##########

split_aa〜split_adでファイル出力。内容はPython版と変わらず。

考察:コマンド引数ごとに分割する方法を最初はzipでやっていたのですが、余剰がある場合は切り捨てられるという自体に陥ってどうしようと思っていたら、丁度良い記事が見つかりました。 (参考:リストをn個ずつのサブリストに分割 (Python) - おぎろぐはてブロUnixコマンドでは「-d」オプションで出力するファイル名をPython版で統一しようと思ったら「split: -d: illegal line count」と言われました。何でだ...と調べたら、OS X版のsplitには存在しないということでした。 (参考:macos - -d option for split is illegal on OS X 10.9 - Ask Different

17.1列目の文字列の異なり

問題:1列目の文字列の種類(異なる文字列の集合)を求めよ.確認にはsort, uniqコマンドを用いよ.

解答(Python):

basefile = open('col1.txt')
line = basefile.readlines()

commonlist = {x.replace('\n', '') for x in line}

basefile.close()

for y in commonlist:
    print(y)

##########結果##########

岐阜県
埼玉県
千葉県
愛知県
愛媛県
大阪府
高知県
静岡県
和歌山県
群馬県
山梨県
山形県

解答(Unixコマンド):

$ cat col1.txt | sort | uniq

##########結果##########

Python版と同じ

考察:なし

言語処理100本ノック(10〜14)

10.行数のカウント

問題:行数をカウントせよ.確認にはwcコマンドを用いよ.

解答(Python):

print(len(open('hightemp.txt', 'r').readlines()))

##########結果##########

24

解答(Unixコマンド):

$ wc -l  hightemp.txt

##########結果##########

24 hightemp.txt

考察:別解でこんな書き方もありました。

解答(Python):

print(sum(1 for i in open('hightemp.txt', 'r')))

##########結果##########

24

【2018/1/5 追記】 こちらで質問させていただいたのですが、subprocessでwcを利用した方法もあるとのこと。

teratail.com

解答(Python):

import subprocess

run = subprocess.getoutput
t = run('wc -l {0}'.format('hightemp.txt'))
print(t)

##########結果##########

24 hightemp.txt

Python奥が深いですね。とても勉強になります!

11.タブをスペースに置換

問題:タブ1文字につきスペース1文字に置換せよ.確認にはsedコマンド,trコマンド,もしくはexpandコマンドを用いよ.

解答(Python):

basefile = open('hightemp.txt', 'r')
newfile = open('hightemp_new.txt', 'w')

for s in basefile:
    s = s.expandtabs(1)
    newfile.write(s)

newfile.close()
basefile.close()

##########結果##########

高知県 江川崎 41 2013-08-12
埼玉県 熊谷 40.9 2007-08-16
岐阜県 多治見 40.9 2007-08-16
山形県 山形 40.8 1933-07-25
山梨県 甲府 40.7 2013-08-10
和歌山県 かつらぎ 40.6 1994-08-08
静岡県 天竜 40.6 1994-08-04
山梨県 勝沼 40.5 2013-08-10
埼玉県 越谷 40.4 2007-08-16
群馬県 館林 40.3 2007-08-16
群馬県 上里見 40.3 1998-07-04
愛知県 愛西 40.3 1994-08-05
千葉県 牛久 40.2 2004-07-20
静岡県 佐久間 40.2 2001-07-24
愛媛県 宇和島 40.2 1927-07-22
山形県 酒田 40.1 1978-08-03
岐阜県 美濃 40 2007-08-16
群馬県 前橋 40 2001-07-24
千葉県 茂原 39.9 2013-08-11
埼玉県 鳩山 39.9 1997-07-05
大阪府 豊中 39.9 1994-08-08
山梨県 大月 39.9 1990-07-19
山形県 鶴岡 39.9 1978-08-03
愛知県 名古屋 39.9 1942-08-02

解答(Unixコマンド):

$ cat hightemp.txt | tr '\t' ' ' > hightemp_new2.txt

##########結果##########

Python版と同じ

考察:なし

12.1列目をcol1.txtに,2列目をcol2.txtに保存

問題:各行の1列目だけを抜き出したものをcol1.txtに,2列目だけを抜き出したものをcol2.txtとしてファイルに保存せよ.確認にはcutコマンドを用いよ.

解答(Python):

basefile = open('hightemp.txt', 'r')
col1 = open('col1.txt', 'w')
col2 = open('col2.txt', 'w')

for x in basefile:
    col1.write(x.split('\t')[0] + '\n')
    col2.write(x.split('\t')[1] + '\n')

col2.close()
col1.close()
basefile.close()

##########結果##########

(col1.txt)
高知県
埼玉県
岐阜県
山形県
山梨県
和歌山県
静岡県
山梨県
埼玉県
群馬県
群馬県
愛知県
千葉県
静岡県
愛媛県
山形県
岐阜県
群馬県
千葉県
埼玉県
大阪府
山梨県
山形県
愛知県

(col2.txt)
江川崎
熊谷
多治見
山形
甲府
かつらぎ
天竜
勝沼
越谷
館林
上里見
愛西
牛久
佐久間
宇和島
酒田
美濃
前橋
茂原
鳩山
豊中
大月
鶴岡
名古屋

解答(Unixコマンド):

$ cut -f 1 hightemp.txt > col1_unix.txt
$ cut -f 2 hightemp.txt > col2_unix.txt

##########結果##########

Python版と同じ

考察:なし

13.col1.txtとcol2.txtをマージ

問題:12で作ったcol1.txtとcol2.txtを結合し,元のファイルの1列目と2列目をタブ区切りで並べたテキストファイルを作成せよ.確認にはpasteコマンドを用いよ.

解答(Python):

col1 = open('col1.txt', 'r')
col2 = open('col2.txt', 'r')
col3 = open('col3.txt', 'w')  # マージ用のファイルを事前に作成

for x, y in zip(col1, col2):
    col3.write(x.replace('\n', '\t') + y)

col2.close()
col1.close()
col3.close()

##########結果##########

(col3.txt)
高知県   江川崎
埼玉県   熊谷
岐阜県   多治見
山形県   山形
山梨県   甲府
和歌山県    かつらぎ
静岡県   天竜
山梨県   勝沼
埼玉県   越谷
群馬県   館林
群馬県   上里見
愛知県   愛西
千葉県   牛久
静岡県   佐久間
愛媛県   宇和島
山形県   酒田
岐阜県   美濃
群馬県   前橋
千葉県   茂原
埼玉県   鳩山
大阪府   豊中
山梨県   大月
山形県   鶴岡
愛知県   名古屋

解答(Unixコマンド):

paste col1.txt col2.txt > col3.txt

##########結果##########

Python版と同じ

考察:なし

14.先頭からN行を出力

問題:自然数Nをコマンドライン引数などの手段で受け取り,入力のうち先頭のN行だけを表示せよ.確認にはheadコマンドを用いよ.

解答(Python):

import sys

args = sys.argv

displayline = int(args[1])
basefile = open('hightemp.txt', 'r')

print(args[1])

for x, y in enumerate(basefile, start=1):
    print(y)
    if displayline == x:
        break

basefile.close()

##########結果##########

(コマンドラインで3を入力した場合)

高知県   江川崎   41 2013-08-12

埼玉県   熊谷  40.9   2007-08-16

岐阜県   多治見   40.9   2007-08-16

解答(Unixコマンド):

$ head -n 3 hightemp.txt

##########結果##########

Python版と同じ

考察:なし

言語処理100本ノック(06〜09)

06.集合

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

和集合,積集合,差集合に関して復習しておきます。 そもそも「集合」とは...

  • 要素が重複しない

  • 要素は順序付けされない

です。これを踏まえたうえで、a・bの2つの集合があった場合、

  • 和集合:aとbどちらかには含まれる

  • 積集合:aとbどちらにも含まれる

  • 差集合:aにのみ含まれる

ということになります。簡単に確認してみます。

a = {1, 1, 2, 4, 5, 7, 8, 9, 0}
b = {2, 3, 5, 5, 6, 9, 0}

x = set(a)
y = set(b)

print('和集合:' + str(x|y))
print('積集合:' + str(x&y))
print('差集合:' + str(x-y))

##########結果##########

和集合:{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
積集合:{0, 9, 2, 5}
差集合:{8, 1, 4, 7}

ということで、問題を解いてみます。

解答:

a = "paraparaparadise"
b = "paragraph"


def text_ngram(x):
    # 文字bi-gram
    text = set([x[b:b+2] for b in range(len(x) - 1)])

    return text

X = text_ngram(a)
Y = text_ngram(b)

wa = str(X|Y)
sa = str(X-Y)
seki = str(X&Y)

print('和集合:' + wa)
print('積集合:' + seki)
print('差集合:' + sa)

if 'se' in X:
    print('Xにseが含まれている')
else:
    print('Xにseが含まれていない')

if 'se' in Y:
    print('Yにseが含まれている')
else:
    print('Yにseが含まれていない')

##########結果##########

和集合:{'di', 'is', 'ad', 'ph', 'ra', 'ag', 'pa', 'ar', 'ap', 'gr', 'se'}
積集合:{'ra', 'ar', 'pa', 'ap'}
差集合:{'di', 'is', 'se', 'ad'}
Xにseが含まれている
Yにseが含まれていない

考察:なし

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

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

解答:

def template(x, y, z):
       return print(str(x) + '時の' + str(y) + 'は' + str(z))


template(12, '気温', 22.4)

##########結果##########

12時の気温は22.4

考察:「あれ、全然難しくない」と思いましたが、str()とキャストしているところがイケてない気がします。ということで、fomat関数がありました。忘れてました。(参考:2. 組み込み関数 — Python 3.6.3 ドキュメント

def template(x, y, z):
       return "{0}時の{1}は{2}".format(x, y, z)


print(template(12, '気温', 22.4))

##########結果##########

12時の気温は22.4

08.暗号文

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

  • 英小文字ならば(219 - 文字コード)の文字に置換

  • その他の文字はそのまま出力

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

解答:

import re

regex = re.compile('[^a-z]')

def cipher(x):
       return ''.join(re.sub('[a-z]', chr(219-ord(i)), i) for i in x)


text = "Hi! My name is Bob. 20 years old."

print(cipher(text))
print(cipher(cipher(text)))

##########結果##########

Hr! Mb mznv rh Bly. 20 bvzih low.
Hi! My name is Bob. 20 years old.

考察:これも最初は(219 - 文字コード)が理解できないままだった(219文字コードというものが存在するのかと思ってました...)のですが、単純に219から文字コードを引けばいいだけでした。ord関数はUnicodeコードポイントを表す整数を返し、chr関数はその逆をします。(参考:2. 組み込み関数 — Python 3.6.3 ドキュメント

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 randomize(sym):

    str1 = [i for i in sym.split()]

    for y, i in enumerate(str1):
        if len(i) > 4:
            z = []
            for x in i[1:-1]:
                z += x
            random.shuffle(z)
            str1[y] = i[0] + ''.join(z) + i[-1]

    return ' '.join(str1)


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


print(randomize(text))

##########結果##########

I c'oludnt bilevee that I could acultlay unetnrsadd what I was rdaeing : the pnehaneoml pewor of the human mind .
(ランダムなので実行時、都度変わります)

考察:スペースで区切った単語をさらに区切ってシャッフルして...が微妙です。shuffleを利用するためにリスト化していましたが、sampleを使えばリスト化しなくてもいけそうです。(参考:9.6. random — 擬似乱数を生成する — Python 3.6.3 ドキュメント

sampleで書き直したのがこちら

import random


def randomize(sym):

    str1 = [i for i in sym.split()]

    for y, i in enumerate(str1):
        if len(i) > 4:
            str1[y] = i[0] + ''.join(random.sample(i[1:-1], len(i[1:-1]))) + i[-1]

    return ' '.join(str1)


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


print(randomize(text))

##########結果##########

I cl'nudot bleeive that I cloud aullcaty utrdenasnd what I was riendag : the phaoeemnnl pwoer of the haumn mind .
(ランダムなので実行時、都度変わります)

これをさらにリスト内包表記で書くとこんな感じです

import random


def randomize(sym):

    str1 = [i for i in sym.split()]

    str1 = [i[0] + ''.join(random.sample(i[1:-1], len(i[1:-1]))) + i[-1] if len(i) > 4 else i for y, i in enumerate(str1)]

    return ' '.join(str1)


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


print(randomize(text))

##########結果##########

I clun'odt bivleee that I cuold aulaclty unrdsaetnd what I was raiendg : the pnaomhenel pwoer of the hamun mind .
(ランダムなので実行時、都度変わります)

さらに短縮できそうですが、上記の記法でもかなり難読だと思っているので、これぐらいにしておきます。

言語処理100本ノック(00〜05)

勉強のために以下で今日表されている「言語処理100本ノック 2015」を順番に解いていきます。

http://www.cl.ecei.tohoku.ac.jp

将来的には「もっとスマートに書けるじゃん」となっていればいいですが、とりあえず今は泥臭くやっていこうと思います。 難しそうなところは自分なりの考察を入れて書いていきます。最初はないかな...

00.文字列の逆順

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

解答:

print("stressed"[::-1])

##########結果##########

desserts

考察:なし

01.「パタトクカシーー」

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

解答:

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

##########結果##########

パトカー

考察:なし

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

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

解答:

str1 = "パトカー"
str2 = "タクシー"
str3 = ""

for i in range(len(str1)):
       str3 += str1[i] + str2[i]

print(str3)

##########結果##########

パタトクカシーー

考察:全然スマートじゃないですね。調べたところ、zip関数というものがありました。(参考:2. 組み込み関数 — Python 3.6.3 ドキュメント) 入力イテラブルの中で最短のものが尽きたときに止まるようなので、lenを取る必要ものないですね。

str1 = "パトカー"
str2 = "タクシー"
str3 = ""

for a, b in zip(str1, str2):
       str3 += (a+b)

print(str3)

##########結果##########

パタトクカシーー

03. 円周率

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

解答:

import re

regex = re.compile('[^a-zA-Z]')  # (1)

str1 = "Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics."
str2 = [len(regex.sub('', i)) for i in str1.split()]  # (2)
print(str2)

##########結果##########

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

考察:ここで最初のimportですね。文中には「.」と「,」しかありませんが、その他の記号も考慮して、regexで対処します。(1)(stringのascii_lowercaseあたりを活用したかったのですが、上手い方法が見つかりませんでした...)

(2)はリスト内包表記です。意味合い的には「str1を空白でsplitしながら、「a-zA-Z」以外の文字が入って入れば削除し、その結果の文字数をリストに突っ込む」と言った感じでしょうか。

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

regex = re.compile('[^a-zA-Z]')

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

str2 = [regex.sub('', i) for i in str1.split()]
number = [1, 5, 6, 7, 8, 9, 15, 16, 19]

result = {}

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

print(result)

##########結果##########

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

考察:全然スマートじゃないですね!特に「i + 1」とかやってるあたり...(1)は先ほどの「03.円周率」と同様の方法です。調べるとenumerate関数あたりが使い勝手が良さそうです。(参考:2. 組み込み関数 — Python 3.6.3 ドキュメント

import re

regex = re.compile('[^a-zA-Z]')

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

str2 = [regex.sub('', i) for i in str1.split()]
number = [1, 5, 6, 7, 8, 9, 15, 16, 19]

result = {}

for i, a in enumerate(str2, start=1):
       if i in number:
              result[i] = a[:1]
       else:
              result[i] = a[:2]

print(result)

##########結果##########

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

気持ち悪い感じはなくなりました。が、Pythonのことだからもっと短く、もっとスマートに書けそう! ということで、こちらがよりスマートな書き方です。

import re

regex = re.compile('[^a-zA-Z]')

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

str2 = [regex.sub('', i) for i in str1.split()]
number = [1, 5, 6, 7, 8, 9, 15, 16, 19]

result = {i: a[:2-(i in number)] for i, a in enumerate(str2, start=1)}

print(result)

##########結果##########

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

辞書内包表記ですね。特に「2-(i in number)」はfor文を省略しているのでかなり簡略化しています。

最初からこの解答に辿り着くにはまだまだ勉強が必要ですね。

05. n-gram

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

すいません、最初に申し上げておきますが、恥ずかしながら問題文の意味が全くわからなかったです。 そもそもn-gramとは何なのか、については以下のサイトで詳細に書かれていましたので、参考にさせていただきました。(参考:N-gram - Negative/Positive Thinking) そして今回の問題では、N-gramの中でも2-gram(bigram)で出力しろ、ということなんですね。

解答:

a = "I am an NLPer"

def word_ngram():
       # 単語bi-gram
       word = [b for i in [a] for b in zip(i.split(" ")[:-1], i.split(" ")[1:])]  # (1)

       return word

def text_ngram():
       # 文字bi-gram
       text = [a[b:b+2] for b in range(len(a) - 1)]

       return text

print(word_ngram())
print(text_ngram())

##########結果##########

[('I', 'am'), ('am', 'an'), ('an', 'NLPer')]
['I ', ' a', 'am', 'm ', ' a', 'an', 'n ', ' N', 'NL', 'LP', 'Pe', 'er']

(1)は内包表記の2重ループです。最初見たときはギョっとしましたが、分解すると以下と同義です。

word = []
for i in [a]:
       for b in zip(i.split()[:-1], i.split()[1:]):
              word.append(b)

i.split()[:-1]、i.split()[1:]で空白前後の単語を抽出し、それをzip関数でイテレータで返しています。

5問目でいきなり難易度が高くなっているように感じましたが、引き続きやっていきます。