日々是好日

プログラミングについてのあれこれ、ムダ知識など

【LAMBDA関数】有効桁数で処理する関数を作る【LET関数】

どうも、けーしぃです。数年ぶりの更新です。

仕事柄、有効桁数を指定して処理する作業が多いのですが、エクセルでは桁数を自動で判別して処理する関数は用意されていません。

RoundUp, RoundDown, Round関数は桁数をユーザが指定する必要があるので、1,000円単位と10,000円単位の数字が混ざっていると、それぞれのセルで桁数を確認しないとなりません。

そこで、Microsoft 365、Excel 2021以降で導入されたLAMBDA関数とLET関数で、桁数を自動で判別し、有効桁数をユーザから指定すればいい感じに処理してくれる関数を自作しました。GoogleSpreadsheetでも使えます。

また、LAMBDA関数、LET関数が導入される以前はVBAでユーザ定義関数を作っていましたので、併せて掲載しておきます。

なお、ここで作成した関数は0以上の正の数でしか正しく適用できません。

負の数では切り捨て、切り上げ処理を逆にするなど、少し改良が必要です。

欲しい関数

先にどんな形で一撃処理したいのか考えます。

ユーザの指定が必要な項目は以下のとおりです。

  • 対象の数値を指定する ... v
  • 処理する桁数を指定する ... keta
  • 指定桁の次の桁の処理を指定する ... treat
    • 切り捨て -1, 四捨五入 0, 切り上げ 1の3つの値のみ取る

これらを引数としたYukoKetaという関数を作っていきます。

=YukoKeta(v, keta, treat)

たとえば、0.005555を有効桁数3桁、4桁目を四捨五入する場合は次のようになります。

=YukoKeta(0.005555, 3, 0) '有効数字3桁、4桁目を四捨五入(treat = 0)
'出力 0.00556

有効桁数の判別

まずは、対象の数字が何桁なのかをLOG10関数とCEILING.MATH関数で判別します。

LOG10関数で対象の数字が10の何乗に近いのか判別し、CEILING.MATH関数(天井関数)で最も近い整数に切り上げます。

CEILING.MATH(LOG10(ABS(v)))

対象の数値をそれぞれの関数で処理した結果は以下のとおりです。

ここで得られた天井関数の数字を用いてRound系関数の桁数を計算します。

'5555555を3桁で四捨五入処理したい場合
=ROUND(5555555, -4)
'出力5560000

この-4は、指定桁数-天井関数の出力で計算できます。

=ROUND(5555555, 3-7 = -4)

LAMBDA、LETで組み立てる

上記の関数でYukoKeta関数を組み立てていきます。

先に完成形を示します。改行を消してセルに張り付ければ使用できます。

=LAMBDA(v,keta,treat,
    LET(c,CEILING.MATH(LOG10(ABS(v))),k,keta-c,
        SWITCH(treat,-1,ROUNDDOWN(v,k),0,ROUND(v,k),1,ROUNDUP(v,k)))
    )
 (A1,3,0)

'(A1,3,0)より前の部分を YukoKeta という名前で定義しておく(「名前の定義」機能)
'すると、どのセルでもYukoKeta関数が使えるようになる
=YukoKeta(A1,3,0)

LAMBDA関数とLET関数の組み合わせはめちゃくちゃ強力で、最終出力を得るための途中計算をワンライナーに組み込めるようになります。

LAMBDA関数の部分

LAMBDA関数は、無名の関数を作ることができます。(何を言っている)

=LAMBDA(v,keta,treat,LET(...))(A1,3,0)
'A1の手前までが無名の関数
'無名とは、SUM関数やABS関数といったような名前がついていないという意味

プログラミングをやったことがないとなんとも理解しがたいものですが、ごめんなさい進めます。

まずLET関数の中身を無視します。

このv, keta, treatに対応するのが末尾のA1, 3, 0です。

このLAMBDA関数は、3つの引数(ひきすう, v, keta, treatの3つ)を取る関数として定義されています。

LET関数の部分

上記の3つの引数をLET関数に引き渡し、桁数の計算などをしていきます。

'c : 対象の数値の桁数の計算結果を持つ
'k : 指定された桁数-c の計算結果を持つ
LET(  c,  CEILING.MATH(LOG10(ABS(v))),  k,  keta-c,
    SWITCH(  treat,  -1,  ROUNDDOWN(v,k),  0,  ROUND(v,k),  1,  ROUNDUP(v,k)))
)

ここもなかなか異様な見た目をしていますが、c = CEILING.MATH(LOG10(ABS(v)))k = keta-cとして考えてください。

c, kは計算結果を一時的に保持しておく箱のようなもので、LET関数の5つめの引数であるSWITCH関数に引き渡すための数値を保持しています。

SWITCH関数は、1つめの引数であるtreat(A1, 3, 0 ののうち、0が入る)の値に応じて処理を分岐させる関数ですが、-1, 0, 1の3つの処理を行っています。

今回はゼロが入力されているため、ROUND(v,k)の結果が出力される形となります。

もし-1, 0, 1以外であれば、エラー値として#N/Aが出力されます。

名前の定義で登録する

