今回は第11回から第16回までの演習を総括し、解説と解答を掲載していきます。


第11回「知っていると便利な「型」について学ぼう」の解答解説

演習1

問題

リストから重複を取り除く関数を、セットを使って作成してください。要素の順序は問いません。

解説

セットはその中身に重複が発生しません。

list1 = ['apple', 'bingo', 'cake', 'apple', 1, 'dance']
print(set(list1))
# set(['bingo', 'cake', 'apple', 'dance', 1])

解答

一番簡単な解答は、単にlist -> set -> listと変換することです。

def remove_dup(original_list):
    return list(set(original_list))

list1 = ['apple', 'bingo', 'cake', 'apple', 1, 'dance']
print(remove_dup(list1))

順序を維持しようとすると、もう少し高度になり、setに重複の管理をさせてlistの詰め直しをします。新しいlistの中に要素がなければ、それを詰めるというアルゴリズムでもいいかもしれないですね。

def remove_dup(original_list):
    dup_check_set = set()
    new_list = []

    for i in original_list:
        if not i in dup_check_set:
            dup_check_set.add(i)
            new_list.append(i)

    return new_list

演習2

問題

標準入力を使って生徒の成績を管理するツールを作ってください。"save 生徒名 点数"とすると、その生徒の点数を保存します。"get 生徒名"とするとその生徒の点数を表示します。生徒が登録されていない場合は"Error"と出力させて処理を継続させます(※終了は Ctrl-D などで強制終了してかまいません)。

解説

辞書型を使う問題です。"save 生徒名 点数"とした場合は、生徒名をkey、点数をvalueとした「格納(store)」を行い、"get 生徒名"とした場合は、生徒名をkeyとしてvalueである点数を「取得」します。登録されていないkeyのvalueを取得しようとするとエラーになるので、取得の前に key が登録されているかどうかの確認が必要です。

解答

d = {}

while(True):
    line = raw_input()
    words = line.split()

    if(words[0] == 'save' and len(words) == 3):
        key = words[1]
        value = words[2]
        d[key] = value

    elif(words[0] == 'get' and len(words) == 2):
        key = words[1]
        if key in d:
            print(d[key])
        else:
            print('Error')

    else:
        print('Input Error')

以前お話した対話型のプログラムの実装方法を使っています。while文で無限ループさせています。

まず最初に標準入力でキーボードからの入力を取得し、次にそれをsplit関数で分割します。たとえば'save taro 10'と入力すると、['save', 'taro', '10']というリストになります。

次に1番目の要素がsave, getで、なおかつきちんと適切な数の入力が入っている場合のみ、それぞれのアクションを実行し、問題がある場合は'Input Error'と表示し、特になにもしません。saveとgetの中での辞書の取り扱いは、解説で書いたとおりです。

演習3

問題

英語の文章の単語出現数をカウントするプログラムを書いてください。たとえば'hello python hello world'というテキストを与えると、{'hello':2, 'python':1, 'world':1}が返ってきます。なお、当然ながら辞書オブジェクト内のキーの順序は問いません。

解説

長いテキストを、まず単語に区切る作業が必要です。たとえば'hello python hello world'というテキストは['hello', 'python', 'hello', 'world']とします。次に辞書型を使って、keyに単語、valueに出現数を持たせて単語のカウントを行います。

解答

def get_word_count_map(text):
    words = text.split()
    d = {}
    for word in words:
        if word in d:
            count = d[word]
            d[word] = count + 1
        else:
            d[word] = 1
    return d

print(get_word_count_map('hello python hello world'))

# {'python': 1, 'world': 1, 'hello': 2}

文字が辞書にすでに登録されている場合は出現数を取得し、それに+1を行った後で再登録(上書き)しています。文字が辞書に登録していない場合は、key単語、value1(出現回数)として登録しています。


第12回「Pythonでテキスト処理/ファイル処理をしてみよう」の解答解説

演習1

問題

