見出し画像

Notionでの乱数の作り方と活用術( ˘ω˘ )

この記事は、Notion 大学 Advent Calendar 2024 18日目の記事です。

昨日はShogoさんの記事「Notionフォーム活用」でした。
そういえばフォームはほぼ使ったことないなぁ。

どうもどうも、ねここです。
ずいぶん久しぶりの投稿になるのですが、今年もNotion大学のAdvent Calendarをやるぜということで、3列目センターをいただくことにしました(カレンダー視点)。


ねこことNotion大学Advent Calendar

本題に入る前に、ちょいとぼくがNotion大学に吸い込まれた経緯をひと語りさせてください(いらんぜ、という人は上の目次から本題に飛んでくださいな)。

実はぼくがNotion大学に入ったきっかけが昨年のAdvent CalendarでToshiさんとつぶあんさんの記事を見たことでした。

これらの記事を読んで「Notion大学おもしろそう」と思ってしまい、気づいたら入ってました。
昨年Advent Calendarに導かれてしまったので、今年は書く側で読みに来た人をNotion大学に引きずり込もう導こうと思います。もう1年か。
ぼくのこの記事をきっかけに、誰かひとりぐらい引きずり込まれてきたらおもしろいなぁと思ってます。
興味ある方は、ぜひ以下リンクから吸い込まれてきてください。

Notionで乱数を使う

さて、本編です。

Notionを使い込んでいくと、
「一定間隔でデータベースアイテムを並べ替えたい」
「定期的に表示するアイテムをランダムに変更したい」
という要望が出てくることがあります。ぼくはしょっちゅうです。

しかし、Notionには乱数を出力する関数が標準で実装されていません。
需要は絶対に高いと思うのでこれからも実装に期待ではあるのですが、無いならアリモノでなんとかすればいいじゃないというマリーアントワネットでも言わなさそうな強い気持ちを持って、この1年ぐらい試行錯誤を積み重ねてきました。
今回はその成果を惜しげもなく、読みに来たみなさんに投げつけていきます。

ちなみに、ちょうど1年ぐらい前にもNotionで乱数を扱う記事を書いたんですが、今回はそのアップデート版です。基本的な考え方は変わってないですが取り扱う変数はいろいろ変わってます。

また、実際に触れるテンプレートを副教材として用意しています。
この記事を読みに来ている人はだいたいNotionのアカウントを持っていると勝手に思っているので、ぜひ自分の環境に複製して眺めながらお読みください。

乱数の数式(すぐ使いたい人向け)

今回ご紹介する乱数の数式をまずご紹介します。
「仕組みとか別にいらんので、とりあえず使いたい」という人は、数式プロパティを追加してこちらの数式をペタリと貼っていただければ使えます。

/* なんかいい感じに乱数っぽい動きをする数式(1日1回更新) */
floor( 
	(sqrt( toNumber( replaceAll( substring( id(), 16, 32), "[a-z]", "") ) )
		/
		ln(month(now()) * 100 + date(now()))
	)
	* (10^4) % (10^2)
)

こちらを数式プロパティに貼り付けると、1日単位更新されるで0~99の乱数が発行されます。
乱数の範囲を変えたい? 更新頻度を変えたい?
そんな方はぜひここから先の解説をお読み下さいw

数式の解説

それではこの乱数の数式の解説をしていきます。

乱数を生成する数式のコンセプト

どうやってNotion数式で乱数っぽい動きをさせるかを追求した結果たどり着いたのは「絶対に割り切れない割り算をして、小数部を一部分だけ拾ってくる」という手法でした。ねここ式乱数の作り方は、言い換えると「小数部分がいい感じに乱れる割り算の作り方」です。
ここがブレてなければ、正直どんな数式でも問題ないのです。

①分子部分:ページIDから(ほぼ)ユニークな数値を作る

まずは、この部分から解説していきます。

toNumber( replaceAll( substring( id(), 16, 32), "[a-z]", "") )

これは「ページIDから後ろ16桁を取り出し、abcdefを取り除いて、数値として扱えるようにする」処理をしています。

ページIDは「ページ作成時に自動で割り当てられる、16進数で32桁の識別用ID」です。このIDは他のページIDと絶対に被らないことが保証されているので(おそらく全ユーザーの範囲で)、ページ毎のユニークな値として使えます。

赤枠の部分がページID

ページIDを取得する関数 id を使うことでこれは取得できます。
上の例だと「14c918bc3ef780e893edc63d964afa19」を返してくれます。

【余談】
「たかだか32桁で作成したページを全部網羅できるのマジ?」って思った方もいると思います。ですが16進数32桁はわりと想像を超えてくる程度にはでかい数で、具体的には0~340澗2823溝6692穣0938𥝱4634垓6337京4607兆4317億6821万1455まで表すことができます。1兆の1兆倍の1兆倍よりも大きいです。宇宙爆誕から1秒間に1万ページ作成してたとしても全然余ります。

このままでもいいんですが、桁数が大きすぎると扱いづらかったりするので、文字列の任意の部分を取り出す関数 substring を使って、後ろ16桁だけを使うことにしています(ここはみなさんの自由でいいと思います)

