2012年8月26日日曜日

Project Euler - Problem 53

Project Euler(プロジェクト オイラー)の問題をpythonでやってみます。

出典: Project Euler(日本語 翻訳サイト)
   ProjectEuler.net(英語サイト)

Problem 53
12345から3つ選ぶ選び方は10通りである.
123, 124, 125, 134, 135, 145, 234, 235, 245, 345.
組み合わせでは, 以下の記法を用いてこのことを表す: 5C3 = 10.

一般に, r≦n についてnCr = n!/(r!(n-r)!) である.
ここで, n! = n×(n-1)×...×3×2×1, 0! = 1と階乗を定義する.

n = 23になるまで, これらの値が100万を超えることはない:
23C10 = 1144066.
1≦n≦100について, 100万を超えるnCrは何通りか?

-----


私の解答例は以下です。畳んでいます。
def h(n):
	if n<=1: return 1
	else: return h(n-1)*n

def g(n, m): return h(n)//h(n-m)//h(m)

def f(n, m):
	s = 0
	for i in xrange(1, n+1):
		for j in xrange(1, i+1):
			if g(i, j)>m: s += 1
	return s

print f(100, 1000000)


1.関数h(n)
・nの階乗を返します。problem20と同じです。


2.関数g(n, r)
・順列nCrの値を返します。problem15と同じです。


3.関数f(m):
・順列nC*のすべての組み合わせ値のうちmを超える組の件数を返します。


・s = 0
 sは件数カウンターです。0クリアしておきます。


・for i in xrange(1, n+1):
 ループ関数iは順列nCr、n個のうちr個選ぶ組み合わせのnです。


・for j in xrange(1, i+1):
 ループ関数jは順列nCr、n個のうちr個選ぶ組み合わせのrです。


・if g(i, j)>m: s += 1
 関数g(i,j)で順列値を求めその値がmを超える場合、件数カウンターsをカウントアップします。


・return s
 iとjのループが終わったら、件数カウンターsを返します。

4.関数の外
 関数f(100,1000000)を呼び出し、その戻り値を出力します。


解答はこのすぐ下の行です。文字の色を白にしてます。選択状態にすると見えます。
4075

Project Euler - Problem 52

Project Euler(プロジェクト オイラー)の問題をpythonでやってみます。

出典: Project Euler(日本語 翻訳サイト)
   ProjectEuler.net(英語サイト)

Problem 52
125874を2倍すると251748となる. これは元の数125874と同じ数を含む.

2x, 3x, 4x, 5x, 6xがxと同じ数を含むような最小の正整数xを求めよ.

-----

私の解答例は以下です。畳んでいます。
def f():
	for i in xrange(1, 10000000):
		s = set(str(i))
		for j in xrange(2, 7):
			t = set(str(i*j))
			if s!=t: break
		else: return i

print f()


1.関数f(n)
・for i in xrange(1, 10000000):
 iは1からの連番です。10000000は、とりあえずの終了値です。
 結果的に6桁で問題に合う値に到達します。

・s = set(str(i))
 連番iをstr関数で文字型にして、set関数で集合型にします。
 集合型にすることで、1文字ずつの重複要素を削除し、構成要素だけの集合にします。

・for j in xrange(2, 7):
 jは掛ける数です。範囲は2から6までです。

・t = set(str(i*j))
 連番iと掛ける数jの積について、上記iと同様に構成要素だけの集合にします。

・if s!=t: break
 元の連番とj倍した値のそれぞれの構成要素が異なれば、ループ変数iの次の値へ処理を進めます。

・else: return i
 ループ変数jのfor文のelseです。
 for文のelseは、for文を全部最後の要素まで回り終わった場合に実行します。
 途中でループを抜けた場合には実行されません。
 ここでは上記if文ででbreakぜずに、2倍値から6倍値までのすべてで、その構成要素が元の数iの構成要素と同じ場合にだけ、そのiを呼び出し元に返します。

2.関数の外
 関数f()を呼び出し、その戻り値を出力します。


