練習問題の解答

解答:ランダムなリスト

import random

def randlist(size):
  lst = []
  for i in range(size):
    lst.append(random.randint(0, 100))
  return lst

リスト内包表記を使った解答:

import random

def randlist(size):
  return [random.randint(0, 100) for i in range(size)]

ramdom(en, ja) モジュールの randint 関数を使うとランダムな整数を得ることが出来ます。

random.randint(a, b)

とすれば、a <= N <= b であるようなランダムな整数 N が返されます。

>>> import random
>>> for i in range(30):
...   print random.randint(0, 10),
...
5 3 8 8 5 3 6 2 3 10 7 6 6 8 0 0 0 5 1 1 9 9 8 6 4 10 2 0 2 5

解答:ランダムなリスト(範囲指定)

import random

def randlist(size, lower=0, upper=100):
  lst = []
  for i in range(size):
    lst.append(random.randint(lower, upper))
  return lst

リスト内包表記を使った解答:

import random

def randlist(size, lower=0, upper=100):
  return [random.randint(lower, upper) for i in range(size)]

解答:比較関数を関数に渡してソート

def qsort(fn, lst):
  if lst == []:
    return lst
  else:
    def partition(x, lst):
      a, b = [], []
      for i in lst:
        if fn(i, x):
          a.append(i)
        else:
          b.append(i)
      return a, b
    xs, ys = partition(lst[0], lst[1:])
    return qsort(fn, xs) + [lst[0]] + qsort(fn, ys)

def slencmp(x, y):
  return len(x) < len(y)

補足:リストにも sort メソッドがあります。

>>> lst = ["short", "double", "int", "long"]
>>> lst.sort()
>>> lst
['double', 'int', 'long', 'short']

sort メソッドには比較関数を渡す事が出来ます。

>>> help(list)
|
|  sort(...)
|      L.sort(cmpfunc=None) -- stable sort *IN PLACE*; cmpfunc(x, y) -> -1, 0, 1
| 

文字列の長さで比較する lencmp 関数を以下のように作ります。

def lencmp(x, y):
  a, b = len(x), len(y)
  if a == b:
    return 0
  elif a < b:
    return -1
  else:
    return 1

lencmp を sort メソッドに渡すと、文字列の長さで比較が行われるようになります。

>>> lst = ["short", "double", "int", "long"]
>>> lst.sort(lencmp)
>>> lst
['int', 'long', 'short', 'double']

解答:2つのファイルを並列に表示

コマンドラインで指定されたファイルを読み込み、改行でスプリットします。左側に表示されるファイルに対して、ファイルの各行のうち最も長い行の長さを width に代入します。この width の値を引数にして各行の文字列に対し ljust メソッドを呼び出して、各行の長さが等しくなるようにしています。

def par(filename1, filename2):
  file1 = open(filename1)
  file2 = open(filename2)
  
  ls = file1.read().split("\n")
  width = max(map(len, ls))
  ls = [s.ljust(width) for s in ls]

  output(ls, width, file2.read().split("\n"))

  file1.close()
  file2.close()

def output(lls, lwidth, rls):
  llen, rlen = len(lls), len(rls)
  s = " " * lwidth

  for i in range(max(llen, rlen)):
    if i < llen:
      print lls[i],
      if i < rlen:
        print "| %s" % rls[i]
      else:
        print "|"
    else:
      print "%s | %s" % (s, rls[i])

if __name__ == "__main__":
  import sys
  if len(sys.argv) != 3:
    print "usage: python par.py file1 file2"
  else:
    par(sys.argv[1], sys.argv[2])

解答:ファイルの拡張子変更

import sys, glob

if len(sys.argv) != 3:
  print "usage: python rename.py ext new_ext"
else:
  ext, new_ext = sys.argv[1], sys.argv[2]
  length = len(ext)
  for filename in glob.glob("*" + ext):
    new_filename = filename[:-length] + new_ext
    print filename, "=>", new_filename

