たのしい関数:Current関数
2018年12月13日のLibreOffice Advent CalendarはnogajunさんのLibreOfficeを使ってるならTelegram messengerの「LibreOffice-JA」グループに入ろうでした。(この辺の紹介はEnokiさんのマネ)
この記事はLibreOffice Advent Calendar 2018の14番目の記事です。
Advent Calendar初参戦です。
本題が出てくるまでが非常に長いので注意。
関数に置き換える
こんな感じで関数が定義されていたとする。
Function Add(arg1 As Double, arg2 As Double) As Double Add = arg1 + arg2 End Function Function Subtract(arg1 As Double, arg2 As Double) As Double Subtract = arg1 - arg2 End Function Function Multiply(arg1 As Double, arg2 As Double) As Double Multiply = arg1 * arg2 End Function Function Divide(arg1 As Double, arg2 As Double) As Double Divide = arg1 / arg2 End Function
いろんな式を演算子を使わずに、上記ユーザー定義の物を含めて関数だけを使って書いてみる。
答えになる式は、関数の引数のための括弧は使っていいけど、演算の優先順位変更のための括弧は使ってはいけない。
例題
1問目
= 3*5+4*1
は
= Add (Multiply(3,5),Multiply(4,1))
となる。
2問目
RSA暗号から。
= Mod((2^5)^29,7*5)
は
= Mod(Power(Power(2,5),29),35)
となる。なお本来の答えは2だが、浮動小数点数は10進数15桁くらいしか保存しないせいか、Calcはこの答えを0とする、と記事を書きながら検証していてわかった。
3問目
いつぞやに話題になったExcel列名変換を自分で試しにCalcの数式で解いてみていた。多分標準関数がもうすでにあると思う。
= (26^Len("XFD")-26)/(26-1) + Decimal(Base(Decimal("XFD",36) - (36^Len("XFD") - 1)/35 * 10,36),26) + 1
は
= Add(Add(Divide(Subtract(Power(26,Len("XFD")),26),Subtract(26,1)),Decimal(Base(Subtract(Decimal("XFD",36), Multiply(Divide(Subtract(Power(36,Len("XFD")),1),35),10)),36),26)),1)
となります。発狂しそうだった。
逆ポーランド記法
さて、これら答えの式の関数名をその引数の括弧の後ろに持っていくようにする。もうCalcの数式としては使えないのでわかりやすくするため先頭の=は外しておく。
1問目
= Add (Multiply(3,5),Multiply(4,1))
は
((3,5)Multiply,(4,1)Multiply)Add
括弧やカンマを半角スペースに置き換える。2つ以上連続するスペースは1つにする。
3 5 Multiply 4 1 Multiply Add
となる。この書き方を逆ポーランド記法とか後置記法という。
2問目
= Mod(Power(Power(2,5),29),35)
は
(((2,5)Power,29)Power,35)Mod
で
2 5 Power 29 Power 35 Mod
3問目
自分で出しておいてなんだけど、3問目はもうやりたくない→やった(2018-12-15)
(((((26,("XFD")Len)Power,26)Subtract,(26,1)Subtract)Divide,(((("XFD",36)Decimal, ((((36,("XFD")Len)Power,1)Subtract,35)Divide,10)Multiply)Subtract,36)Base,26)Decimal)Add,1)Add
で
26 "XFD" Len Power 26 Subtract 26 1 Subtract Divide "XFD" 36 Decimal 36 "XFD" Len Power 1 Subtract 35 Divide 10 Multiply Subtract 36 Base 26 Decimal Add 1 Add
オマケ:引数が2個じゃないとき
ちなみに、引数が2つじゃないときもやり方は同じで
= Weekday(Date(Year(Today()), Mod(Month(Today()), 12)+1, 1),1)
なら
(((()Today)Year, (((()Today)Month, 12)Mod,1)Add, 1)Date,1)Weekday
こうなって
Today Year Today Month 12 Mod 1 Add 1 Date 1 Weekday
こう!
こうやって出来た謎の文字列を、以下の方法によって処理する。
空のリストを準備し、文字列を左から順に最後まで読んでいく。それぞれの内容に対して以下の操作を行う。
1. 関数名以外が来たらリストの先頭に挿入する。結果それまでに挿入されてきたものは後ろに1個ずれる
2. 関数名が来たときは、その関数名に対応した処理を行う。多くの関数名は「必要な個数の引数を先頭から取り出して処理の結果を先頭に追加する。」となっている。取り出すときは先頭のものから取り出すとその内容はリストから消え、後ろにあったものが前にずれて出てくる。
例題の場合
1問目
3 5 Multiply 4 1 Multiply Add
リストは最初の3で
3
次の5で
5 3
次のMultiplyで
15
次の4で
4 15
次の1で
1 4 15
次のMultiplyで
4 15
次のAddで
19
よって答えは19となる。
2問目
最初の2で
2
次の5で
5 2
次のPowerで
32
次の29で
29 32
次のPowerで32の29乗をWolfram Alphaに聞いたら
44601490397061246283071436545296723011960832
で35で
35 44601490397061246283071436545296723011960832
最後のModで余りを求める。
2
答えは本来は2となるわけだ。
おまけの場合
以下同様に
Today Year Today Month 12 Mod 1 Add 1 Date 0 Weekday
は
最初のTodayで2018-12-14にあたる
43448
次のYearで
2018
またTodayがあるから
43448 2018
Monthで
12 2018
12が来て
12 12 2018
Modが来て
0 2018
1が来て
1 0 2018
Addが来て
1 2018
1が来て
1 1 2018
Dateが来て
43101
1が来て
1 43101
Weekdayが来る。
2
ということで2018年1月1日は月曜日。2019年ではありません。来月求めようとしたけど、今年なのか来年なのかの場合分けが複雑化するので断念。
ここから本題
Current関数は引数を取らない関数で、逆ポーランド記法を処理するときに登場すると、そのときにリストの先頭にあったものを取り出して(pop)2回先頭に突っ込む(push)。つまり先頭が複製される。
=1+3*Current()
は
1 3 Current Multiply Add
だから
1
こうして
3 1
こうなって、そしてCurrentで以下のようになって
3 3 1
Multiplyだから
9 1
Addで
10
ですが、
=1+Current()*3
は
1 Current 3 Multiply Add
だから
1
1 1
3 1 1
3 1
4
となる。Currentが登場する位置によって答えが異なる。乗算の可換性?何それ食えるの?
=1+3*Current()*Current()
は
1 3 Current Multiply Current Multiply Add
なので
1
3 1
3 3 1
9 1
9 9 1
81 1
82
前のCurrentと後ろのCurrentで値が違う、という話。
短絡評価
通常は全ての引数が順番に評価されるから
=Mod(4+Current(),Current())
の場合
4 Current Add Current Mod
というように引数間もその影響を受ける。答えはもちろん0
ただ、例えばIf関数(関数ということにしておく)の第一引数がtrueであったときは第三引数、falseとなる値だったときは第ニ引数の式は評価されない。だから
=If(0,4 + Current(), Current())
の後ろのCurrent()は8ではなく0になる。VB.NETなんかではIf演算子とIIf関数とかでよく違いが語られている話だ。
ちなみに、Ifs()関数とかが短絡評価しないんだが?というバグレポがある。
動機というかなんというか
理解されなかったり誤解されやすい関数でいつか説明したいとは思ってた。どのタイミングでどうやって切り出すか迷ってたけどいい機会なので。英辞郎によるto dateの意味
明日のLibreOffice Advent Calendar 2018はMucky999さんの「LibreOffice Conference 2018 Tirana の話」です。期待しています。