Remrinのpython攻略日記

python3に入門しました。python3についてあれこれとサンプルコードとか。

NumPyの使い方(9) 並べ替えsortなど

NumPyのsortについて。
 

sort()メソッド 内部で配列をソート。返り値はNone
np.sort()関数 ソート済み配列を返す。元の配列は保存。

 

# coding: utf-8
import numpy as np

np.random.seed(1)
a1 = np.random.randint(0, 100, 10)
print(a1)  # [37 12 72  9 75  5 79 64 16  1]

a1.sort()
print(a1)        # [ 1  5  9 12 16 37 64 72 75 79]

print(a1.sort()) # None

np.random.seed(1)
a1 = np.random.randint(0, 100, 10)
print(a1)  # [37 12 72  9 75  5 79 64 16  1]

a2 = np.sort(a1)
print(a1)  # [37 12 72  9 75  5 79 64 16  1] 元配列は保存
print(a2)  # [ 1  5  9 12 16 37 64 72 75 79]

 

axisオプション

 どのaxisでソートするかのオプション
 列内でのソートはaxis=0とするか、あるいは単に0
 行内でのソートはaxis=1とするか、あるいは単に1
 デフォルトは-1で、最後の軸(2次元ならaxis=1)でソート
 axisをNoneとすると1次元配列に平坦化。

np.random.seed(1)
a3 = np.random.randint(0, 100, (3, 6))
print(a3)
# [[37 12 72  9 75  5]
#  [79 64 16  1 76 71]
#  [ 6 25 50 20 18 84]]

# axis=0
print(np.sort(a3, 0))
# [[ 6 12 16  1 18  5]
#  [37 25 50  9 75 71]
#  [79 64 72 20 76 84]]

# axis=1
print(np.sort(a3, 1))
# [[ 5  9 12 37 72 75]
#  [ 1 16 64 71 76 79]
#  [ 6 18 20 25 50 84]]

# axis=-1
print(np.sort(a3, axis=-1))
# [[ 5  9 12 37 72 75]
#  [ 1 16 64 71 76 79]
#  [ 6 18 20 25 50 84]]

# axisのデフォルトは-1
print(np.sort(a3))
# [[ 5  9 12 37 72 75]
#  [ 1 16 64 71 76 79]
#  [ 6 18 20 25 50 84]]

# axis=None
print(np.sort(a3, axis=None))
# [ 1  5  6 ..., 76 79 84]

 

kindオプション

 ソートに利用するアルゴリズムを選ぶ。デフォルトは'quicksort'

