新しいメールですが、Google の Gmail を使うことにしました。メールアドレスは Profile をご覧くださいませ。
この度、Yahoo! Japan を退会することにしました。Yahoo! Japan には長い間大変お世話になりました。厚くお礼申しあげます。Yahoo メール (m_hiroi@yahoo.co.jp) は使用できないのでご注意ください。新しいメールはまだ決めておりません。決まりましたら改めてご報告いたします。これからもがんばりますので、よろしくお願い申しあげます。
円順列は順列の要素を円形に並べ、それを回転してできる並び方を同じ順列として扱うものです。たとえば、[1, 2, 3, 4] の順列は 24 通りあります。配列の末尾と先頭をつなげて [1, 2, 3, 4] を回転すると、[2, 3, 4, 1], [3, 4, 1, 2], [4, 1, 2, 3] という並び方ができます。これを [1, 2. 3. 4] と同じものと考えるのが円順列です。したがって、[1, 2, 3, 4] の円順列は、次に示すように (4 - 1)! = 6 通りになります。
[1, 2, 3, 4] [1, 2, 4, 3] [1, 3, 2, 4] [1, 3, 4, 2] [1, 4, 2, 3] [1, 4, 3, 2]
このように、要素が n 個の円順列の総数は、要素を一つ固定して考えると、残りの要素 n - 1 個の順列の数 (n - 1)! と同じになります。
円順列を生成するプログラムは、順列と組み合わせ で作った関数 permutation(n, xs) を使うと簡単です。
リスト : 円順列の生成 def circular_permutation(xs): for ys in permutation(len(xs) - 1, xs[1:]): yield [xs[0]] + ys
xs の先頭要素を固定して、残りの要素 xs[1:] の順列を permutation で求めます。あとは、その先頭に xs[0] を追加して返すだけです。
簡単な実行例を示します。
>>> from perm import * >>> for x in circular_permutation([1,2,3]): print(x) ... [1, 2, 3] [1, 3, 2] >>> for x in circular_permutation([1,2,3,4]): print(x) ... [1, 2, 3, 4] [1, 2, 4, 3] [1, 3, 2, 4] [1, 3, 4, 2] [1, 4, 2, 3] [1, 4, 3, 2] >>> for x in circular_permutation([1,2,3,4,5]): print(x) ... [1, 2, 3, 4, 5] [1, 2, 3, 5, 4] [1, 2, 4, 3, 5] [1, 2, 4, 5, 3] [1, 2, 5, 3, 4] [1, 2, 5, 4, 3] [1, 3, 2, 4, 5] [1, 3, 2, 5, 4] [1, 3, 4, 2, 5] [1, 3, 4, 5, 2] [1, 3, 5, 2, 4] [1, 3, 5, 4, 2] [1, 4, 2, 3, 5] [1, 4, 2, 5, 3] [1, 4, 3, 2, 5] [1, 4, 3, 5, 2] [1, 4, 5, 2, 3] [1, 4, 5, 3, 2] [1, 5, 2, 3, 4] [1, 5, 2, 4, 3] [1, 5, 3, 2, 4] [1, 5, 3, 4, 2] [1, 5, 4, 2, 3] [1, 5, 4, 3, 2]
異なる n 個の要素を持つ集合 xs を分割することを考えます。たとえば、集合 [1, 2, 3] は次のように分割することができます。
1 分割 : [[1, 2, 3]] 2 分割 : [[1, 2], [3]], [[1, 3], [2]], [[1], [2, 3]] 3 分割 : [[1], [2], [3]]
このように、分割した集合 ys は元の集合 xs の部分集合になります。分割した部分集合の積は空集合になり、分割した部分集合のすべての和を求めると元の集合になります。そして、集合を分割する仕方の数を「ベル数 (Bell Number)」といいます。ベル数は次の漸化式で求めることができます。
B(0) = 1 n B(n+1) = Σ nCk * B(k) # n >= 1 k=0
詳しい説明は拙作のページ Puzzle DE Programming: 集合の分割とベル数 をお読みくださいませ。
また、n 個の要素を k 分割する仕方の数を nSk または S(n, k) と書くことがあります。これを「第 2 種スターリング数」といい、次の漸化式で求めることができます。
nS1 = nSn = 1 nSk = n-1Sk-1 + k * n-1Sk
ベル数 B(n) は第 2 種スターリング数 S(n, 1) から S(n, n) の総和と等しくなります。
n B(n) = Σ nSk k=1
それでは、nSk を求める関数 stirling_number(n, k) を作りましょう。使用するプログラミング言語は Python3 です。次のリストを見てください。
リスト : 第 2 種スターリング数 def stirling_number(n, k): if k == 1 or k == n: return 1 return stirling_number(n - 1, k - 1) + k * stirling_number(n - 1, k)
定義通りにプログラムしただけです。簡単ですが二重再帰になるので実行速度は遅くなります。
>>> for n in range(1, 11): ... for k in range(1, n + 1): ... print(stirling_number(n, k), end=' ') ... print() ... 1 1 1 1 3 1 1 7 6 1 1 15 25 10 1 1 31 90 65 15 1 1 63 301 350 140 21 1 1 127 966 1701 1050 266 28 1 1 255 3025 7770 6951 2646 462 36 1 1 511 9330 34105 42525 22827 5880 750 45 1 >>> import time >>> s = time.time(); print(stirling_number(20, 10)); time.time() - s 5917584964655 0.028607606887817383 >>> s = time.time(); print(stirling_number(30, 15)); time.time() - s 12879868072770626040000 20.694570302963257
Python の場合、メモ化関数を使うと簡単に高速化することができます。
リスト : 高速化 (1) # メモ化関数 def memoize(f): table = {} def func(*args): if not args in table: table[args] = f(*args) return table[args] return func @memoize def stirling_number(n, k): if k == 1 or k == n: return 1 return stirling_number(n - 1, k - 1) + k * stirling_number(n - 1, k)
>>> s = time.time(); print(stirling_number(30, 15)); time.time() - s 12879868072770626040000 0.0007224082946777344 >>> s = time.time(); print(stirling_number(60, 30)); time.time() - s 956352885509440274475273302536897620138631741164099440 0.0021333694458007812
動的計画法を使うと、もう少し速くなります。
リスト : 高速化 (2) # 動的計画法 def stirling_number_dp(n, k): xs = [0, 1] for i in range(n - 1): ys = [0] for j in range(len(xs) - 1): ys.append(xs[j] + (j + 1) * xs[j + 1]) ys.append(1) xs = ys return xs[k]
>>> s = time.time(); print(stirling_number_dp(60, 30)); time.time() - s 956352885509440274475273302536897620138631741164099440 0.0005311965942382812 >>> s = time.time(); print(stirling_number_dp(100, 50)); time.time() - s 430983237009366340421514301547258695943520289614340613912441741131280319058853783145598261659992013900 0.0012993812561035156
次は集合の分割を列挙する関数 stirlings(xs, k) を作りましょう。stirlings は集合 xs を k 個のグループに分割する仕方を列挙します。Python の場合、ジェネレータを使うと簡単です。次のリストを見てください。
リスト : 集合の分割を列挙する def stirlings(xs, k): if k == 1: yield [xs] elif k == len(xs): yield [[x] for x in xs] else: for ys in stirlings(xs[1:], k - 1): yield [[xs[0]]] + ys for ys in stirlings(xs[1:], k): for i in range(k): zs = ys[:] zs[i] = [xs[0]] + zs[i] yield zs
基本的な考え方は第 2 種スターリング数の定義と同じです。k が 1 の場合、xs の要素を 1 つのグループに格納して返します。k と xs の要素数が等しい場合、xs の要素を一つずつ格納した k 個のグループを生成して返します。これらの条件が再帰呼び出しの停止条件になります。
else 節では要素 xs[0] を選んでグループに追加します。最初の for ループでは、xs[0] を新しいグループに追加します。xs[1:] を k - 1 個のグループに分割し、その返り値 ys に xs[0] を格納したグループを追加します。次の for ループでは、xs[1:] を k 個のグループに分割します。そして、入れ子の for ループで各々のグループに xs[0] を追加して返します。これで、stirlings の動作は漸化式と同じになります。
簡単な実行例を示します。
>>> for x in stirlings([1,2,3], 1): print(x) ... [[1, 2, 3]] >>> for x in stirlings([1,2,3], 2): print(x) ... [[1], [2, 3]] [[1, 2], [3]] [[2], [1, 3]] >>> for x in stirlings([1,2,3], 3): print(x) ... [[1], [2], [3]] >>> for i in range(1, 5): ... for x in stirlings([1,2,3,4], i): print(x) ... [[1, 2, 3, 4]] [[1], [2, 3, 4]] [[1, 2], [3, 4]] [[2], [1, 3, 4]] [[1, 2, 3], [4]] [[2, 3], [1, 4]] [[1, 3], [2, 4]] [[3], [1, 2, 4]] [[1], [2], [3, 4]] [[1], [2, 3], [4]] [[1], [3], [2, 4]] [[1, 2], [3], [4]] [[2], [1, 3], [4]] [[2], [3], [1, 4]] [[1], [2], [3], [4]]
正常に動作しているようです。興味のある方はいろいろ試してみてください。
異なる n 個のものから r 個を取り出して、1 列に並べる並べ方を「順列」といい、その数を nPr と書きます。順列の数は次の公式で求めることができます。
nPr = n(n - 1)(n - 2) ... (n - r + 1) = n! / (n - r)!
また、異なる n 個のものから重複を許して r 個を選んで、1 列に並べる並べ方を「重複順列」といい、その数は nr になります。
異なる n 個のものから r 個を取り出す選び方を「組み合わせ」といい、その数を nCr と書きます。組み合わせの数は次の公式で求めることができます。
(1) nCr = nPr / r! = n! / r!(n - r)!
(2) nC0 = nCn = 1 nCr = nCr-1 * (n - r + 1) / r
(3) nC0 = nCn = 1 nCr = n-1Cr-1 + n-1Cr
プログラムを作る場合、(1) と (2) の公式を使うと簡単に (高速に) 答えを求めることができます。ただし、(3) の公式をそのままプログラムすると二重再帰になるので、大きな値を求めると時間がかかります。
また、異なる n 個の中から重複を許して r 個を取り出す選び方を「重複組み合わせ」といい、その数を nHr と書きます。重複組み合わせの数は次の公式で求めることができます。
nHr = n+r-1Cr
それでは、順列と組み合わせを列挙するプログラムを作りましょう。Python3 のジェネレータを使うと、プログラムは次のようになります。
リスト : 順列と組み合わせ # 順列 def permutation(n, xs): if n == 0: yield [] else: for ys in permutation(n - 1, xs): for x in xs: if x not in ys: yield ys + [x] # 組み合わせ def combination(n, xs, m = 0): if n == 0: yield [] elif len(xs) > m: for a in combination(n - 1, xs, m + 1): yield [xs[m]] + a for a in combination(n, xs, m + 1): yield a
再帰定義でジェネレータを作成する場合、関数内でジェネレータを生成し、その値を使って新しい値を yield 文で返すようにします。たとえば、要素が n 個の順列を生成する場合、n - 1 個の順列を生成するジェネレータを生成し、そこに要素を一つ加えて n 個の順列を生成すると考えます。
ジェネレータ permutation は xs から n 個の要素を選ぶ順列を生成します。n が 0 の場合、すべての要素を選択したので yield 文で空リスト [ ] を返します。そうでなければ、permutation を再帰呼び出しして生成される値を for 文で受け取ります。そして、その値 ys に要素 x を追加したリストを yield で返します。このとき、x が ys に含まれていないことを確認します。これで順列を生成することができます。
ジェネレータ combination は xs から n 個の要素を選ぶ組み合わせを生成します。引数 m は選択する要素の位置 (xs の添字) を表します。n が 0 の場合、すべての要素を選んだので yield 文で空リストを返します。m が len(xs) より小さい場合は xs[m] を選ぶことができます。
最初の再帰呼び出しが xs[m] を選ぶ場合です。このとき、引数 m を +1 することをお忘れなく。返り値 a に xs[m] を追加して yield 文で返します。次の再帰呼び出しが xs[m] を選ばない場合です。n の値はそのままにして、m の値を +1 します。そして、返り値 a を yield 文でそのまま返します。
簡単な実行例を示します。
>>> for x in permutation(3, [1,2,3]): print(x) ... [1, 2, 3] [1, 3, 2] [2, 1, 3] [2, 3, 1] [3, 1, 2] [3, 2, 1] >>> for x in permutation(2, [1,2,3]): print(x) ... [1, 2] [1, 3] [2, 1] [2, 3] [3, 1] [3, 2] >>> for x in combination(3, [1,2,3,4,5]): print(x) ... [1, 2, 3] [1, 2, 4] [1, 2, 5] [1, 3, 4] [1, 3, 5] [1, 4, 5] [2, 3, 4] [2, 3, 5] [2, 4, 5] [3, 4, 5] >>> for x in combination(2, [1,2,3,4,5]): print(x) ... [1, 2] [1, 3] [1, 4] [1, 5] [2, 3] [2, 4] [2, 5] [3, 4] [3, 5] [4, 5]
重複順列と重複組み合わせは permutation と combination をちょっと修正するだけです。次のリストを見てください。
リスト : 重複順列と重複組み合わせ # 重複順列 def perm_with_rep(n, xs): if n == 0: yield [] else: for ys in perm_with_rep(n - 1, xs): for x in xs: yield ys + [x] # 重複組み合わせ def comb_with_rep(n, xs, m = 0): if n == 0: yield [] elif len(xs) > m: for a in comb_with_rep(n - 1, xs, m): yield [xs[m]] + a for a in comb_with_rep(n, xs, m + 1): yield a
perm_with_rep は重複順列を生成するジェネレータです。permutation では選択した要素 x が ys にないことをチェックしましたが、その処理を省くと重複順列を生成することができます。
comb_with_rep は重複組み合わせを生成するジェネレータです。最初の再帰呼び出して要素 xs[m] を選ぶとき、combination では m の値を +1 しましたが、comb_with_rep では m の値をそのままにして再帰呼び出しします。これで同じ要素を何度も選択することができます。
簡単な実行例を示します。
>>> for x in perm_with_rep(2, [1,2,3]): print(x) ... [1, 1] [1, 2] [1, 3] [2, 1] [2, 2] [2, 3] [3, 1] [3, 2] [3, 3] >>> for x in perm_with_rep(3, [1,2,3]): print(x) ... [1, 1, 1] [1, 1, 2] [1, 1, 3] ・・・省略・・・ [3, 3, 1] [3, 3, 2] [3, 3, 3]
>>> for x in comb_with_rep(2, [1,2,3]): print(x) ... [1, 1] [1, 2] [1, 3] [2, 2] [2, 3] [3, 3] >>> for x in comb_with_rep(3, [1,2,3,4,5]): print(x) ... [1, 1, 1] [1, 1, 2] [1, 1, 3] ・・・省略・・・ [4, 4, 5] [4, 5, 5] [5, 5, 5]
正常に動作しているようです。順列と組み合わせは Python 以外の言語でも簡単にプログラムすることができます。興味のある方はいろいろな言語で試してみてください。
n 桁の自然数 m において、各桁の n 乗の和が m に等しい数を「ナルシシスト数 (narcissistic number)」といいます。定義により 1 桁の数 (1 - 9) はナルシシスト数になります。2 桁のナルシシスト数はありませんが、3 桁のナルシシスト数は以下の 4 つがあります。
153 = 13 + 53 + 33 = 1 + 125 + 27 = 153 370 = 33 + 73 + 03 = 27 + 343 + 0 = 370 371 = 33 + 73 + 13 = 27 + 343 + 1 = 371 407 = 43 + 03 + 73 = 64 + 0 + 343 = 407
ナルシシスト数は有限個しかありません。n 桁のナルシシスト数の最小値は 10n-1 で、最大値は n * 9n です。ナルシシスト数が存在するには次の不等式が成立しないといけません。
10n-1 <= n * 9n
両辺を 9n-1 で割ると以下の式になります。
(10 / 9)n-1 <= 9 * n
n が小さいとき上式は成立しますが、n が大きくなると指数関数の値は爆発的に増加するので、上式はいづれ不成立になります。具体的には n が 60 を超えると不成立になります。
n = 60: (10 / 9)59 = 500.8327, 9 * 60 = 540, 成立 n = 61: (10 / 9)60 = 556.4808, 9 * 61 = 549, 不成立
ナルシシスト数を求める場合、たとえば 4 桁のナルシシスト数であれば、1000 - 9999 までの整数値を順番に生成し、各桁の 4 乗和の計算結果が元の整数と等しければ、その数はナルシシスト数と判定することができます。ただし、この方法はとても非効率です。1634 は 4 桁のナルシシスト数ですが、それ以外の数字の並び方 (23 通り) はナルシシスト数にはなりません。
各桁の n 乗和は数字の並びに関係なく計算することができるので、この場合は 0 - 9 から n 個の数字を選ぶ重複組み合わせを求めたほうが効率的です。選んだ数字の組み合わせから整数値を生成し、その値を桁ごとに分解した結果、元の組み合わせと同じであれば、その数をナルシスト数と判定することができます。
重複組み合わせを生成するプログラムを Python3 で作ると次のようになります。
リスト : 重複組み合わせの生成 def combinations(f, n, m, a): if n == 0: f(a) elif m < len(a): a[m] += 1 combinations(f, n - 1, m, a) a[m] -= 1 combinations(f, n, m + 1, a)
関数 combinations は、0 から len(a) 未満の数字から n 個の数字を選ぶ重複組み合わせを生成します。選択した数字の個数は引数の配列 a に格納します。引数 m が選ぶ数字を表します。n が 0 ならば組み合わせを一つ生成したので、引数の関数 f を呼び出します。
m が len(a) よりも小さければ、数字 m を選ぶことができます。a[m] の個数を +1 してから、combinations を再帰呼び出しします。そのあと、a[m] を -1 してから m を選ばない組み合わせを生成します。
簡単な実行例を示します。
>>> combinations(print, 2, 0, [0,0]) [2, 0] [1, 1] [0, 2] >>> combinations(print, 3, 0, [0,0]) [3, 0] [2, 1] [1, 2] [0, 3] >>> combinations(print, 3, 0, [0,0,0]) [3, 0, 0] [2, 1, 0] [2, 0, 1] [1, 2, 0] [1, 1, 1] [1, 0, 2] [0, 3, 0] [0, 2, 1] [0, 1, 2] [0, 0, 3]
n 乗和と数値を桁に分解するプログラムも簡単です。プログラムと実行例を示します。
リスト : n 乗和と数の分解 # n 乗和 def power_sum(n, a): return sum([x * (i ** n) for i, x in enumerate(a)]) # 数値を桁ごとに分解 def split_digit(n): a = [0] * 10 while n > 0: a[n % 10] += 1 n //= 10 return a
>>> split_digit(1634) [0, 1, 0, 1, 1, 0, 1, 0, 0, 0] >>> power_sum(4, split_digit(1634)) 1634 >>> split_digit(45678) [0, 0, 0, 0, 1, 1, 1, 1, 1, 0] >>> power_sum(5, split_digit(45678)) 61500
最後に n 桁のナルシシスト数を求める関数 solver を作ります。
リスト : n 桁のナルシシスト数を求める def solver(n): def check(a): x = power_sum(n, a) b = split_digit(x) if a == b: print(x) combinations(check, n, 0, [0] * 10)
combinations に局所関数 check を渡して呼び出します。生成した組み合わせは check の引数 a に渡されます。power_sum で累乗輪を求めて変数 x にセットし、split_digit で x を桁ごとに分解して変数 b にセットします。a と b が等しければ、x はナルシシスト数です。print で x を表示します。
それでは実行してみましょう。使用する処理系は PyPy3 です。
$ pypy3 --version Python 3.8.13 (7.3.9+dfsg-1, Apr 01 2022, 21:41:47) [PyPy 7.3.9 with GCC 11.2.0]
>>>> from narci import * >>>> import time >>>> for x in range(1, 19): .... print('-----', x, '-----') .... s = time.time() .... solver(x) .... e = time.time() .... print(e - s) .... ----- 1 ----- 1 2 3 4 5 6 7 8 9 0.0016078948974609375 ----- 2 ----- 0.0008893013000488281 ----- 3 ----- 370 407 153 371 0.009445667266845703 ----- 4 ----- 8208 1634 9474 0.018713951110839844 ----- 5 ----- 93084 92727 54748 0.024398088455200195 ----- 6 ----- 548834 0.004481315612792969 ----- 7 ----- 9800817 4210818 1741725 9926315 0.01246333122253418 ----- 8 ----- 24678050 24678051 88593477 0.018772125244140625 ----- 9 ----- 146511208 912985153 472335975 534494836 0.038550615310668945 ----- 10 ----- 4679307774 0.08915567398071289 ----- 11 ----- 32164049650 40028394225 42678290603 49388550606 32164049651 94204591914 44708635679 82693916578 0.13814592361450195 ----- 12 ----- 0.21680974960327148 ----- 13 ----- 0.3555634021759033 ----- 14 ----- 28116440335967 0.5982820987701416 ----- 15 ----- 0.9512383937835693 ----- 16 ----- 4338281769391370 4338281769391371 1.538985252380371 ----- 17 ----- 35875699062250035 21897142587612075 35641594208964132 2.4040043354034424 ----- 18 ----- 3.643921375274658
18 桁程度であれば PyPy でもナルシシスト数を簡単に求めることができるようです。これ以上桁数を増やすには、より高速なプログラミング言語を使う、何かしらの枝刈りを入れる、あるいはその両方を行う必要があると思います。参考 URL 3 によると、ナルシシスト数は全部で 88 個あり、最大桁数は 39 になるそうです。deepgreen さんは、参考 URL 1 ですべてのナルシシスト数を求めています。興味のある方は挑戦してみてください。
複素数 z の逆双曲線関数 acosh(z) の定義に式 (2) を追記しました。
asinh z = log (z + √(z2 + 1)) acosh z = log (z + √(z2 - 1)) -- (1) = log (z + (√(z + 1)) * (√(z - 1))) -- (2) atanh z = (1 / 2) * log((1 + z) / (1 - z)) = (1 / 2) * (log(1 + z) - log(1 - z))
参考 URL 2 によると、acosh(z) を式 1 でプログラムすると「分枝切断線」が複雑になるため、他の式 (たとえば式 2 など) でプログラムする処理系が多いようです。ちなみに、ANSI Common Lisp では acosh(z) を次の式で定義しています。
acosh(z) = 2 * log(sqrt((z + 1)/2) + sqrt((z - 1)/2))
Linux のフォントはライブラリ fontconfig を使って管理されています。ユーザーがフォントの優先順位を変更するときは、fontconfig の設定ファイル ~/.config/fontconfig/fonts.conf で行うことができるようです。詳しい説明は fonts.conf のドキュメント (man fonts-conf) や下記 URL をお読みください。
M.Hiroi は URL 3, 4 を参考に、fonts.conf を下記のように定義したところ、設定を Noto フォントに変更することができました。
リスト : fonts.conf <?xml version='1.0'?> <!DOCTYPE fontconfig SYSTEM 'fonts.dtd'> <fontconfig> <!-- Default font (no fc-match pattern) --> <match> <edit mode="prepend" name="family"> <string>Noto Sans CJK JP</string> </edit> </match> <!-- Default font for the ja_JP locale (no fc-match pattern) --> <match> <test compare="contains" name="lang"> <string>ja</string> </test> <edit mode="prepend" name="family"> <string>Noto Sans CJK JP</string> </edit> </match> <!-- Default sans-serif font --> <match target="pattern"> <test qual="any" name="family"><string>sans-serif</string></test> <edit name="family" mode="prepend" binding="same"><string>Noto Sans CJK JP</string> </edit> </match> <!-- Default serif fonts --> <match target="pattern"> <test qual="any" name="family"><string>serif</string></test> <edit name="family" mode="prepend" binding="same"><string>Noto Serif CJK JP</string> </edit> </match> <!-- Default monospace fonts --> <match target="pattern"> <test qual="any" name="family"><string>monospace</string></test> <edit name="family" mode="prepend" binding="same"><string>Noto Sans Mono CJK JP</string></edit> </match> </fontconfig>
$ fc-match NotoSansCJKjp-Regular.otf: "Noto Sans CJK JP" "Regular" $ fc-match sans NotoSansCJKjp-Regular.otf: "Noto Sans CJK JP" "Regular" $ fc-match serif NotoSerifCJKjp-Regular.otf: "Noto Serif CJK JP" "Regular" $ fc-match mono NotoSansMonoCJKjp-Regular.otf: "Noto Sans Mono CJK JP" "Regular"
Wayland は X Window System (X11) の後継を目指して開発されている通信プロトコル (初版 2008 年 9 月) です。Weston というリファレンス実装も公開されています。Wayland だけでは今までの X11 アプリケーションが動作しないので、XWayland という X サーバー互換の機能も用意されています。
現在、ほとんどの Linux ディストリビューションが Wayland に対応しています。GUI ツールキットでも、GTK 3.0 や Qt 5 など多くのツールキットが対応済みです。そして、WSLg が Wayland (Weston) を採用しています。
M.Hiroi が Wayland のことを知ったのは WSL2 + WSLg を使うようになってからです。WSLg で X11 アプリが動作するのは、中で X サーバーが動いているからだと思っていました。実際には、X サーバーではなく WayLand の XWayland が機能していたわけです。Wayland のことはよくわかっていないので、参考 URL を読んで勉強してみようかなと思っています。
お正月ということで、あらためて Ubunts 22.04 LTS を WSL にインストールしてみました。今回は日本語の設定もうまくいきました。
sudo apt install language-pack-ja sudo update-locale LANG=ja_JP.UTF8 sudo apt install manpages-ja sudo apt install manpages-ja-dev sudo apt install fontconfig
なお、WSL の Ubuntu には日本語フォントがインストールされていません。昔の Ubuntu は日本語フォントに Takao フォントが用いられてきましたが、Ubuntu 18.04 からは Noto フォントになりました。Noto Sans CJK JP がゴシック体、Noto Serif CJK JP が明朝体になります。
これらのフォントは apt で簡単にインストールすることができます。
sudo apt install fonts-not-cjk
もう一つ方法があって、WSL から Windows のフォントを利用することもできます。フォントのインストールは次のページが参考になると思います。
M.Hiroi は Windows に Noto フォントをインストールしてあるので、1 の方法を使わせてもらいました。有益な情報を公開されてる作者様に感謝いたします。
ところが、フォントの優先順位が Ubuntu 20.04 と Ubuntu 22.04 で異なっているようです。Ubuntu 20.04 で fc-match を実行すると Noto Sans CJK JP になったのですが、Ubuntu 22.04 では次のようになります。
$ fc-match msgothic.ttc: "MS ゴシック" "標準" $ fc-match sans msgothic.ttc: "MS ゴシック" "標準" $ fc-match serif msmincho.ttc: "MS 明朝" "標準" $ fc-match mono msgothic.ttc: "MS ゴシック" "標準"
フォントの優先順位は変更できると思うのですが、Linux のことは勉強不足でよくわかりませんでした。Noto フォントを使いたい場合、各々のアプリケーション (Tcl/Tk や Python/Tkinter など) でフォントを指定する、または、Windows 側のフォントを使わずに、Ubuntu 側にフォントをインストールしたほうが簡単なのかもしれませんね。