CodeIQ:関数で等差数列の作成。

私自身が表題の問題を解いた時のプログラムについて解説します。
問題の詳細は「関数で等差数列の作成。」(CodeIQ)を参照してください。

問題の概要

問題を引用します。
任意の等差 n の数列の配列を、気軽に作成できる以下のような関数があるとします。
def arithmetic_progression():
    return [lambda x: i * x for i in range(1, 10)]

ところが、この関数を以下のように実際に使ってみると、おかしなことになってしまいます。
for funcs in arithmetic_progression():
    print(funcs(2), end=', ')
print()

for funcs in arithmetic_progression():
    print(funcs(10), end=', ')
print()

具体的には、以下のように出力結果になり、等差 2、10 がありません。

18, 18, 18, 18, 18, 18, 18, 18, 18,
90, 90, 90, 90, 90, 90, 90, 90, 90,

望んだ出力結果、

2, 4, 6, 8, 10, 12, 14, 16, 18, 20,
10, 20, 30, 40, 50, 60, 70, 80, 90, 100,

が得られるように、下記コードの「穴埋め」という部分を書き換えてください。
さらに、標準入力を初項、等差とし、項数 10 の等差数列を上記の数列に続けて出力するコードを追加してください。

【穴埋め用コード】
def arithmetic_progression():
    return [ 穴埋め for i in range(1, 11)]

for func in arithmetic_progression():
    print(func(2), end=', ')
print()

for func in arithmetic_progression():
    print(func(10), end=', ')
print()

diff = int(input())
for func in arithmetic_progression():
    print(func(int(diff)), end=', ')
print()

私のプログラム

Pythonで解答しています。

#!/usr/local/bin/python3

def arithmetic_progression():
    return [lambda x, i=i: i * x for i in range(1, 11)]

for func in arithmetic_progression():
    print(func(2), end=', ')
print()

for func in arithmetic_progression():
    print(func(10), end=', ')
print()

diff = int(input())
for func in arithmetic_progression():
    print(func(int(diff)), end=', ')
print()

解説

穴埋め問題なので記事にするかどうか迷いましたが記事にします。

考え方

要するに変数iの評価タイミングの問題です。JavaScriptでも同じような問題が発生します。
答えはarithmetic_progression()の返り値のlambda式に引数を一つ増やし、それにデフォルト値iを与えるということになります。これでリスト内包表記が解決されるタイミングで必要なだけデフォルト値が与えられるので正しく動きます。

雑感

この記事の本体は雑感です。
普通、こういう時はジェネレータを使うんでは?

#!/usr/local/bin/python3

def arithmetic_progression():
	for i in range(1, 11):
		yield lambda x: x * i

for func in arithmetic_progression():
	print(func(2), end=', ')
print()

for func in arithmetic_progression():
	print(func(10), end=', ')
print()

diff = int(input())
for func in arithmetic_progression():
	print(func(int(diff)), end=', ')
print()
こっちの方がわかりやすいし普通だと思うんだけど。