上記完成形をYukoKetaという名前で登録します。

スクショはGoogle Spreadsheetですが、エクセルでも「名前の定義」で同じように登録できます。

これで、=YukoKeta(A1, 3, 0)というような形で関数呼び出しが可能になります。

VBAで組み立てる

VBAで関数を定義する場合は下記のとおりです。標準モジュールを作成して貼り付ければ完了です(急にぶん投げ)。

なお、マクロ有効化ファイルとするのが必要なので、エクセルファイルの保存時に.xlsx⇒.xlsmに拡張子を変更して保存してください。

'value: 処理する値
'keta : 有効桁数の指定
'treat: 1=次の位切上、0=次の位四捨五入、-1=次の位切捨
'正の値の切上げは+∞方向への丸め、切下げは-∞方向への丸め
'負の値の切上げは-∞方向への丸め、切下げは+∞方向への丸め
Public Function YukoKeta(ByVal value As Double, ByVal keta As Integer, ByVal treat As Integer) As Double

  If value = 0 Then
    YukoKeta = 0
    Exit Function
  ElseIf keta < 1 Or Abs(treat) >= 2 Then
    YukoKeta = Error(5)
    Exit Function
  End If

  Dim ceil As Long

  '先頭の数字がどこにあるか
  ceil = -Application.WorksheetFunction.Ceiling_Math(Application.WorksheetFunction.Log10(Abs(value)))
  
  If treat = 1 Then
    '指定桁の次を切上げ
    YukoKeta = Application.WorksheetFunction.RoundUp(value, keta + ceil)
  ElseIf treat = 0 Then
    '指定桁の次を四捨五入
    YukoKeta = Application.WorksheetFunction.Round(value, keta + ceil)
  Else
    '指定桁の次を切下げ
    YukoKeta = Application.WorksheetFunction.RoundDown(value, keta + ceil)
  End If
  
End Function

まとめ

LAMBDA関数とLET関数はプログラミングの機能が関数として切り出されたような存在で、それゆえ使い方が難しいですがめちゃくちゃ強力な関数です。

それぞれ単体で解説されている記事は数多ありますが、組み合わせるとこんなことができるという記事があまりなかったので書いてみました。

プログラミングを勉強されたことがある方は、VBA無しでいろいろな関数を作ることができるかと思います。

AndroidでMiAuth実装してみた

リポジトリ

AndroidでMiAuthを実装しました。

手っ取り早くソース寄こせって人はこちらから。 developmentブランチです。

あと、かなりクラスを行ったり来たりするので、コード表示しながら読んでいただければ幸いです。

github.com

Misskey Hub

解説では明示されていませんが、カスタムスキーマは現在受け付けておらず、httpsに統一しているそうです(しゅいろママが言ってた)。

misskey-hub.net

トークン取得フロー

MiAuthは、次のようなイメージでアクセストークンを取得します。 OAuth2は未実装なので、詳細については言及しません。

さらに詳細を文字起こしにするとこんな感じ。

(一応MiAuth想定のフロー)
認証開始
 -> ViewModel#startAuth
 -> AuthUseCase#startAuth
 -> ユーザによりアプリ連携が許可される
 -> Activity#onNewIntentでコールバックURLのキャッチ
 -> RegisterAppCallback#onRegistered で受信する
 -> Interactor#requestToken
 -> トークンが返ってくる(成功の場合)
 -> ViewModel で実装し Interactor に渡されている AuthResultCallback を叩いてトークンを ViewModel に渡す
 -> ViewModel から PreferenceUseCase 等を通じてトークンを保存する

インターフェース定義

AuthActivityAuthActivityViewModelは認証フローの詳細を知らず、認証サーバとのやりとりはすべてAuthUseCaseを実装した各種インタラクタが実行します。 ViewModel はほとんどの場合、Activityからのイベントをインタラクタに通知するだけです。

また、MVVM を採用しているので、ViewModel は各種 UseCase を集約しています。

AuthUseCase

MiAuth以外にも対応できるようにsealed interfaceで定義しました。 sealed interfaceとすることで、AuthUseCaseを次のようにobject句で実装しようとしてもエラーになります。

また、どんな認証フローでもstartAuthを叩くことでフローを開始するように縛ります。

fun foo() {
  val XxxAuthInteractor = object : AuthUseCase {
    ...
  }
}
//-> This type is sealed, so it can be inherited by only its own nested classes or objects

AuthUseCaseは次のとおり定義します。

sealed interface AuthUseCase {
  fun startAuth(context: Context, ticket: AuthTicket, resultCallback: AuthResultCallback): RegisterAppCallback
  fun onDismiss()
  interface MiAuthUseCase: AuthUseCase
  interface OAuth2UseCase: AuthUseCase
}

//ユーザがアプリ連携を許可した際に必要なコールバック
//フローにActivity#onNewIntentが絡むため、何かしらの形でインタラクタに通知する仕組みが必要
interface RegisterAppCallback {
  suspend fun onRegistered(callbackIntent: Intent)
  suspend fun onFailed(err: Exception)
}