解答はこのすぐ下の行です。文字の色を白にしてます。選択状態にすると見えます。
142857

Project Euler - Problem 51

Project Euler(プロジェクト オイラー)の問題をpythonでやってみます。

出典: Project Euler(日本語 翻訳サイト)
   ProjectEuler.net(英語サイト)

Problem 51
*3の第1桁を置き換えることで,
13, 23, 43, 53, 73, 83という6つの素数が得られる.

56**3の第3桁と第4桁を同じ数で置き換ることを考えよう.
この5桁の数は7つの素数をもつ最初の例である:
56003, 56113, 56333, 56443, 56663, 56773, 56993.
よって, この族の最初の数である56003は, このような性質を持つ最小の素数である.

桁を同じ数で置き換えることで8つの素数が得られる最小の素数を求めよ.
(注:連続した桁でなくても良い)

-----


私の解答例は以下です。畳んでいます。
def p(n):
	L = [0,0]+[1]*(n-1)
	i = 2
	while i*i<=n:
		while not L[i]: i += 1
		for j in xrange(i+i, n+1, i): L[j] = 0
		i += 1
	return [i for i in xrange(n+1) if L[i]]

def h(n, L):
	for i in xrange(10):
		t = ""
		for j, k in enumerate(str(n)):
			if j in L: t += str(i)
			else: t += k
		yield int(t)

def g(m, n):
	d = {}
	for i, t in enumerate(str(m)):
		d[t] = d.get(t, [])+[i]
	for k,v in sorted(d.items()):
		if len(v)==n: yield int(k),v

def f():
	for i in xrange(3, 100, 3):
		for n in xrange(i+1, 100):
			P = [s for s in p(10**n) if s>10**(n-1)]
			for j in P:
				for k,L in g(j, i):
					if k>2: break
					if L[-1]>=n-1: break
					t = 0
					for u in h(j, L):
						if u not in P: t += 1
						if t>2: break
					if t==2: return j

print f()

候補を絞り込みます。

桁を同じ数で置き換えることで8つの素数が得られる最小の素数とのことなので、
置換後値が素数になる場合の置換数字には0~9うちの8個選ばれるので、
置換数字の最小値には0,1,2のどれかが含まれます。...(A)

素数でない数として、まず2の倍数をはずします。
置換数字には、0~9の10種類の数字のうちの8個選ばれるので、必ず偶数を含みます。
1の位に偶数がきたら2の倍数となり素数ではないので、
置換数字は1の位にきません。...(B)

つまり、置換数字は10の位よりも上の桁になるので、
全体桁数は置換数字の桁数よりも1桁以上多いです。...(C)

次に3の倍数をはずします。
置換数字が1桁の連番の場合、
3で割るとその余りは、0,1,2が繰り返しでてきます。
すると余り0,1,2が最低各3つずつあり、
3の倍数以外が8個あることはあり得ず問題に合いません。

置換数字が2桁の11,22,...の場合も同様で
余り0,1,2が最低各3つずつあり、
3の倍数以外が8個あることはあり得ず問題に合いません。

置換数字が3桁の111,222,...の場合は
置換数字そのものの各桁和が3の倍数なので、置換数字はすべて3の倍数なので、
3で割るとその余りはすべて同じで、0,1,2のどれかです。
この場合は問題に合うことがあり得ます。

置換数字が4桁以上の場合も上記と同様なので、
置換数字の桁数は3の倍数です。...(D)

上記の4条件を使って絞り込みます。
4つの関数を使っています。

1.関数p(n)
・n以下の素数リストを返します。problem37と同じです。

2.関数h(n, L):
・数値nの、位置リストLの位置を0-9に置換した数値を返します。
 指定位置の桁の値を置換数字で置換した値を返します。

・for i in xrange(10):
 ループ変数iは置換数字です。0~9の値を順に取ります。

・t = ""
 置換後数字を1桁ずつためていく変数です。初期値は""です。

・for j, k in enumerate(str(n)):
 ループ変数kは数字nを上から1文字ずつ取り出した値で、jはそのときの0からの連番です。