substring( id(), 16, 32)

は「id関数で取得した値16文字目から32文字目の前までを抜き取る」という処理になります。”の前"の部分が慣れてないとちょっとクセを感じるかも。
「14c918bc3ef780e893edc63d964afa19」が「93edc63d964afa19」になります。

また、16進数のままだと数値としては扱いずらいので、文字を置換する関数 replaceAll を使ってアルファベット部分を抹消します。

replaceAll( substring( id(), 16, 32), "[a-z]", "")

は「後ろ16桁取り出したページIDに含まれているアルファベットを空白に変換、つまり消す」という処理をします。
「93edc63d964afa19」が「936396419」になります。
無事に数値になったヤッターと一見なりますが、実はこの時点ではまだこの値はまだ文字列です。そのまま数値として扱おうとするとエラーが返ってきます。とんだ罠です。
なので、文字列を数値に変換する関数 toNumber を使って数字として扱えるように処理をします。ヤッター。

最後にもうひとつ、変換した数値がまだ大きい場合に備えて、平方根を求める関数 sqrt を加えて、数値を圧縮しています。

sqrt( toNumber( replaceAll( substring( id(), 16, 32), "[a-z]", "") ) )

「小数部からぶっこ抜いてくる」というコンセプトなので、整数部分の桁が大きいと不都合な動作になる可能性があったので入れています。正直入れなくてもいいかもですが、まぁここはちょっとした味付けということでひとつ。
「936396419」が「30600.59507591315」になりました。
この値が割り算の分子になります。

え、ここまでで3000文字? マジ?

②分母部分:現在日時から分母部分を作る

次は割り算の分母部分を解説していきます。

ln(month(now()) * 100 + date(now()))

分子部分に比べるとシンプルに見えますね。
ひとまず、「なんだこれ?」っておそらくなるであろう ln に囲まれている部分から解説していきます。

month(now()) * 100 + date(now())

これは今日の日付をいい感じに3桁or4桁の数値にする処理です。
今日は12月18日なので「1218」になります。1月1日なら「101」ですね。
日付を数値として扱えるようにする処理は結構便利なので、これはこれで覚えておくとNotionに限らず使えることもあるかもです。

さて、コンセプトである「絶対に割り切れない割り算」を実現するためのキモの部分になります。肝でもありますし、キモくもありますw

ln 関数は、ある数値の自然対数を返す関数です。
これが何をするのか具体的なところは正直どうでもよくてw、この関数に入れることで割り切れない割り算を作るためのいい感じの小数部分を含む数字が生成されると思ってくれてOKです。

【余談】
ちょっとだけ具体的に説明すると、引数に入れる数値がネイピア数 e (2.718281828459045 … )の何乗か、を返す処理です。

たとえば ln(2.718281828459045) であれば「2.718281828459045 は 2.718281828459045 の何乗?」なので 1 が返ってきます。

ところでなんでnotionにこの関数が実装されてるんですかね….w ぼく以外でこれ使ってる人を見たことがないw

たとえば今日の日付である12月18日であれば、ln(1218) で「7.10496544827」が返ってきます。

③割り算をして小数部の一部を切り取る

さぁ、割り算の準備ができました。割ってみましょう。

30600.59507591315 / 7.104965448 = 4306.93087794886

無事に割り切れませんでした。おそらくこの方法で作った割り算でぴったり割り切れることはほぼないはずです。割り切れたら奇跡です。

で、この割り切れなかった小数部分をもぎにいきます。整数部分はゴミです。

floor(割り算した結果	* (10^4) % (10^2) )

割り算した結果を10の4乗倍(10000)して、10の2乗(100)で割った余りを求めて、最後に小数部を切り捨てています。

4306.93087794886 * (10^4) = 43069308.7794886
43069308.7794886 % (10^2) = 8.779488600791
floor(8.779488600791) = 8

つまり小数第3位~4位の部分を抜いています。この数式をいじることでどの部分を抜き取るかを変えることができます。

// 小数第1位~2位を抜き取る
floor(4306.93087794886) * (10^2) % (10^2) ) = 93
// 小数第2位~3位を抜き取る
floor(4306.93087794886) * (10^3) % (10^2) ) = 30

どこを抜き取るかはお好みでどうぞ。
ということで、めでたく乱数(っぽい数値)を生成することができました。
やったね!

乱数の使いどころ

冒頭でも書きましたが、Notionで乱数を使いたいシーンはそこそこあります。ぼくはとりあえず数式だけ組み込んでおいて、そのうち使い道を考えるレベルですw
パッと思いつく使い道を並べてみましょう。

  1. ランダムで並び順を変える
    ぼくの場合は作成したメモを定期的に見直す仕組みとして、いろんなメモがPOPして代わる代わる目に入る、という形で使っています。
    データベースの「読み込み制限」と併用したりもします。

  2. ランダムで表示ON/OFFを切り替える
    締め切りを設定しない「いつかやる」タスクを10%の確率(0~99の乱数範囲で90以上が出たら)でメインタスクリストに顔見せさせる、みたいな使い方をしてたりします。応用範囲はかなり広い。