以下のCSV形式のテキストデータから、教科ごとの生徒の平均点を算出してください。

text = '''
lecture\students, 1, 2, 3, 4
math, 80, 70, 75, 54
english, 60, 80, 90, 80
'''

可能なら生徒や教科が増えても対応可能なプログラムにしてください。

解説

テキスト処理と型変換の問題です。CSVを処理するためには、

  1. 記号 ',' で分割
  2. 返された要素の前後の空白を削除

とすればだいたい大丈夫です。

解答

text = '''lecture\students, 1, 2, 3, 4
math, 80, 70, 75, 54
english, 60, 80, 90, 80'''

lines = text.split('\n')
del lines[0]

for line in lines:
    words = line.split(',')
    cariculum = words[0]
    del words[0]
    sum = 0
    for score in words:
        sum += int(score)
    average = sum / len(words)
    print(cariculum + ' : ' + str(average))

ちゃんと入力されていないときの例外処理なども欲しいところですが、Pythonだと長期間使わないコードは適当でよいと思います。入力の1行目を捨てて、各行に対して第二要素から最後の要素までの合計を算出、平均を求めるという手順で実装されています。

演習2

問題

あるテキストファイルAの内容を読み取り、まったく同じ内容をファイルBに書き出すプログラムを書いてください。

解説

ファイル処理の簡単な問題です。1行1行読み書きしてもかまいませんし、まとめて読んで書いてもかまいません。

解答

INPUT_FILE = '/Users/yuichi/Desktop/readfile.txt'
OUTPUT_FILE = '/Users/yuichi/Desktop/writefile.txt'

r = open(INPUT_FILE, 'r')
w = open(OUTPUT_FILE, 'w')

for line in r:
    w.write(line)

r.close()
w.close()

演習3

問題

演習2で作ったプログラムを改良し、ファイルBに行番号を書き出すようにしてください。ただし、行番号は最後の行の桁数にあるように0詰めしてください。たとえば以下のようになります。

a
b
c
…
i
j
k
…
z



01 a
02 b
03 c
…
09 i
10 j
11 k
…
26 z

解説

まず行数を把握して、0詰めすべき桁数を把握する必要があります。次に0詰めした行数を書き込んだあとで、空白と残りの文を書き込みます。

解答

INPUT_FILE = '/Users/yuichi/Desktop/readfile.txt'
OUTPUT_FILE = '/Users/yuichi/Desktop/writefile.txt'

def get_digit(num_lines):
    if num_lines == 0:
        return 1;    
    digit = 0
    while(num_lines != 0):
        num_lines = num_lines / 10        
        digit += 1
    return digit

r = open(INPUT_FILE, 'r')
w = open(OUTPUT_FILE, 'w')
lines = r.readlines()
digit = get_digit(len(lines))

for i, line in enumerate(lines):
    w.write(str(i).zfill(digit) + ' ' + line)

r.close()
w.close()

演習4

問題

標準入力で入力されたテキストをpickleでファイルに保存してください。そしてそれをロードして、画面に表示してください。さまざまなデータを Pickleで保存して、そのファイルを開いて中身を確認してみてください。

解説

Pickleの使い方はそれほど難しくありません。普通のファイルの扱いと同じようにきちんとcloseすることにだけ注意してください。

解答

import pickle

text = raw_input()

f1 = open('_test.dump', 'wb')
pickle.dump(text, f1)
f1.close()

f2 = open('_test.dump', 'rb')
text2 = pickle.load(f2)
f2.close()
print(text2)

第13回「正規表現をマスターしよう」の解答解説

演習1

問題

以下の文字列から正規表現を利用して、英単語(アルファベットのみから構成される)をすべて抜き出してください。

'I have 2 pens.'

解説

すべての抜き出しはfindall関数を使うと簡単です。英単語はアルファベットだけとしているので、[A-Za-z]で問題ありません。

解答

import re

text = 'I have 2 pens'
l = re.findall('[A-Za-z]+', text)
print(l)

演習2

問題