・if j in L: t += str(i)
 else: t += k
 連番jがリストLに含まれていれば現在位置が置換位置なので、置換数字iをstr関数で文字型変換した値を付け足します。
 そうでなければ数字nを上から1文字ずつ取り出した値kを付け足します。

・yield int(t)
 yield文で値が1つ決まるごとに処理を一旦凍結して呼び出し元に値を返却します。そして再度呼び出されたら処理を再開し続行、次の値を処理します。
 yield文では返却値が1つ決まるごとに処理凍結、値返却、処理再開を繰り返します。

このようにyield文で値を返す関数を「ジェネレータ関数」と呼びます。
これに対してreturn文で値を返し処理を終了してしまう関数は「イテレータ関数」といいます。
1つの関数の中にyield文とreturn文を両方含めることはできません。

3.関数g(m, n):
・数字mにn個含まれる構成文字とその位置リストの組を返します。
 置換数字を置換する位置を求めるときに使います。

・d = {}
 辞書dは数字mに含まれる各桁の値をキーに、その位置のリストを値として持ちます。初期値は空です。

・for i, t in enumerate(str(m)):
  d[t] = d.get(t, [])+[i]
 ループ変数iはenumerate関数による0からの連番で、数字mの中での左からの位置を示します。
 ループ変数tは数字mの上から1文字ずつの値です。
 get関数で、辞書dに数字mの上から1文字ずつの値tがキーとして存在すればtに対応する値を返し、存在しなければ空のリスト[]を返します。
 これに0からの連番iだけをを要素に持つリストを連結することで、
 数字mを構成する文字をキーとする、位置リストの辞書を作成します。

・for k,v in sorted(d.items()):
 items関数で辞書dのキーと値の組をループ変数k,vとして取得します。
 sorted関数でキー順に並べ替えます。
 これは1122のように同じ件数の構成文字が複数種類ある場合、小さい順に処理するためです。

・if len(v)==n: yield int(k),v
 ループ変数vは数字mの構成文字kの位置リストなので、len関数でその個数をみて、指定個数n個と等しければ、int関数で数値にした構成文字kとその位置リストを返します。

4.f():
・主処理です。

・for i in xrange(3, 100, 3):
 ループ変数iは置換文字の桁数です。
 絞り込み検討(D)より、置換数字の桁数は3の倍数なので、
 3から100まで3飛びの値をとります。100というのはとりあえずの終了値です。
 結果的にi=3のときに求める値に到達しました。

・for n in xrange(i+1, 100):
 ループ変数nは全体桁数です。
 絞り込み検討(C)より、全体桁数は置換数字の桁数よりも1以上多いので、
 i+1から100までの値をとります。100というのはとりあえずの終了値です。
 結果的にn=6のときに求める値に到達しました。

・P = [s for s in p(10**n) if s>10**(n-1)]
 Pはn桁の素数リストです。
 10**nは10のn乗、つまりn桁最大値+1なので、p(10**n)はn桁以下の素数リストです。
 内包表記で、for文でn桁以下の素数リストから順番に1つずつ取得してsとして、後ろのif文に送り、n桁の最小値を超える値をforの前に送り、そのまま要素にします。
 なお、n桁の最小値は1000などで決して素数ではありません。
 例えばn=4の場合、p(10000)は9999以下の素数で、Pはこのうち1000を超える素数です。

・for j in P:
 ループ変数jはn桁の素数リストから順に1つずつ取得します。
 このjが求める値の候補です。

・for k,L in g(j, i):
 ループ変数kとLには、関数gで算出した、
 候補jの置換数字候補とその位置リストの組をそれぞれ設定します。

・if k>2: break
 絞り込み検討(A)より、置換数字の最小値には0,1,2のどれかが含まれますので、置換数字候補が2を超えたら、次の候補jに進みます。

・if L[-1]>=n-1: break
 絞り込み検討(B)より、置換数字は1の位にきませんので、
 置換位置リストLの最後の値が全体桁数n-1以上ならば、次の候補jに進みます。

