M.Hiroi's Home Page

Memorandum

プログラミングに関する覚え書や四方山話です。
[ Home | 2024年 1月, 4月, 5月, 6月 ]

2024 年 6 月

6月3日

●Web フォント

通常、Web ブラウザはパソコンなどの端末にインストールされているフォントを使って文字を表示します。これに対し、Web サーバー上にフォントを設置し、それを使って文字を表示する方法があります。これは 2018 年に CSS3.0 fonts module として標準化されました。Web サーバー上のフォントを Web フォントといいます。

日本語は文字数が多いため、Web フォントの普及は欧米よりも遅れていたのですが、近年になって日本語 Web フォントを提供するサービスも増えてきました。特に、Google 社のサービス Google Fonts を使うと、簡単に Web フォントを無料で利用することができます。もちろん、Noto フォントもあります。

Google Fonts の基本的な使い方は簡単です。

  1. フォントを選択する
  2. Get embed code をクリックする
  3. 使用するフォントの Weight などを指定する
  4. 表示される HTML と CSS のコードをコピペする

実際に、Noto Sans Japanese と M PLUS 1p を試してみました。本ページの <head> ~ </head> に以下のコードを追加しました。

リスト : Google Fonts の使用

  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  <link href="https://fonts.googleapis.com/css2?family=M+PLUS+1p&family=Noto+Sans+JP&display=swap" rel="stylesheet">

  ... 略 ...

  <style>
    .noto-sans-jp {
      font-family: "Noto Sans JP", sans-serif;
      font-optical-sizing: auto;
      font-weight: 400;
      font-style: normal;
    }
    .m-plus-1p {
      font-family: "M PLUS 1p", sans-serif;
      font-weight: 400;
      font-style: normal;
    }
  </style>

どちらも綺麗なフォントだと思います。M.Hiroi's Home Page のメインフォントとして、Web フォントを使ってみるのも面白いかもしれませんね。

●参考 URL

  1. Google Fonts
  2. Webフォント - Wikipedia

2024 年 5 月

5月11日

●クロージャの落とし穴

昔は Lisp / Scheme や関数型言語の専売特許だった「クロージャ (closure)」ですが、最近ではほとんどの言語でサポートされるようになりました。クロージャはとても便利な機能ですが、副作用 (破壊的な代入操作) がある言語で使うときには注意が必要です。Python での例を示しましょう。

>>> adder = [lambda y: x + y for x in range(5)]
>>> for f in adder: print(f(1))
...
5
5
5
5
5

Python の繰り返しは変数 y の環境を 1 回だけ生成し、繰り返しのたびに y の値を破壊的に書き換えています。この場合、クロージャに保存される環境はみな同じものであり、その値を破壊的に書き換えているので、 リストに格納されたクロージャを評価するとすべて同じ値 (5) になるわけです。

これは次のように関数 make_adder を経由すると上手く動作します。

>>> def make_adder(x): return lambda y: x + y
...
>>> adder = [make_adder(x) for x in range(5)]
>>> for f in adder: print(f(1))
...
1
2
3
4
5

関数を呼び出すたびに引数 x の環境は新しく生成され、それがクロージャに保存されます。したがって、adder に格納されたクロージャを評価すると 1, 2, 3, 4, 5 となります。なお、再帰定義でも同じことができます。

>>> def make_adder1(x, a):
...     if x >= 5: return a
...     a.append(lambda y: x + y)
...     return make_adder1(x + 1, a)
...
>>> adder1 = make_adder1(0, [])
>>> for f in adder1: print(f(1))
...
1
2
3
4
5

Python の場合、もう一つ注意点があります。次の例を見てください。

>>> def make_odd_gen():
...     n = -1
...     def _gen():
...         n += 2
...         return n
...     return _gen
...
>>> g = make_odd_gen()
>>> g()

... 略 ...

UnboundLocalError: local variable 'n' referenced before assignment

make_odd_gen() は奇数列を生成する関数を返します。Python の場合、局所関数の中から外側の変数の値を参照することはできますが、その値を書き換えることはできません。_gen() の中で変数に値を代入するとき、その変数が _gen() の中で見つからない場合、Python は _gen() の中に変数 n を新しく生成します。ところが、変数 n はまだ初期化されていません。このためエラーが発生するのです。

この場合、_gen() の中で変数 n を nonlocal 宣言すると上手く動作します。

>>> def make_odd_gen1():
...     n = -1
...     def _gen():
...         nonlocal n
...         n += 2
...         return n
...     return _gen
...
>>> g = make_odd_gen1()
>>> g()
1
>>> g()
3
>>> g()
5
>>> g()
7
>>> g()
9