URLのみを、正規表現を利用して抜き出して下さい。

'Urs is http://example.com/index.html'

解説

自分で一から正規表現を作ることは期待していません。汎用的なものは検索するとたいてい解決できることが多いです。たとえば、「python regex url」というキーワードでGoogle検索すると、以下のページが見つかりました。

解答

import re

text = 'Urs is http://example.com/index.html'
urls = re.findall('http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', text)
print(urls)

# ['http://example.com/index.html']

第14回「Pythonで日本語を扱うには? - 文字コードについて理解しよう」の解答解説

演習1

問題

10進数を2進数に変換する関数を既存の関数を使わずに自作してください。引数に整数を受け取り、01で構成される文字列を返します。

解説

10進数を2進数に変換するためには、割り算を繰り返す作業をします。たとえば、35を2進数に変換してみます。

35/2 = 17 余り 1
17/2 = 8  余り 1
8/2 =  4  余り 0
4/2 =  2  余り 0
2/2 =  1  余り 0
1/2 =  0  余り 1

この割り算の際に余り(1,1,0,0,0,1) がでてきますが、これを後の計算で出したものから順につなげていくと2進数となります。今回だと100011ですね。これをプログラムで書いてあげればOKです。

解答

def get_binary(decimal_value):
    if(decimal_value == 0):
        return '0'

    binary_list = []
    while(decimal_value != 0):
        a = decimal_value % 2
        binary_list.append(str(a))
        decimal_value /= 2
    binary_list.reverse()
    return ''.join(binary_list)


for i in range(36):
    print(str(i) + " : " + get_binary(i))

get_binary関数を見てもらうとわかりますが、ループを回るごとに1/2をされて、余りをリストにどんどん追加していっています。すべて割り終わる(0になる)と、余りのリストを反転させて結合して、2進数にしています。

下部のfor文で、0から35までをためしに出力させています。左が10進数、右が2進数です。

0 : 0
1 : 1
2 : 10
3 : 11
4 : 100
...
34 : 100010
35 : 100011

35はちゃんと100011になっていますね。

演習2

問題

コマンドプロンプトかターミナルのエンコードをUTF-8に変更し、Python から"あいうえお"と出力してください。私が確認した限り、文字コードの変更方法は調べれば簡単にやり方が出てきました。

解説

Macの場合は設定画面にいき、文字コードを変更します。

文字コードの設定画面

プログラムを記述するファイルには文字コードの宣言が必要です。

解答

# coding: utf-8

print(u'あいうえお')

演習3

問題

Unicode文字列 "今日はいい天気ですね。おはようございます。ハロー" という文字列を記号 "。" で分割してください。

解説

通常の文字列ではなくUnicode文字列を使用する必要があります。Unicode文字列を使用しない場合は、単なるバイナリの羅列という扱いになるので注意が必要です。

解答

# coding: utf-8

text = u'今日はいい天気ですね。おはようございます。ハロー'
l = text.split(u'。')

for unicode_string in l:
    print(unicode_string)

演習4

問題

Pythonで以下のコマンドを作って下さい。

python convert_codec.py FILE_NAME FILE_CODEC WRITE_CODEC

コマンドライン引数に読み込むファイル名とその文字コード、そして書き出す文字コードを与えます。するとFILE_NAME.WRITE_CODEC.txtというファイル名を指定したコーデックで書き出します。

たとえば、

python convert_codec.py utf8.txt utf-8 sjis

とするとutf8.txt.sjis.txtというファイル名のファイルが作成されます。その中身はutf8.txtと同じですが、文字コードがsjisになっています。

解説

コマンドライン引数でファイル名や文字コードなどを取得し、それを使ってファイルの読み書きをすればいいだけです。

解答

import sys, codecs

if(len(sys.argv) != 4):
   exit()

read_codec = sys.argv[2]
read_file = sys.argv[1]
write_codec = sys.argv[3]
write_file = read_file + "." + write_codec + ".txt"