・t = 0
 候補jの置換後数値についての、素数以外カウンターです。
 素数判定の処理が重いで、10個中素数8個ということを素数以外が2個ということに代えて処理を軽くします。

・for u in h(j, L):
 ループ変数uは置換後数値です。
 関数hを使って、候補jの、位置リストLの位置の桁の値を0-9に置換した数値を1つずつ設定します。

・if u not in P: t += 1
 置換後数値uが素数でなければ、素数以外カウンターをカウントアップします。

・if t>2: break
 素数以外が2個以上(素数8個未満)が判明したら、次の値に処理を進めます。

・if t==2: return j
 素数以外が2個(素数8個)で問題の条件に合いますので、候補jを返し処理を終了します。

5.関数の外
 関数f()を呼び出し、その戻り値を出力します。


解答はこのすぐ下の行です。文字の色を白にしてます。選択状態にすると見えます。
121313

2012年8月19日日曜日

Project Euler - Problem 50

Project Euler(プロジェクト オイラー)の問題をpythonでやってみます。

出典: Project Euler(日本語 翻訳サイト)
   ProjectEuler.net(英語サイト)

Problem 50
素数41は6つの連続する素数の和として表せる:
41 = 2 + 3 + 5 + 7 + 11 + 13.
100未満の素数を連続する素数の和で表したときにこれが最長になる.

同様に, 連続する素数の和で1000未満の素数を表したときに最長になるのは953で21項を持つ.

100万未満の素数を連続する素数の和で表したときに最長になるのはどの素数か?


-----


私の解答例は以下です。畳んでいます。
def p(n):
	L = [0,0]+[1]*(n-1)
	i = 2
	while i*i<=n:
		while not L[i]: i += 1
		for j in xrange(i+i, n+1, i): L[j] = 0
		i += 1
	return [i for i in xrange(n+1) if L[i]]

def f(n):
	P = p(n)
	i, s, t = 0, 0, 0
	while s<n: i, s = i+1, s+P[i]
	for j in xrange(i):
		if i-j<t: break
		for k in xrange(i-1, j, -1):
			if k-j<t: break
			L = P[j:k+1]
			if sum(L) in P: M, t = L, len(L)
	return M

L = f(1000000)
print sum(L)
print "sum of "+str(len(L))+" consecutive primes, " \
+ "from "+str(L[0])+" to "+str(L[-1])+"."


連続する素数和が100万未満ということなので、
まず100万までの素数リストを作ります。
次にこの素数リストの小さい方から和が100万未満となる最大位置を把握します。
そしてこの最大位置までの範囲内で連続する区間を短くしならがその和が、
素数リストにあるかチェックするという流れでやります。

2つの関数を使っています。

1.関数p(n)
・n以下の素数リストを返します。problem37と同じです。

2.関数f(n)
・n以下の素数を連続する素数の和で表したときに最長になる素数のリストを返します。
 問題では100万未満ですが、100万は明らかに素数ではないのでこのまま使用します。

・P = p(n)
 n以下の素数リストです。

・i, s, t = 0, 0, 0
 初期設定です。0クリアしておきます。

・while s<n: i, s = i+1, s+P[i]
 iはカウンター、sは素数表のi番目までの和です。
 この和がnを超えたらループ終了です。
 カウンターiが、連続素数和の構成に使用できる最大位置となります。

・for j in xrange(i):
  if i-j<t: break
 ループ変数jは、連続素数和の最小値の位置です。
 範囲は0から使用最大位置iの1つ手前までです。
 tは後で出てきますが、連続素数の候補の連続する個数です。
 i-jが現在のjの値を固定したときに使用できる素数の最大件数なので、
 i-j<tになったら現在調査済みの連続個数を超えられないのでループをは終了します。

・for k in xrange(i-1, j, -1):
  if k-j<t: break
 ループ変数kは、連続素数和の最大値の位置です。
 範囲は上記最小値の位置jの1つ手前から使用できる最大位置iの1つ手前までです。
 大きい順にチェックするので、開始位置と終了位置を入れ替えて刻みを-1にしています。
 k-jが現在のkの値を固定したときに使用できる素数の最大件数なので、
 k-j<tになったら現在調査済みの連続個数を超えられないのでループをは終了します。