//トークンをリクエストした際のサーバのレスポンス
//取得に成功した場合、tokenが格納される
sealed interface AuthResultCallback {
  fun onAuthFinish(ticket: AuthTicket, token: Token)
  fun onFailed(err: Exception)
  interface MiAuthResultCallback: AuthResultCallback
  interface OAuth2ResultCallback: AuthResultCallback
}

AuthTicketは各種パーミッションやホスト情報等をまとめた単なるデータクラスです。 こちらもsealedで定義します。

github.com

MiAuthInteractor と OAuth2Interactor

AuthUseCaseを実装した具象クラス。 このように認証方式ごとにAuthUseCaseを実装したクラスを作ることで、startAuthを叩くだけで異なる認証フローを起動できるようにしています(たぶん)。

また、RegisnterAppCallbackInterector#startAuth にて、AuthResultCallback は ViewModel にて実装しています。

MiAuthInteractor github.com

AuthActivityViewModel github.com

雑なまとめ

  • 一部甘い部分(クラス変数にしている部分とか)があるけどそこはスルーで。
  • 別にMiAuthでトークン取得するテストなだけならここまでやる必要はない。。。

まとメモ プライバシーポリシー

まとメモ プライバシーポリシー

三者に個人を特定できる情報を提供することはありません。

個人情報の管理には最新の注意を払い、以下に掲げた通りに扱います。

サポート時

サポートメールに、問題解決のための端末種類、OSバージョン等が本文として記述されます。

個人を特定できる情報は一切送信されません。

データ解析

アプリの利便性向上のため、匿名で個人を特定できない範囲で最新の注意を払い、アクセス解析をしております。

例えばアプリのクラッシュ時にどんな原因でクラッシュしたかを匿名で送信して、バグの素早い修正に役立たせております。

免責事項

利用上の不具合・不都合に対して可能な限りサポートを行っておりますが、利用者が本アプリを利用して生じた損害に関して、開発元は責任を負わないものとします。

ICL手術レポ

眼内コンタクトレンズ(ICL: Implantable Collamer Lens)手術を受けてきたのでレポです。

  • ICLとは
  • 手術前の視力や費用、モチベーションなど
  • 今現在の状況
  • 手術前後の流れと経過
    • 手術前検査(2021年12月11日)
    • 手術当日(1日目)(2021年12月29日)
      • 手術前
      • 手術
      • 手術後
      • 帰宅後
    • 翌日検診(2日目)
    • 3日目
    • 4日目
    • 5日目
    • 6日目
    • 7日目(2022年1月4日)
    • 8日目
    • 9日目
    • 10日目
    • 1週間検診(11日目)
    • 12日目
    • 13日目(現在)
  • まとめ
続きを読む

特定のIdを含む全経路を取得する with 閉包テーブル

閉包テーブルにて「特定のIdを含む根~葉までの全経路」を取得しようとしたら、意外とめんどうだったのでメモ。

最適化全然出来ない。

  • サンプルデータ
  • クエリ
    • 分解
      • 部分木の葉を取得する
      • 全経路を取得する
  • 参考
続きを読む

Android Room における隣接リストから閉包テーブルへのMigration

隣接リストにて作成してしまった木構造のデータを、深さ付きの閉包テーブルにマイグレーションしたのでそのときの備忘録です。

そもそもの木構造の表現方法には触れず、あくまでMigrationの手順についてのみ記載しています。

  • モチベーション
  • サンプルデータ
  • 実装
    • 下準備
      • スキーマをエクスポートする
      • テストメソッドを作成
    • テストを実装する
      • テスト用データベースの初期化
      • Migrations.migrate1To2の実装
      • 実行結果の確認
    • 本番環境への移植
    • 所感
  • 参考としたページ
続きを読む

CoordinatorLayout+BottomNavigationView+NestedScrollView+その他諸々で画面構築してみた

次のぎじゅつを使って画面構築してみたのでメモ

  • 見た目はToolbar+コンテンツ表示Fragment+BottomNavigationViewの画面構成
  • コンテンツ表示Fragmentは、BottomNavigationViewの選択により入れ替える
  • Toolbarはコンテンツ表示Fragmentのスクロール動作により隠れるようにする
  • Toolbarのタイトルを、Navigationを使ってBottomNavigationViewに連動させる
  • BottomNavigationViewは固定する

BottomNavigationView & Toolbar & Navigation 間の連携に微妙なコツがあったり、 そもFrameLayoutであることを考慮したり、いろいろハマりまくった。

スクロールでBottomNavigationViewも隠したい場合は、カスタムBehavior定義して設定してやればおけ。 個人的には画面切り替えにスクロール動作が増えてしまうので、あえて固定する方法をとった。

  • 動作プレビュー
  • レイアウトXML
  • 実装編
    • NavigationとToolbar/BottomNavigationViewの連携の実装
      • コンテンツのレイアウトを作成する
      • BottomNavigationViewのMenuを作成する
      • Navigation graphを作成する
      • FragmentContainerViewにnav_graphをセット
    • スクロール時にToolbarを隠す動作の実装
      • 基本となる画面構造
    • NestedScrollView内にRecyclerViewを配置する場合の注意
  • 参考
続きを読む