Notionでなにかゲームを作ろうとしたときはわりと乱数の出番があったりするかもですね。
いい使い方あればむしろ教えてください!

乱数式のアレンジレシピ

ここまで1日1回数値が更新される0~99の乱数式を使ってきたわけですが、当然「異なる更新間隔で使いたい」「0~99以外の範囲で使いたい」というケースもあります。
どこをいじれば変えられるのか、というアレンジレシピをご紹介します。

①更新間隔を変えたい

notionでは時刻を1分単位で取得することができます。なので仕様的には1分間隔で乱数を更新することができます(ただしページが自動更新されるわけではないので、ページを手動でリロードしたりデータベースを何かしら更新することで書き換わる感じです)。
時間周りは割り算分母の部分で操作しているので、ln関数の中身をちょちょいと書き換える感じです。

いくつかアレンジパターンを書いてみましょう。

/* 基本形:1日1回更新 */
floor( 
	(sqrt( toNumber( replaceAll( substring( id(), 16, 32), "[a-z]", "") ) )
		/
		ln(month(now()) * 100 + date(now()))
	)
	* (10^4) % (10^2)
)
/* アレンジ:1時間間隔で更新 */
floor( 
	(sqrt( toNumber( replaceAll( substring( id(), 16, 32), "[a-z]", "") ) )
		/
		ln( date(now()) * 24 + hour(now()) )
		/* ↑ 毎月1日0:00起点で1時間毎にカウントアップしています */
	)
	* (10^4) % (10^2)
)
/* アレンジ:1分間隔で更新 */
floor( 
	(sqrt( toNumber( replaceAll( substring( id(), 16, 32), "[a-z]", "") ) )
		/
		ln( date(now()) * 1440 + hour(now()) * 60 + minute(now()) )
		/* ↑ 毎月1日0:00起点で何分経過してるかを算出してます */
	)
	* (10^4) % (10^2)
)
/* アレンジ:10分間隔で更新 */
floor( 
	(sqrt( toNumber( replaceAll( substring( id(), 16, 32), "[a-z]", "") ) )
		/
		ln( date(now()) * 144 + hour(now()) * 6 + floor( minute(now()) / 10 ) * 10 )
		/* ↑ 毎月1日0:00起点で10分経過毎にカウントアップしています */
	)
	* (10^4) % (10^2)
)

人によって心地いい求め方があると思いますので、好きにアレンジしてもらって大丈夫ですが、ln関数の引数が0になるとエラーになるので、1以上(できれば2以上)になるように調整しましょう。上の数式ではdate関数が必ず1以上を返すので0にならないようになっています。

②乱数の範囲を変えたい

乱数の範囲を0~99の100通り以外にする方法のアレンジレシピです。

/* 基本形:0~99 */
floor( 
	(sqrt( toNumber( replaceAll( substring( id(), 16, 32), "[a-z]", "") ) )
		/
		ln(month(now()) * 100 + date(now()))
	)
	* (10^4) % (10^2)
)
/* アレンジ形:0~999 */
floor( 
	(sqrt( toNumber( replaceAll( substring( id(), 16, 32), "[a-z]", "") ) )
		/
		ln(month(now()) * 100 + date(now()))
	)
	* (10^4) % (10^3) /* 最後の10^2(100で割った余り)を10^3(1000で割った余り)に変更 */
)

数式の最後の部分、余りを算出する %演算子の後ろの部分で範囲を調整しています。

// 基本形:0~99(小数第3位~4位を抜き取る)
floor(4306.93087794886) * (10^4) % (10^2) ) = 8
// アレンジ:0~999(小数第2位~4位を抜き取る)
floor(4306.93087794886) * (10^4) % (10^3 ) = 308

10^1 にすれば0~9の10通りですし、10^4 なら0~9999の1万通りになりますね。

また、例えば6面サイコロを再現したい、みたいなこともあると思います。
それもいくつで割って余りを出すかの部分を書き換えることで対応できます。

/* アレンジ形:1~6 */
floor( 
	(sqrt( toNumber( replaceAll( substring( id(), 16, 32), "[a-z]", "") ) )
		/
		ln(month(now()) * 100 + date(now()))
	)
	* (10^4) % 6 + 1 /* 6で割った余り(0~5)の1を足して1~6にする */
)

この数式であれば1~6を出力することはできます。
ただ勘がいい人は気づくかと思いますが、確率に若干偏りがあり、小さい数字が出る確率はほんの少しだけ上がります。Notionで乱数使うレベルではほぼ無視できるレベルではありますが、気になる人はいろいろアレンジを試してみてください。
ねここ的な解法はありますが、それを書くには余白────はあるけど、時間が足りない……!(需要あれば別記事にするかもしないかも)

あとがき

ということでNotionで乱数を生成する方法のご紹介でした。
2025年も数式をいじりつつ、乱数以外のところをいろいろやっていきたいと思ってはいるけれど、結局乱数のブラッシュアップをしてそうな気がしますねw

次回は「りり」さんですね。わくわくしてマテ!


いいなと思ったら応援しよう!