・L = P[j:k+1]
 リストLは上記の位置jと位置kの範囲で素数表Pから取り出した連続素数のリストです。

・if sum(L) in P: M, t = L, len(L)
 連続素数リストLの合計が素数表Pにあれば、連続素数リストLを連続素数最大長リストMとして、また連続数をtとして保存します。

・return M
 上記のループがすべて終了した時点で、連続素数最大長リストMを呼び出し元へ返します。

3.関数の外
 100万を関数fに渡し、100万未満の素数を連続する素数の和で表したときに最長になる素数のリストを受け取ります。
求める値はこのリストの和、sum(L)です。

解答はこのすぐ下の行です。文字の色を白にしてます。選択状態にすると見えます。
997651
sum of 543 consecutive primes, from 7 to 3931.

Project Euler - Probrem 49

Project Euler(プロジェクト オイラー)の問題をpythonでやってみます。

出典: Project Euler(日本語 翻訳サイト)
   ProjectEuler.net(英語サイト)

Problem 49
項差3330の等差数列1487, 4817, 8147は次の2つの変わった性質を持つ。

  • (i)3つの項はそれぞれ素数である。
  • (ii)各項は他の項の置換で表される。
1, 2, 3桁の素数にはこのような性質を持った数列は存在しないが、4桁の増加列にはもう1つ存在する。
それではこの数列の3つの項を連結した12桁の数を求めよ。


-----

私の解答例は以下です。畳んでいます。
def p(n):
	L = [0,0]+[1]*(n-1)
 i = 2 while i*i<=n: while not L[i]: i += 1 for j in xrange(i+i, n+1, i): L[j] = 0 i += 1 return [i for i in xrange(n+1) if L[i]] def f(n): import itertools A = [] P = [i for i in p(10**n) if len(str(i))==n] for i in P: L = set(["".join(j) for j in itertools.permutations(str(i), n)]) M = sorted([int(j) for j in L if int(j) in P]) for j in xrange(len(M)-1): for k in xrange(j+2, len(M)): s, t = divmod(M[j]+M[k], 2) if (not t) and (s in M): v = int(str(M[j])+str(s)+str(M[k])) if v not in A: A.append(v) break return A L = f(4) for s in L: if s!=148748178147: print s


2つの関数を使っています。

1.関数p(n)
・n以下の素数リストを返します。problem37と同じです。

2.関数f(n)
・import itertools
 pythonの標準モジュール「itertools」をインポートします。

・A = []
 P = [i for i in p(10**n) if len(str(i))==n]
 初期設定です。
 戻り値用リストAは空リストです。
 リストPは、n桁の素数のリストです。
 内包表記です。
 **演算子は累乗で、10**nでn桁の最大値+1になります。
 まずfor文で0からn桁の最大値を1つずつループ変数iとします。
 次に後ろのif文へiを渡し、str関数で文字型にしてlen関数で文字列長を取得しこれがnの場合に限り、for文の前に渡し、そのままリストにためます。

・for i in P:
 n桁素数リストPから1つずつループ変数iとします。

・L = set(["".join(j) for j in itertools.permutations(str(i), n)])
 リストLは、素数iの構成数字を並び替えた値のリストです。
 標準モジュールitertoolsの関数permutations(t, n)は、文字列tの構成文字列から繰り返しを許さずにn個を選んで組にして返します。
 例えば、t="13",n=2ならば、("1","3")("3","1")を返します。

 ここでは、str関数で文字型にした素数iから
 n桁の「繰り返しを許さない順列タプル」を生成し、
 for文で1つずつループ変数jとして、
 このタプルjを区切り文字無しで連結してリストLにためます。
 例の場合、["13", "31"]となります。