nonlocal 宣言により、_gen() の中で局所変数 n の生成を抑制し、外側の変数 n の値を読み書きすることができます。

他の言語、たとえば Lisp / Scheme では局所変数を定義する let, let* などが用意されていて、変数のスコープを明確に定義することができます。ところが、Python にはそのような機能はありません。まだ nonlocal が実装されていない時代では、n をリスト (1 次元配列) にして、その要素を書き換えることで代用していました。Python で局所関数やクロージャを使うときにはご注意くださいませ。


2024 年 4 月

4月12日

●Hy (Hylang)

Hy (Hylang) は Python で動作する Lisp 方言のひとつです。プログラムを S 式 (リスト) で記述するところは Lisp (Clojure とよく似ている) なのですが、データ構造は Python のものを使うことを前提としているようで、Lisp の cons, car, cdr に相当する関数がないところがユニークです。そのかわり、Python の機能 (ライブラリ) は簡単に利用することができますし、Common Lisp ライクなマクロも用意されています。

Hy は pip で簡単にインストールすることができます。

python3 -m pip install hy

Hy を実行するときはシェルで hy と入力してください。

$ hy
Hy 0.28.0 using CPython(main) 3.10.12 on Linux
=>

メッセージとプロンプト '=>' が表示され、対話モードで Hy が起動されます。終了するときは (quit) または CTRL-D を入力してください。

簡単な実行例を示しましょう。

=> (print "hello, world")
hello, world
=> (+ 1 2 3)
6
=> (setv a [1 2 3 4 5])
=> a
[1 2 3 4 5]
=> (.append a 6)
=> a
[1 2 3 4 5 6]
=> (.pop a)
6
=> a
[1 2 3 4 5]

=> (a.append 6)
=> a
[1 2 3 4 5 6]
=> (a.pop)
6
=> a
[1 2 3 4 5]

=> (get a 0)
1
=> (get a 4)
5
=> (setv (get a 2) 10)
=> a
[1 2 10 4 5]

関数呼び出しは Lisp と同じく (関数 引数 ...) になります。変数の代入は setq や setf ではなく setv を使います。Hy の場合、角カッコ [] は Python のリスト (1 次元配列) に変換されます。要素はカンマではなく空白で区切ってください。

Python のメソッドは obj.method(args, ...) で呼び出しますが、Hy では (.method obj args ...) という形式になります。Hy ver 0.28 では、(obj.method args ...) でも呼び出すことができるようです。リストの要素は get で読み込み、setv と get で書き込みを行います。これは Common Lisp の aref, setf と同じですね。

=> (defn fact [n] (if (= n 0) 1 (* n (fact (- n 1)))))
=> (fact 9)
362880
=> (fact 10)
3628800
=> (fact 11)
39916800
=> (for [x (range 10)] (print (fact x)))
1
1
2
6
24
120
720
5040
40320
362880

関数定義は defn で、引数は [ ] で囲みます。for は Python の for 文と同じです。

(for [var iterable] body ...)

range は Python の range と同じで iterable を返します。Python と同様に iterable は関数 list で Python のリストに変換できます。

=> (range 10)
(range 10)
=> (list (range 10))
[0 1 2 3 4 5 6 7 8 9]
=> (list (range 0 10))
[0 1 2 3 4 5 6 7 8 9]
=> (list (range 0 10 2))
[0 2 4 6 8]

なお、リストの生成には内包表記を使ったほうが簡単です。Hy では lfor を使います。

(lfor var iterable body)
=> (lfor x (range 10) x)
[0 1 2 3 4 5 6 7 8 9]
=> (lfor x (range 10) (* x x))
[0 1 4 9 16 25 36 49 64 81]
=> (lfor x (range 3) y (range 3) [x y])
[[0 0] [0 1] [0 2] [1 0] [1 1] [1 2] [2 0] [2 1] [2 2]]
=> (lfor x (range 10) :if (= (% x 2) 0) x)
[0 2 4 6 8]

条件式は :if で指定します。詳細は Hy のドキュメントをお読みくださいませ。

モジュール (ライブラリ) の読み込みは import で行います。

=> (import math)
=> (math.sqrt 2)
1.4142135623730951
=> (import fractions [Fraction])
=> (setv x (Fraction 1 2))
=> x
(Fraction 1 2)
=> (setv y (Fraction 1 3))
=> y
(Fraction 1 3)
=> (+ x y)
(Fraction 5 6)
=> (- x y)
(Fraction 1 6)
=> (* x y)
(Fraction 1 6)
=> (/ x y)
(Fraction 3 2)