f_in = codecs.open(read_file, 'r', read_codec)
f_out = codecs.open(write_file, 'w', write_codec)
for line in f_in:
    f_out.write(line)
f_in.close()
f_out.close()

第15回「Pythonをシェルスクリプトのように使ってみよう(前編)」の解答解説

演習1

問題

あるディレクトリの「直下」に存在するすべてのファイル名(ディレクトリは不要)を書き出すプログラムを作ってください。

解説

あるディレクトリという指定の仕方が曖昧でしたね。今回は現在のディレクトリという形で作成しました。引数や標準入力でパスを受け取るのもいいと思います。パス名を加えずにファイル名だけを書くと、現在のディレクトリのファイルという扱いになります。

解答

import os
l = os.listdir('./')
for f in l:
    if(os.path.isfile(f)):
        print(f)

演習2

問題

演習1のプログラムを改造し、あるディレクトリの「配下すべて」を対象としてください。

解説

実装方法はさまざまあると思いますが、今回は再帰を使ったプログラムとしています。

解答

import os

def list_file():
    l = os.listdir('./')
    for f in l:
        if(os.path.isfile(f)):
            print(f)
        elif(os.path.isdir(f)):
            os.chdir(f)
            list_file()
            os.chdir('../')

list_file()

ポイントとなるのはos.chdirを使うことで絶対パスを意識しないコードとなっているところでしょうか。そのディレクトリを探し終わったらもとの階層に戻るために '../' としています。絶対パスを構築してls -lを発行していくプログラムでも問題ありません。


第16回「Pythonをシェルスクリプトのように使ってみよう(後編)」の解答解説

演習1

問題

シェルの'ls -l'コマンドを使って「プログラムが実行されたディレクトリ」の配下にあるディレクトリ名のみ抽出してください。

たとえば'ls -l'を実行すると以下のような出力になります。この各行の最初にある'drwxrwxrwx'はファイルのパーミッション等の情報を示していますが、この最初の文字がdだと、それはディレクトリです。

[root@localhost ~]# ls -l
total 3372
-rw-------.  1 root root    1475 Dec 22  2014 anaconda-ks.cfg
drwxrwxrwx. 18  500  500    4096 Dec 23  2014 click-2.0.1
-rw-r--r--.  1 root root 3423136 Sep 25  2011 click-2.0.1.tar.gz
-rw-r--r--.  1 root root     919 Sep  2 12:58 ping.py
-rw-r--r--.  1 root root     393 Sep  2 09:38 setip.py
-rw-r--r--.  1 root root     477 Sep  1 17:42 test.py
drwxr-xr-x.  2 root root    4096 Sep  2 13:12 testdir1
drwxr-xr-x.  2 root root    4096 Sep  2 13:12 testdir2
-rw-r--r--.  1 root root       0 Sep  2 13:12 testfile1
-rw-r--r--.  1 root root       0 Sep  2 13:12 testfile2

dがディレクトリなので、この階層でプログラムを実行した場合の出力は、

click-2.0.1
testdir1
testdir2

となります。

解説

ls -lの出力を見ればわかりますが、最初がdならば、それはディレクトリです。

解答

きちんと処理しようとすると、そこそこ面倒なコードになってしまいました。これならファイルに関わる関数でコードを書いたほうがいいかもしれないですね。

import commands

result = commands.getoutput('ls -l')
for line in result.split('\n'):
    words = line.split()
    if(len(words) < 9):
        continue

    permission_word = words[0]
    if(permission_word[0] != 'd'):
        continue

    del words[0:8]
    print(' '.join(words))

ただ、一番簡単なコードだと以下にまで短縮できます。

import commands

result = commands.getoutput('ls -l')
for line in result.split('\n'):
    if(line[0] == 'd'):
        words = line.split()
        print(words[len(words) - 1])

スクリプトとしてのコードであれば、こっちのほうが正しいのかもしれません。

演習2

問題

引数で与えられたドメインに対してpingを行い、RTT(Round Trip Time)の平均値をミリ秒で表示するプログラムを作成してください。