glob(en, ja) モジュールの glob 関数にファイル名のパターンを指定すると、それにマッチするファイルのリストが返されます。

>>> import glob
>>> glob.glob("*")
['sugar.txt', 'salt.txt', 'pepper.txt', 'prog.py', 'foo.py', 'rename.py', 'main.c']
>>> glob.glob("*.txt")
['sugar.txt', 'salt.txt', 'pepper.txt']
>>> glob.glob("*.c")
['main.c']

glob 関数で指定された拡張子のファイルのリストを得ます。それからファイル名の拡張子部分を取り去り、指定された新しい拡張子をそれに連結しています。

また、実際にファイル名の変更を行うには、os(en, ja) モジュールの rename 関数を使います。

補足:glob のほかに文字列メソッドの endswith を使うのもいいかもしれません。

>>> print str.endswith.__doc__
S.endswith(suffix[, start[, end]]) -> bool

Return True if S ends with the specified suffix, False otherwise.
With optional start, test S beginning at that position.
With optional end, stop comparing S at that position.
>>> ls = ["sugar.txt", "salt.txt", "pepper.c"]
>>> [x for x in ls if x.endswith(".txt")]
['sugar.txt', 'salt.txt']
>>> [x for x in ls if x.endswith(".c")]
['pepper.c']

解答:mapconcat

def mapconcat(fn, seq, sep):
  return sep.join(map(fn, seq))

文字列メソッドに join があります。join メソッドは、

s.join(sequence)

とあるとき、引数の sequence をつなげた文字列を返します。そのとき、各要素のセパレータは s となります。

>>> "-".join(["foo", "bar", "baz"])
'foo-bar-baz'

map 関数は引数として 1 つの関数と 1 つ以上のシーケンスを受け取ります。この関数はシーケンスの各要素に関数を適用した結果のリストを返します。

>>> map(lambda x: x+10, [1, 2, 3])
[11, 12, 13]
>>> map(lambda x,y: x+y, [1, 2, 3], [4, 5, 6])
[5, 6, 7]

なお、上の mapconcat 関数では map の代わりにリスト内容表記を使うことも出来ます。

def mapconcat(fn, seq, sep):
  return sep.join([fn(e) for e in seq])

※mapconcat は、Emacs Lisp の mapconcat 関数を参考にしました。

解答:filterの自作

問題1

 def myfilter(fn, ls):
   ret = []
   for e in ls:
     if fn(e):
       ret.append(e)
   return ret

 # リスト内包表記を使った例
 def myfilter(fn, ls):
   return [e for e in ls if fn(e)]

問題2

 def partition(fn, ls):
   a, b = [], []
   for e in ls:
     if fn(e):
       a.append(e)
     else:
       b.append(e)
   return a, b

問題3

>>> ls = [("Beth", 43), ("Kathy", 80), ("Mark", 56), ("Mary", 70), ("Susie", 68)]
>>> partition(lambda x: x[1] >= 60, ls)
([('Kathy', 80), ('Mary', 70), ('Susie', 68)], [('Beth', 43), ('Mark', 56)])

解答:入れ子のリストを平らにする

def flatten(ls):
  def rec(xs, r):
    for e in xs:
      if type(e) is list:
        rec(e, r)
      else:
        r.append(e)
    return r

  return rec(ls, [])

あるいは

def flatten(ls):
  r = []
  for e in ls:
    if type(e) is list:
      r.extend(flatten(e))
    else:
      r.append(e)
  return r

解答:rangeの返す数列を生み出すジェネレータ

def grange(a, b=None, step=1):
  if b is None:
    cur  = 0
    stop = a
  else:
    cur  = a
    stop = b

  assert step != 0
  if step > 0:
    while cur < stop:
      yield cur
      cur += step
  else:
    while cur > stop:
      yield cur
      cur += step

Python ページ