(import module) は Python の import module と同じ、(import module [name]) は from module import name と同じです。fractions は分数 (有理数) を扱うモジュールで、Fraction は分数を表すクラスです。fractions を import することで、Hy でも分数の計算を行うことができます。

このように、見た目は Lisp ライクな Hy ですが、その中身は Python 寄りのように感じました。Lisp は List Processor の略で、連結リストを操作するのが得意な言語です。Hy で「リスト遊び」をするのは、Lisp ほど簡単ではなさそうですが、気軽に Python の機能を利用できるのは便利ですね。興味のある方はいろいろ試してみてください。

●参考 URL


4月10日

●貴金属数

数学の世界では、二次方程式 \(x^2 - n x - 1 = 0\) の正の解を「貴金属数 (metallic number)」といいます。

\( x = \dfrac{n + \sqrt {n^2 + 4}}{2} \)

n 番目の貴金属数を「第 n 貴金属数」といいます。一番有名なのは第 1 貴金属数で、「黄金数 (golden number)」といいます。このほかに、第 2 貴金属数の「白銀数 (silver number)」や第 3 貴金属数の「青銅数 (bronze number)」などがあります。

\( \begin{array}{lll} n = 1 & \varphi = \dfrac{1 + \sqrt 5}{2} & \fallingdotseq 1.618\\ n = 2 & \tau = 1 + \sqrt 2 & \fallingdotseq 2.414 \\ n = 3 & \xi = \dfrac{3 + \sqrt 13}{2} & \fallingdotseq 3.303 \end{array} \)

次の漸化式で生成する数列において、隣り合う二項の比は第 n 貴金属数に収束します。

\( \begin{cases} M_0 = 0 & \\ M_1 = 1 & \\ M_{k+2} = n M_{k+1} + M_k & \mathrm{if} \ k \gt 0 \end{cases} \)

n = 1 とすると「フィボナッチ数列」になりますね。つまり、フィボナッチ数列の隣接する二項の比は黄金数に収束します。n = 2 で生成される数列の数を「ペル数 (Pell number)」といい、隣り合う二項の比は白銀数に収束します。n = 3 であれば青銅数に収束します。

漸化式をプログラムするのは簡単です。Python で試してみました。

>>> def metallic_number(k, n, a = 0, b = 1):
...     if k == 0: return a
...     return metallic_number(k - 1, n, n * a + b, a)
...
>>> for n in range(1, 9):
...     for k in range(16): print(metallic_number(k, n), end=" ")
...     print("")
...
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610
0 1 2 5 12 29 70 169 408 985 2378 5741 13860 33461 80782 195025
0 1 3 10 33 109 360 1189 3927 12970 42837 141481 467280 1543321 5097243 16835050
0 1 4 17 72 305 1292 5473 23184 98209 416020 1762289 7465176 31622993 133957148 567451585
0 1 5 26 135 701 3640 18901 98145 509626 2646275 13741001 71351280 370497401 1923838285 9989688826
0 1 6 37 228 1405 8658 53353 328776 2026009 12484830 76934989 474094764 2921503573 18003116202 
110940200785
0 1 7 50 357 2549 18200 129949 927843 6624850 47301793 337737401 2411463600 17217982601 122937341807 
877779375250
0 1 8 65 528 4289 34840 283009 2298912 18674305 151693352 1232221121 10009462320 81307919681 660472819768
5365090477825
>>> for k in range(1, 10): print(metallic_number(k + 1, 1) / metallic_number(k, 1))
...
1.0
2.0
1.5
1.6666666666666667
1.6
1.625
1.6153846153846154
1.619047619047619
1.6176470588235294
>>> for k in range(1, 10): print(metallic_number(k + 1, 2) / metallic_number(k, 2))
...
2.0
2.5
2.4
2.4166666666666665
2.413793103448276
2.414285714285714
2.4142011834319526
2.4142156862745097
2.414213197969543
>>> for k in range(1, 10): print(metallic_number(k + 1, 3) / metallic_number(k, 3))
...
3.0
3.3333333333333335
3.3
3.303030303030303
3.302752293577982
3.3027777777777776
3.302775441547519
3.3027756557168324
3.302775636083269

貴金属数の一般項はフィボナッチ数と同様に求めることができます。興味のある方は拙作のページ Memorandum (数学編) 初等整数論 [5] をお読みくださいませ。


2024 年 1 月

1月1日

あけましておめでとうございます

旧年中は大変お世話になりました
本年も M.Hiroi's Home Page をよろしくお願い申し上げます


Copyright (C) 2024 Makoto Hiroi
All rights reserved.

[ Home ]