たとえば、以下のような出力となります。

test.py cisco.com
average RTT is 185.846 ms

解説

まずpingを実行してどのような表示のされかたをするか確認することから始めます。

[root@localhost ~]# ping 192.168.141.169 -i 0.1 -c 5
PING 192.168.141.169 (192.168.141.169) 56(84) bytes of data.
64 bytes from 192.168.141.169: icmp_seq=1 ttl=64 time=0.568 ms
64 bytes from 192.168.141.169: icmp_seq=2 ttl=64 time=0.899 ms
64 bytes from 192.168.141.169: icmp_seq=3 ttl=64 time=0.471 ms
64 bytes from 192.168.141.169: icmp_seq=4 ttl=64 time=0.445 ms
64 bytes from 192.168.141.169: icmp_seq=5 ttl=64 time=0.443 ms

--- 192.168.141.169 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 403ms
rtt min/avg/max/mdev = 0.443/0.565/0.899/0.173 ms

RTTは各ICMP echoごとのものと、最後のstatisticのところに確認できますね。今回はせっかくstatisticでRTTの平均値を表示してくれているので、それを利用したいと思います。

もし利用しているOSのpingがRTTのaverageを表示してくれなければ、毎回表示される1回ごとのRTTを利用して平均値を求める必要があります。

解答

statisticsを使う場合は、たとえば以下のようになります。

import sys, commands, re

if(len(sys.argv) != 2):
    exit()

dest_ip = sys.argv[1]
ping_command = 'ping ' + dest_ip + ' -c 5'
result = commands.getoutput(ping_command)

regex = re.compile('rtt min/avg/max/mdev = [\d.]+/([\d.]+)/[\d.]+/[\d.]+ ms') 
for line in result.split('\n'):
    m = regex.match(line)
    if m:
        avg = m.group(1)
        print('average RTT is ' + avg + ' ms')

自分で平均を求める場合は次のようになります。前半は同じなので省略しています。

regex = re.compile('\d+ bytes from 1[\d.]+: icmp_seq=\d+ ttl=\d+ time=([\d.]+) ms')
rtt_list = []
for line in result.split('\n'):
    m = regex.match(line)
    if m:
        rtt_list.append(float(m.group(1)))

avg = sum(rtt_list) / len(rtt_list)
print('average RTT is ' + str(avg) + ' ms')

次回の内容

次回は番外編のコラム回となります。これから「オブジェクト指向」というプログラミングの手法にどんどん入っていくのですが、その前に簡単に「関数型」と呼ばれているプログラムの紹介をします。

関数型の言語自体に関する需要はそれほど高くありません。実際に、私も大学を卒業してから関数型言語での開発はしていません。ただ、関数型の根本となる思想は手続き型言語でもオブジェクト指向言語でも非常に役にたつ概念ですので、個人的には関数型を知ることはプログラマとして重要な経験なのではないかと思っています。

そのため、さらっと関数型の言語の特徴について紹介します。また、Pythonも関数型のように使うことが可能な機能が存在しているので、それについても紹介する予定です。

執筆者紹介

伊藤裕一(ITO Yuichi)

シスコシステムズでの業務と大学での研究活動でコンピュータネットワークに6年関わる。専門はL2/L3 Switching とデータセンター関連技術およびSDN。TACとしてシスコ顧客のテクニカルサポート業務に従事。社内向けのソフトウェア関連のトレーニングおよびデータセンタとSDN関係の外部講演なども行う。

もともと仮想ネットワーク関連技術の研究開発に従事していたこともあり、ネットワークだけでなくプログラミングやLinux関連技術にも精通。Cisco社内外向けのトラブルシューティングツールの開発や、趣味で音声合成処理のアプリケーションやサービスを開発。

Cisco CCIE R&S, Red Hat Certified Engineer, Oracle Java Gold,2009年度 IPA 未踏プロジェクト採択

詳細(英語)はこちら