kind speed worst case stable
'quicksort' 1 O(n^2) ×
'mergesort' 2 O(n*log(n)
'heapsort' 3 O(n*log(n) ×

stable:等しい要素の並び順を保存
 

orderオプション

フィールドをもつ要素のばあい、キーとするフィールドを指定できる。

dtype = [("name", 'U10'), ("height", int), ("age", int)]
values = [("香川", 165, 48), ("秋山", 175, 32), ("長田", 170, 27)]
a1 = np.array(values, dtype=dtype)
print(a1)
# [('香川', 165, 48) ('秋山', 175, 32) ('長田', 170, 27)]

print(a1.dtype)
# [('name', '<U10'), ('height', '<i4'), ('age', '<i4')]

print(np.sort(a1, order="age"))
# [('長田', 170, 27) ('秋山', 175, 32) ('香川', 165, 48)]

 

逆順ソート

降順配列にするには

array[::-1]    行を逆順にする
array[:, ::-1] 列を逆順にする

などのスライスを使う。
 

np.random.seed(1)
a3 = np.random.randint(0, 100, (3, 6))
print(a3)
# [[37 12 72  9 75  5]
#  [79 64 16  1 76 71]
#  [ 6 25 50 20 18 84]]

# axis=0の逆順ソート
print(np.sort(a3, 0)[::-1])
# [[79 64 72 20 76 84]
#  [37 25 50  9 75 71]
#  [ 6 12 16  1 18  5]]

# axis=1の逆順ソート
print(np.sort(a3, 1)[:, ::-1])
# [[75 72 37 12  9  5]
#  [79 76 71 64 16  1]
#  [84 50 25 20 18  6]]

argsort()

argsort()はソートした場合のindex番号の配列を取得する。

1次元配列の場合

a1 = np.array([37, 12, 72, 9, 75, 5])

# argsort()...ソートした場合のindex番号を配列にする
indexer = a1.argsort()
print(indexer)
# [5 3 1 0 2 4]   a1[5] < a1[3] < a1[1] < a1[0] ...

print(a1[indexer])
# [ 5  9 12 37 72 75]

 
2次元配列の場合は、argsort()でキーとする行の指定ができる。

np.random.seed(1)
a1 = np.random.randint(0, 100, (3, 6))
print(a1)
# [[37 12 72  9 75  5]
#  [79 64 16  1 76 71]
#  [ 6 25 50 20 18 84]]

#0行目をキーとしてソート
print(a1[:, a1[0].argsort()])
# [[ 5  9 12 37 72 75]
#  [71  1 64 79 16 76]
#  [84 20 25  6 50 18]]

#1行目をキーとしてソート
print(a1[:, a1[1].argsort()])
# [[ 9 72 12  5 75 37]
#  [ 1 16 64 71 76 79]
#  [20 50 25 84 18  6]]

 
argsortにもaxis, kind, orderオプションがある。
 

lexsort()

複数のキーで間接ソートするときに使う。
lexsort()もargsort()同様にインデックスの配列を得る。

last_name = ["はつね", "さとう", "しまだ", "さとう"]
first_name = ["みく", "めぐみ","なおみ", "かな"] 

indexer = np.lexsort((first_name, last_name))
print(indexer) # [3 1 2 0 ]

name =[(last_name[i], first_name[i]) for i in indexer]
print(name)
# [('さとう', 'かな'), ('さとう', 'めぐみ'), ('しまだ', 'なおみ'), ('はつね', 'みく')]

 

searchsorted()

ソートされた配列内で二分探索をし、挿入される位置を返す。

a1 = np.array([0, 5, 10, 15, 20])
print(a1.searchsorted(7))          # 2
print(a1.searchsorted(18))         # 4
print(a1.searchsorted(5))          # 1

print(a1.searchsorted([7, 18, 5])) # [2 4 1]

 
sideオプション:同じ値が複数あるとき、
'left' もっとも左のインデックス。(デフォルト)
'right' もっとも右のインデックス

a1 = np.array([1, 1, 1, 2, 2, 2])
print(a1.searchsorted([1, 2]))                # [0 3]
print(a1.searchsorted([1, 2], side="right"))  # [3 6]

 

np.unique()

1次元の配列から重複を取り除き、ソートした配列を返す。
python標準のsorted(set(seq))と同様の働き。

a1 = np.array([1, 3, 2, 0, 1, 2, 2, 1])
print(np.unique(a1))  # [0 1 2 3]

 
return_index=Trueとすると初回のインデックスを返す。
return_counts=Trueとすると、重複している回数を返す。

print(np.unique(a1, return_index=True))
# (array([0, 1, 2, 3]), array([3, 0, 2, 1], dtype=int32))

print(np.unique(a1, return_counts=True))
# (array([0, 1, 2, 3]), array([1, 3, 3, 1]))

 

NumPyの使い方(8) 真偽値

NumPyの真偽値について。
 

・TrueとFalse
 python標準のTrue, Falseでは

False 数値のゼロ、空文字列、空配列、False
True 他すべて

 
TrueがFalse以外ということは、
負の値、NaN、infもTrue
となる。

・条件がTrueとなる要素の確認

import numpy as np

a1 = np.arange(8).reshape(2, 4)
print(a1)
# [[0 1 2 3]
#  [4 5 6 7]]

# 2の倍数かどうか(1)
print(a1 % 2 == 0)
# [[ True False  True False]
#  [ True False  True False]]

# 2の倍数かどうか(2) 
print(np.mod(a1, 2) == 0)
# [[ True False  True False]
#  [ True False  True False]]

# 2の倍数の個数
print((a1 % 2 == 0).sum()) #4

a1 = np.arange(20)

print(a1 > 10)
# [False False False ...,  True  True  True]

# 条件がTrueとなる要素の個数
print((a1 > 10).sum())  # 9

# 条件がTrueとなる値の和
print(a1[a1 >10].sum()) # 135

a2 = np.array([1, 2, 3, 4])
a3 = np.array([1, 1, 4, 4])
print(a2 == a3)  # [ True False False  True]

 

anyとall

any 1つでもTrueだとTrueを返す。
all すべてがTrueのときのみTrueを返す。

a1 = np.array([[0, 0, 0], [1, 1, 1], [1, 1, 1]])
print(a1)
# [[0 0 0]
#  [1 1 1]
#  [1 1 1]]

print(a1.any()) # True
print(a1.all()) # False

 
any(), all()で軸を指定できる。次元削減になる。
axis=0 列方向に次元削減
axis=1 行方向に次元削減
f:id:rare_Remrin:20170512120642p:plain

print(a1.all(axis=0)) # [False False False]
print(a1.all(axis=1)) # [False  True  True]
print(a1.any(axis=0)) # [ True  True  True]
print(a1.any(axis=1)) # [False  True  True]

 

boolと条件

真偽値boolや条件などについて。
 

真偽値bool

・True, Falseの2つの値のみ。
・True,=1, False=0で変更できない。(python2では変更できた)

True = 0  # SyntaxError: can't assign to keyword

 

False 数値のゼロ、空文字列、空配列、False
True 他すべて

・必要になったら、True=1, False=0の数値として扱われる。

print(True)             # True
print(True + True + 1)  # 3

 
・条件分岐や繰り返しに使える。

x = 5
if x > 0:
    print(x > 0) # True
    
if True:
    print(True)  # True
    
if 1:
    print(x)     # 5

if 2:
    print(x)     # 5

if -1:
    print(x)     # 5

 

# and, orは短絡 (short-circuit)演算子
# 左から右へ評価し、結果が確定した時点で評価を止める。
# 戻り値は最後に評価された引数

# 最後の引数3を評価するまで確定しない。
print(1 and 2 and 3)  # 3 

# (1 and 0 and) の時点で評価がFalse(=0)と確定するので3は評価されない。
print(1 and 0 and 3)           # 0

# (0 and)の時点で評価がFalseと確定     
print(0 and 1 and 3)           # 0

# 最後の(3/0)は評価されない     
print(1 and 0 and (3/0))       # 0

print(0 or 1 or 2 or 3)        # 1
print(0 or 0 or 10 or 20)      # 10     
     
string1, string2, string3 = '', 'Trondheim', 'Hammer Dance'
non_null = string1 or string2 or string3
print(non_null)      # 'Trondheim'

 
・3項演算子のような書き方ができる。

print(1==1 and "T" or "F") # T
print(1==0 and "T" or "F") # F

 
参考:
pythonドキュメント 条件に付いてもう少し

NumPyの使い方(7) 統計関数、最大・最小

NumPyの統計関数の使い方

 
ndarray配列全体、あるいは特定の軸についての統計処理をする。
sumやmeanなどの集計処理は次元削減になる。
axis=0 縦方向にreduce
axis=1 横方向にredece
  

sum 和。 長さ0の配列では0を返す。
mean 平均。長さ0の配列ではNaNを返す。
var 分散
std 標準偏差
min 最小値, aminも同じ
max 最大値, amaxも同じ
nanmin NaNを無視した最小値
nanmax NaNを無視した最大値
minimun 2つの配列を比較して小さい要素、NaNを優先
maximun 2つの配列を比較して大きい要素、NaNを優先
fmin 2つの配列を比較して小さい要素、NaNを無視
fmax 2つの配列を比較して小さい要素、NaNを無視
argmin 最小値のインデックス
argmax 最大値のインデックス
cumsum 累積和
cumprod 累積積

maximumやfmaxでは予めNaNを処理したほうがよさそう。
 

import numpy as np

a1 = np.array([[1, 2], [3, 4]])
a2 = np.array([[0, 5], [2, 6]])
a3 = np.array([[0, 5], [2, np.nan]])

print(a1.max(axis=0))     # [3 4]
print(a1.max(axis=1))     # [2 4]
print(a1.max())           # 4
print(a1.argmax())        # 3  : 1次元配列にし、3番要素が最大値
print(a3.max())           # nan
print(np.nanmax(a3))      # 5.0
     
print(np.maximum(a1, a2)) # [[1 5]
                          #  [3 6]]
print(np.maximum(a1, a3)) # [[1 5]
                          #  [3 nan]]  RuntimeWarning: invalid value encountered in maximum
print(np.fmax(a1, a3))    # [[ 1.  5.]
                          # [ 3.  4.]] RuntimeWarning: invalid value encountered in fmax     

 

mean(), sum()

a1 = np.arange(12).reshape(3, 4)
print(a1)
# [[ 0  1  2  3]
#  [ 4  5  6  7]
#  [ 8  9 10 11]]

# mean
print(a1.mean())   # 4.0  メソッド
print(np.mean(a1)) # 4.0  関数

# axisを指定すると1次元低い配列を取得     
print(np.mean(a1, 0))      # [ 4.  5.  6.  7.]
print(np.mean(a1, axis=0)) # [ 4.  5.  6.  7.]
print(np.mean(a1, 1))      # [ 1.5  5.5  9.5]
print(np.mean(a1, axis=1)) # [ 1.5  5.5  9.5]
#print(np.mean(a1, 2))     IndexError: tuple index out of range

print(a1.sum())   # 66
print(np.sum(a1)) # 66
print(a1.sum(0))  # [12 15 18 21]
print(a1.sum(1))  # [ 6 22 38]

 

累積和cumsum(), 累積積cumprod()

a2 = np.ones((3, 4), dtype=int)
a2*= 2
print(a2)
# [[2 2 2 2]
#  [2 2 2 2]
#  [2 2 2 2]]

print(np.cumsum(a2, 0))
# [[2 2 2 2]
#  [4 4 4 4]
#  [6 6 6 6]]

print(np.cumsum(a2, 1))
# [[2 4 6 8]
#  [2 4 6 8]
#  [2 4 6 8]]

print(np.cumprod(a2, 0))
# [[2 2 2 2]
#  [4 4 4 4]
#  [8 8 8 8]]

print(np.cumprod(a2, 1))
# [[ 2  4  8 16]
#  [ 2  4  8 16]
#  [ 2  4  8 16]]

print(np.cumsum(a2))
# [ 2  4  6 ..., 20 22 24]

 

NumPyの使い方(6) 条件制御

NumPyの条件制御について。
 
if~elseで条件分岐をするとき

a = 5
if a >= 0:
    b = 1
else:
    b = -1

と書けますが、これを3項演算子を使い以下のようにも書けます。

b = 1 if a >= 0 else -1

 
似たような動作を組み込みのリスト型で行うとき、
例えば、Trueならlist1から、そうでなければlist2から要素を選ぶとき、

list1 = [1, 2, 3, 4, 5]
list2 = [11, 12, 13, 14, 15]
condi = [True, False, True, False, True]

list3 = [x if c else y for (c, x, y) in zip(condi, list1, list2)]
print(list3)   # [1, 12, 3, 14, 5]

 
NumPyではnp.where()を使って
np.where(条件, x, y)のように書きます。
x,yは配列または数値。数値ならブロードキャストされる。

a3 = np.where(condi, list1, list2)
print(a3)     # [ 1 12  3 14  5]

 
他の例も

a1 = np.arange(9).reshape(3, 3)
print(a1)
# [[0 1 2]
#  [3 4 5]
#  [6 7 8]]

# 3未満の要素を0で置き換える(1)
print(np.where(a1 < 3, 0, a1))
# [[0 0 0]
#  [3 4 5]
#  [6 7 8]]

# 3未満の要素を0で置き換える(2)
print(np.where(a1 >= 3, a1, 0))
# [[0 0 0]
#  [3 4 5]
#  [6 7 8]]

# 正なら1、正でないなら-1にする
print(np.where(a1 > 0, 1, -1))
# [[-1  1  1]
#  [ 1  1  1]
#  [ 1  1  1]]

 
np.where()の引数を条件の配列だけにすると、
Trueとなる要素のインデックスを取得。

a1 = np.arange(9).reshape(3, 3)
print(a1)
# [[0 1 2]
#  [3 4 5]
#  [6 7 8]]

# Trueとなる要素のインデックスを取得
print(np.where(a1 > 5))
# (array([2, 2, 2], dtype=int32), array([0, 1, 2], dtype=int32))

# そのインデックスを利用して、要素を取り出せる。
print(a1[np.where(a1 > 5)])
# [6 7 8]

# Trueとなる要素のインデックスを取得
print(np.where(a1 > 2))
# (array([1, 1, 1, 2, 2, 2], dtype=int32), array([0, 1, 2, 0, 1, 2], dtype=int32))

# そのインデックスを利用して、要素を取り出せる。
print(a1[np.where(a1 > 2)])
# [3 4 5 6 7 8]

# 値の検索
target = [3, 4, 7]
ix = np.in1d(a1.ravel(), target).reshape(a1.shape)
print(ix)
# [[False False False]
#  [ True  True False]
#  [False  True False]]

# そのインデックスを表示
print(np.where(ix))
# (array([1, 1, 2], dtype=int32), array([0, 1, 1], dtype=int32))

 

2つの条件を同時に判断

cond1がTrueでcond2もTrue 3
cond1がTrueでcond2はFalse 2
cond1がFalseでcond2はTrue 1
cond1がFalseでcond2もFalse 0

としたい場合は、python標準だとif, elifなどを組み合わせて書けるが、
np.whereを3回ネストしても書ける

cond1 = np.array([True, True, False, False])
cond2 = np.array([True, False, True, False])
result = np.where(cond1 & cond2, 3,
                  np.where(cond1, 2,
                           np.where(cond2, 1, 0)))
print(result)   # [3 2 1 0]

True=1, False=0であることを使い次のようにも書ける。

result = cond1 * 2 + cond2
print(result)   # [3 2 1 0]

NumPyの使い方(5) ベクトル演算とユニバーサル関数

ベクトル演算とユニバーサル関数について。

ベクトル演算

 ndarrayと数値(スカラー)の計算は全要素対象で、forループを使わない。
 ndarrayどうしの演算は対応する位置の要素どうしで計算。
 [注意]ndarray * ndarrayは内積外積にはならない。

import numpy as np

a1 = np.arange(6).reshape(2, 3)
print(a1)
# [[0 1 2]
#  [3 4 5]

print(a1 + 1)
# [[1 2 3]
#  [4 5 6]

print(a1 - 1)
# [[-1  0  1]
#  [ 2  3  4]

print(a1 * 2)
# [[ 0  2  4]
#  [ 6  8 10]

print(a1 / 10)
# [[ 0.   0.1  0.2]
#  [ 0.3  0.4  0.5]

print(a1 ** 2)
# [[ 0  1  4]
#  [ 9 16 25]

print(a1 +a1)
# [[ 0  2  4]
#  [ 6  8 10]]

print(a1 * a1)
# [[ 0  1  4]
#  [ 9 16 25]]

 

ユニバーサル関数(ufunc, universal function)

 ベクトル演算と同様に、要素ごとに計算される。
 引数となる配列が1つのとき「単項ufunc」、2つのとき「2項ufunc」

例:

a1 = np.arange(6)
a2 = np.arange(0, 60, 10)

# 単項ユニバーサル関数の例
print(np.square(a1))
# [ 0  1  4  9 16 25]

# 2項ユニバーサル関数の例
print(np.add(a1, a2))
# [ 0 11 22 33 44 55]

 
単項ufunc

abs 整数、小数、複素数の絶対値
fabs 整数、小数の絶対値。高速
sqrt 平方根。 array**0.5と同等
square 2乗。 array**2と同等
exp eを底としたべき乗
log 自然対数。底がe
log10 常用対数。底が10
log2 底が2の対数
log1p 1を加えてからの自然対数
sign 要素の符号を返す。正:+1, 負:-1, ゼロ:0
ceil 切り上げ
floor 切り捨て
rint dtypeそのままで、四捨五入
modf 整数部分と小数部分の2つの配列を返す
isnan NaNかどうかの真偽値
isfinite True:Nanでもinfでもない。False:NaNかinf
isinf infかどうか。
sin 三角関数
cos  
tan  
sinh 双曲線関数
cosh  
tanh  
arcsin 三角関数
arcsos  
arctan  
arcsinh 双曲線関数
arccosh  
arctanh  
logical_not 論理否定。-arrayと同等

 
2項unfnc

add
subtract
multiply
divide
floor_divide 商を左方向へ丸める。//
power 累乗
maximum 最大値。NaNがあるときはNaNを選ぶ
minimum 最小値。NaNがあるときはNaNを選ぶ
fmax 最大値。NaNがあるときはNaNではない方
fmin 最小値。NaNがあるときはNaNではない方
mod 剰余
copysign 1つめの配列の絶対値を2つめの配列の符号に。
greater >
greater_equal >=
less
less_equal <=
equal ==
not_equal !=
logical_and & 論理和
logical_or 論理積
logical_xor ^ 排他

 
・np.vectorize()を使って自作の関数をufuncにできる。

def myfunc(x, y):
    return x + y
vfunc = np.vectorize(myfunc)

print(vfunc(np.arange(5), 10))
# [10 11 12 13 14]

 

Project Euler

Project Euler problem #001 Multiples of 3 and 5

 
If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23.
Find the sum of all the multiples of 3 or 5 below 1000.
 
問題1:1000未満の3または5の倍数の和を求めよ。
 
forループを使っていくのが自然かな。

# forループとif
limit = 1000
result = 0
for i in range(limit):
    if i%3 == 0 or i%5 ==0:
        result += i
print(result)

 
ifの部分は3項演算子でも。

# forループと3項演算子
limit = 1000
result = 0
for i in range(limit):
    result += i if i%5 == 0 or i%3 ==0 else 0
print(result)

 
リスト内包表記を使うとシンプルに。

# リスト内包表記
limit = 1000
result = sum([x for x in range(limit) if x%3 == 0 or x%5 ==0 ])
print(result)

 
3つの等差数列に分けても。

# 等差数列のsum()
limit = 1000
result = sum(range(0, limit, 3)) + sum(range(0, limit, 5)) - sum(range(0, limit, 15))
print(result)

 
等差数列の和の公式を使うと、コードは複雑になるけど処理時間は大幅短縮。

# 等差数列の和の公式
limit = 1000
def sum_multi(x):
    first = x
    last = (limit - 1) - (limit - 1) % x 
    return int((first + last) * (last / x) / 2)
result = sum_multi(3) + sum_multi(5) - sum_multi(15)
print(result)

 

Project Euler problem #002 Even Fibonacci numbers

 
Each new term in the Fibonacci sequence is generated by adding the previous two terms. By starting with 1 and 2, the first 10 terms will be:
1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ...
By considering the terms in the Fibonacci sequence whose values do not exceed four million, find the sum of the even-valued terms.
 
問題2:フィボナッチ数列で400万以下の偶数の項の和を求めよ。
 

def fib4():
    a, b = 0, 1
    result = 0
    while b < 4*10**6:
        result += b if b % 2 ==0 else 0
        a, b = b, a + b
    return result

print(fib4())
# 4613732

 
フィボナッチ数列の偶数項は3項ごとに登場。
それと、フィボナッチ数列の生成をlambda式で1行にまとめてみると、

f = lambda x:int(((1 + 5**0.5) / 2)**i / 5**0.5 + 0.5)
i, result = 3, 0
while f(i) < 4*10**6:
    result += f(i)
    i += 3
print(result)

 

Project Euler problem #003 Largest prime factor

The prime factors of 13195 are 5, 7, 13 and 29.
What is the largest prime factor of the number 600851475143 ?
 
問題3:600851475143の素因数のうち最大のものを求めよ。
 

# 素数のジェネレータ
def gen2(start=2, stop=1000000):
    pr = max(1, start - 1)
    while True:
        while pr < stop:
            pr += 1
            if all(pr%x != 0 for x in range(2, int(pr**0.5) + 1)):
                break
        yield pr

# 素因数分解
def factorial(n):
    if n in [0, 1]:
        return [n]
    result = []
    stop = int(n**0.5) + 1
    g = gen2()
    pr = next(g)
    while pr < stop:
        if n % pr == 0:
            result.append(pr)
            n //= pr
            continue
        pr = next(g)
    if n > 1:
        result.append(n)
    return result

print(max(factorial(600851475143)))
# 6857

 

Project Euler problem #004 Largest palindrome product

 
A palindromic number reads the same both ways. The largest palindrome made from the product of two 2-digit numbers is 9009 = 91 × 99.
Find the largest palindrome made from the product of two 3-digit numbers.
 
問題:3桁の2つの数の積で作った回文のうち最大のものを求めよ。
 
forループを2重にして回文判定するとよさそう。

result = 0
for i in range(100, 1000):
    for j in range(i, 1000):
        if str(i * j) == str(i * j)[::-1]:
            result = max(result, i * j)
print(result)
# 906609

 
端が9と仮定すると、奇数×奇数なので、forループは奇数で。 

result = 0
for i in range(901, 1000, 2):
    for j in range(i, 1000, 2):
        if str(i * j) == str(i * j)[::-1]:
            result = max(result, i * j)
print(result)

 
積の1の位が9になるのは1×9、3×3、7×7、9×1なので、さらにループを減らせそう。

result = 0
pair9 = {1:8, 3:0, 7:0, 9:2}
for i in range(901, 1000, 2):
    if i%10 in pair9:                  # 1の位が1,3, 7, 9なら
        incre = pair9[i%10]            # 相手の1の位が決まる
    else:
        continue
    for j in range(i+incre, 1000, 10): # 1の位は固定なのでstep=10
        if str(i * j) == str(i * j)[::-1]:
            result = max(result, i * j)
print(result)

Archived Problems - Project Euler