・M = sorted([int(j) for j in L if int(j) in P])
 リストMは、素数iの構成数字を並び替えてさらに素数だった値のリストです。
 内包表記です。
 まずfor文で上記リストLから1つ取り出して要素jとします。
 そして後ろのif文へ渡し、int関数で数値にしたあと素数表Pにあるか判定し、素数の場合、for文の前に渡し、int関数で数値型にしてリストにためます。
 後で差を見るのでsorted関数で小さい順に並べなおしておきます。

・for j in xrange(len(M)-1):
  for k in xrange(j+2, len(M)):
 ループ変数jは求める等差数列のうち最小値に相当する値の、リストMでの位置です。
 ループ変数kは求める等差数列のうち最大値に相当する値の、リストMでの位置です。
 つまり、3項の等差数列の最小値と最大値の候補を、M[j]とM[k]とします。

・s, t = divmod(M[j]+M[k], 2)
 divmod関数は第1引数÷第2引数を計算し、整数の商と余りのタプルを返します。
 ここでは(M[j]+M[k])÷2の商と余りを計算し、それぞれs,tとします。
 商sは3項の等差数列の2番目の項、余りtは商sが整数として存在すれば余り0(論理的にFalse)になります。

・if (not t) and (s in M):
  v = int(str(M[j])+str(s)+str(M[k]))
  if v not in A: A.append(v)
  break
 (not t)で上記sが整数として存在し、(s in M)で上記sがリストMにあることをチェックします。
 このような条件が満たされる場合、3項の等差数列が存在することになるので、問題に合うように3項の値を文字型にして小さい順に連結しvとします。
 上記vが戻り値用リストAに無ければ追加します。

・return A
 このようにしてたまった戻り値リストAを呼び出し元に返します。

3.関数の外
・L = f(4)
 for s in L:
  if s!=148748178147: print s
 問題に合う関数fの戻り値を受け取り、さらに「148748178147」以外の値を表示します。
 


解答はこのすぐ下の行です。文字の色を白にしてます。選択状態にすると見えます。
296962999629

Project Euler - Probrem 48

Project Euler(プロジェクト オイラー)の問題をpythonでやってみます。

出典: Project Euler(日本語 翻訳サイト)
   ProjectEuler.net(英語サイト)

Problem 48
次の式は、11 + 22 + 33 + ... + 1010 = 10405071317 である。
では、11 + 22 + 33 + ... + 10001000 の最後の10桁を求めよ。

-----


私の解答例は以下です。畳んでいます。
def f(n): return str(sum([i**i for i in xrange(1, n+1)]))

s = f(1000)
print s[-10:]


pythonでは多バイト長の数値であっても通常の数値として扱うことができます。

1.関数f(n)
・1からnまでの数字のその数字分の累乗の和を文字型にして返します

・return str(sum([i**i for i in xrange(1, n+1)]))
 内包表記です。
 for文で1からnまでの数字をループ変数iとして、forの前に渡します。
 **演算子は累乗です。i**iで、iのi乗値をリストにためます。
 sum関数で合計し、さらにstr関数で文字型に変換しておきます。

2.関数の外
・s = f(1000)
 引数1000で関数fを呼び出します。なお、この戻り値は3001桁ありました。

・print s[-10:]
 上記で取得したsの右から10桁分を取り出します。

解答はこのすぐ下の行です。文字の色を白にしてます。選択状態にすると見えます。
9110846700

Project Euler - Probrem 47

Project Euler(プロジェクト オイラー)の問題をpythonでやってみます。

出典: Project Euler(日本語 翻訳サイト)
   ProjectEuler.net(英語サイト)

Problem 47
連続する2つの数がそれぞれ2つの異なる素因数を持つのは
  • 14 = 2 × 7
  • 15 = 3 × 5の場合である.
同様に連続する3つの数がそれぞれ3つの異なる素因数を持つのは
  • 644 = 22 × 7 × 23
  • 645 = 3 × 5 × 43
  • 646 = 2 × 17 × 19の場合である.
連続する4つの数がそれぞれ4つの異なる素因数を持つ場合を考え, 連続する数の中で最小のものを答えよ.

-----

私の解答例は以下です。畳んでいます。
def p(n):
	L = [0,0]+[1]*(n-1)
	i = 2
	while i*i<=n:
		while not L[i]: i += 1
		for j in xrange(i+i, n+1, i): L[j] = 0
		i += 1
	return [i for i in xrange(n+1) if L[i]]

def g(n, P):
	s, d = n, {}
	for i in P:
		while s>=i:
			if s%i: break
			else:
				while not s%i:
					d[i], s = d.get(i, 0)+1, s//i
		if s<i: break
	return d

def f(n):
	P = p(10000000)
	i, L = 0, []
	while True:
		i += 1
		s = g(i, P)
		if len(s)==n: L.append(s)
		else: L = []
		if len(L)==n: break
	return L

n = 4
for d in f(n):
	s = 1
	for k, v in d.items():
		s *= k**v
	print s, sorted(d.items())


3つの関数を使っています。

1.関数p(n)
・n以下の素数リストを返します。problem37と同じです。
2.関数g(n, P)
・素数リストPに含まれる素数にて、nを素因数分解した辞書を返します。
 キーが素数で値がその素数が含まれる数です。

・s, d = n, {}
 sを素数で割り算していきます。初期値はnです。
 dは返却用の辞書です。初期値は空の辞書です。

・for i in P:
 素数リストPの要素を1つずつループ変数iとします。

・while s>=i:
 割られる数sが割る数の素数i以下の場合に割り算をしていきます。

・if s%i: break
 %は割り算の余りです。余りがある(論理的にTrueの)場合、割る数iを次の値に進めます。

・else:
  while not s%i:
   d[i], s = d.get(i, 0)+1, s//i
 余りがない(論理的にFalseの)場合、割り切れなくなるまで、割る数iで割り続けます。
 辞書dのキーiの値を1ずつカウントアップします。
 割られる数sはiで割った商を設定します。

・if s<i: break
 割られる数sよりも割る数の素数iが大きくなったら終了します。

3.関数f(n)
・n個の素因数をもつ連続するn個の数について、その連続するそれぞれの値を素因数分解した辞書を要素とするリストを返します。

・P = p(10000000)
 リストPは1000万以下の素数の表です。
 これは私の手持ちの素数発生関数で1分ルールとメモリを満たす限界です。
 素因数分解をするときに割る数として使用します。

・i, L = 0, []
 iはカウンターで、Lは戻り値用リストです。、

・while True:
  i += 1
 無限ループです。ループの都度、カウンターiを1つカウントアップします。

・s = g(i, P)
 sはカウンター値iを素数表Pを使って素因数分解した辞書です。

・if len(s)==n: L.append(s)
 else: L = []
 len関数の辞書sを渡すと、辞書sのキー件数を返します。
 この辞書sのキー件数は素因数の数なので、n個の場合、戻り値用リストLにためます。
 n以外の場合、戻り値用リストLをクリアします。

・if len(L)==n: break
 戻り値用リストLの件数をチェックし、要素数がnならばループ終了です。

3.関数の外
・問題に合う値を表示します。

・n = 4
 for d in f(n):
 問題に合うように引数を4を関数fに渡します。

・s = 1
  for k, v in d.items():
   s *= k**v
  print s, sorted(d.items())
 問題に合う連続する数をsとして、素因数とその掛け合わせる数に基づいて、再現します。
 辞書d.itmes()は、辞書dのキーと値の組のタプルを返します。
 ここではそれぞれループ変数kにキー値を、ループ変数vに値を設定しています。
 **演算子は累乗です。ここではkのv乗値をsに掛け算で累積しています。

解答はこのすぐ下の行です。文字の色を白にしてます。選択状態にすると見えます。
134043 [(3, 1), (7, 1), (13, 1), (491, 1)]
134044 [(2, 2), (23, 1), (31, 1), (47, 1)]
134045 [(5, 1), (17, 1), (19, 1), (83, 1)]
134046 [(2, 1), (3, 2), (11, 1), (677, 1)]