- 投稿日:2022-03-03T21:16:43+09:00
【Unity】UniRxでアニメーションの開始と終了をスクリプトで受け取る
概要 Unityのアニメーションの開始と終了をスクリプトで受け取って操作する手順です。 サンプル 実装するサンプルです。Attackアニメーションの開始、終了および、Combinationアニメーションの開始、終了時にテキストが更新されていることが分かります。 無料Assetの以下を使わせていただきました。 必要なもの 事前にUniRxをimportしておいてください。動かしたいオブジェクトやアニメーションも必要になります。 実装手順 AnimationControllerの設定 動かしたいモデルにアタッチするAnimationControllerの設定をします。 今回は1つの攻撃アニメーションのStateとコンビネーションの攻撃を合わせたSub State Machineを用意しました。 アニメーションの開始と終了を受け取るためにObservableStateMachineTriggerをアタッチしてください。 読んでも詳しく書いてないんですが、ObservableStateMachineTriggerについては以下から参照できます。 Base Layer Combination(Sub-State-Machine) サンプルコード AnimationControllerと合わせてアタッチするスクリプトです。 トリガーイベントとして以下を設定しています。他にもトリガーはあるので以下を参考にしてください。 https://github.com/neuecc/UniRx/wiki/UniRx.Triggers OnStateEnterAsObservable Stateが開始したとき OnStateExitAsObservable Stateが終了したとき トリガー内で指定のStateを検知したときにテキストを書き換える処理にしています。 ここはご自身の好きな処理で大丈夫です。 AnimationUniRxSample.cs using System.Collections; using System.Collections.Generic; using System; using UnityEngine; using UnityEngine.UI; using UniRx; using UniRx.Triggers; public class AnimationUniRxSample : MonoBehaviour { public Text text; void Start() { Animator animator = this.GetComponent<Animator>(); // AnimatorからObservableStateMachineTriggerの参照を取得 ObservableStateMachineTrigger trigger = animator.GetBehaviour<ObservableStateMachineTrigger>(); // Stateの開始イベント IDisposable enterState = trigger .OnStateEnterAsObservable() .Subscribe(onStateInfo => { AnimatorStateInfo info = onStateInfo.StateInfo; // Base Layer if (info.IsName("Base Layer.Attack")) { text.text = "攻撃中: Attack"; } // Sub State Machine if (info.IsName("Combination.WGS_attackA1")) { text.text = "攻撃中: Combination"; } }).AddTo(this); // Stateの終了イベント IDisposable exitState = trigger .OnStateExitAsObservable() .Subscribe(onStateInfo => { AnimatorStateInfo info = onStateInfo.StateInfo; // Base Layer if (info.IsName("Base Layer.Attack")) { text.text = "攻撃終了: Attack"; } // Sub State Machine if (info.IsName("Combination.WGS_attackA3")) { text.text = "攻撃終了: Combination"; } }).AddTo(this); } } ※Sub-State-Machineの名前については、Base LayerからではなくSub-State-Machineの名前.Sub-State-MachineのStateで書いてください。 今回の場合、開始部分についてはCombination.WGS_attackA1、終了部分についてはCombination.WGS_attackA3にしております。 動かすオブジェクトにアタッチする 動かすモデルにAnimatorControllerとスクリプトをアタッチしてください。Animatorの設定等については省略します。 すると実行してTriggerをオンにするとアニメーションと同時にテキストが更新されます。 動作テスト 動作させる方法については環境によってことなるので割愛しますが、私の場合はAnimatorControllerの編集画面で適当にTriggerパラメータを設定し、対象のステートに向かってMake Transition > ConditionsでTriggerを設定の流れにしています。 まとめ 超簡単に開始と終了が作れたのでターン制のゲームなんかではすごく役に立つと思います。 ただ、UniRx最強だけど覚えることが多すぎる。 参考記事
- 投稿日:2022-03-03T13:44:34+09:00
C#でCSVを扱うときに便利なCSVHelperの使い方
CSVHelperとは C#でCSVを取り扱う際にCSVにまつわるもろもろのメンドクサイところをいい感じでやってくれるライブラリです。 ↑でも書かれていますが、CSVファイルはカンマで区切られたファイル、という簡単なイメージほど、取り扱いが簡単ではありません。 その辺の面倒なところを助けてくれるのがここで紹介するCSVHelperになります。 CsvHelper公式 を見ればだいたいわかりますが、備忘のため、記事にします。 インストール Install-Package CsvHelper サンプル ユーザー定義クラスのリストをCSVファイルに書き込むサンプル。 using CsvHelper; using CsvHelper.Configuration; using System.IO; using System.Collections.Generic; using CsvHelper.Configuration.Attributes; class Hoge { [Name("ユーザーID")] public int Id { get; set;} [Name("名前")] public string Name { get; set; } [Name("バージョン表記")] [Constant("foo")] public string Version{get;set;} [Ignore] public string Ignored { get; set; } } class HogeMapper : CsvHelper.Configuration.ClassMap<Hoge> { public HogeMapper() { AutoMap(CultureInfo.InvariantCulture); } } class Program{ static void Main() { List<Hoge> list = new List<Hoge>(); list.Add(new Hoge {Id = 1, Name = "John"}); list.Add(new Hoge {Id = 2, Name = "Paul"}); list.Add(new Hoge {Id = 3, Name = "George"}); list.Add(new Hoge {Id = 4, Name = "Ringo"}); var config = new CsvConfiguration(System.Globalization.CultureInfo.InvariantCulture); config.HasHeaderRecord = true; config.ShouldQuote = (context) => true; using (StreamWriter sw = new StreamWriter(@"sample.csv", false, Encoding.GetEncoding("Shift_JIS"))) using (var writer = new CsvWriter(sw, config)) { writer.Context.RegisterClassMap<HogeMapper>(); writer.WriteRecords(list); } } } 出力結果 "ユーザーID","名前","バージョン表記" "1","John","foo" "2","Paul","foo" "3","George","foo" "4","Ringo","foo" 解説 var config = new CsvConfiguration(System.Globalization.CultureInfo.InvariantCulture); config.HasHeaderRecord = true; config.ShouldQuote = (context) => true; 上記のコードでは、CSVファイルをCsvWriter(後述)が解釈する際の条件(ヘッダ有無とか)を設定しています。 config.HasHeaderRecord = true;は先頭行にヘッダーがある、という定義、config.ShouldQuote = (context) => true;は各項目がダブルクォーテーションで囲まれている、という定義です。この設定が無いとデフォルトではダブルクォーテーションが勝手についたりつかなかったりします。 古い記事だとCsvWriterのコンストラクタにCsvConfigurationのパラメータが無く、インスタンス後にCsvWriterのConfigurationプロパティを経由して設定を変更しているものもありますが、現バージョン(27.0.0.0)ではConfigurationはreadonlyに変更になっておりプロパティの設定ができなくなっているので、CsvWriterを生成する前にCsvConfigurationを生成、設定を行います。 using (StreamWriter sw = new StreamWriter(@"sample.csv", false, Encoding.GetEncoding("Shift_JIS"))) using (var writer = new CsvWriter(sw, config)) { writer.Context.RegisterClassMap<HogeMapper>(); writer.WriteRecords(list); } 上記のコードでCSV出力をしています。 new CsvWriter(sw, config)で出力先のストリームとConfigurationをパラメータとしてCsvWriterを生成します。 writer.Context.RegisterClassMap<HogeMapper>();でユーザー定義クラスとCSVのマッピング方法を指示しています。 writer.WriteRecords(list);でListの各アイテムを一括でCSVファイルに出力しています。 マッピングについて CSVファイルとクラスのマッピングは自動で行われるようですが、ヘッダ項目を独自に設定したい場合などはクラスのAttributeで設定できます。 class Hoge { [Name("ユーザーID")] public int Id { get; set;} の[Name("ユーザーID")]がそれにあたります。 注意点 ネット上にすでに多くの記事がありますが、バージョンアップでかなり破壊的変更があるらしく、古い記事だと、記事のとおりに書いてもうまくいかないことが多々あります。 (Configurationまわりとか) なので、この記事も時間がたつと使えなくなるかもしれません・・・
- 投稿日:2022-03-03T10:22:34+09:00
ASP.NET MVCでセッションの内容を確かめたい
セッションは本来Controller.HttpContextに属するため、中身を見ることはできない。 しかし、確認したい場合は出てくる。ビューでセッション変数を読み込む場合だ。 そこで、以下の様なコードをテストに入れる(MSTestを利用しているが、他のフレームワークに即した形で修正すれば大丈夫なはずだ)。 [TestMethod] public void Test() { var controllerContext = new Mock<ControllerContext>(); var session = new Mock<HttpSessionStateBase>(); var sessionValue = string.Empty; // セッションの値を受け取る変数 session.SetupSet(s => s["value"] = It.IsAny<object>()) .Callback((string name, object o) => { sessionValue = (string)o; }); controllerContext.Setup(p => p.HttpContext.Session).Returns(session.Object); var controller = new FooController { ControllerContext = controllerContext.Object }; var result = contoller.Index() as ViewResult; // FooController.Index()内でSession["value"]の値が"bar"に変わると想定 Assert.AreEqual(string.Empty, result.ViewName); Assert.AreEqual("bar", sessionValue); } 参考:Tales from the Evil Empire - Mocking indexer setters with Moq
- 投稿日:2022-03-03T08:40:13+09:00
規則性の低い数列表から無検索逆引き
バラバラの数列の数からインデックスを得るとき、高級言語(死語?)では検索機能(table.index(N)など)がありますが、低級言語では検索なしにインデックスを得たい気分になります。(ここでの検索とは比較を繰り返しす処理を意味します) テーブル中の数が重複して出てこないとき、剰余を使うと検索なしにインデックスを得られる場合があります。 例(除数は45) >>> table = [57183, 26302, 12855, 61489, 21256, 14532, 24168, 37683, 18386, 35798, 7211] >>> [n % 45 for n in table] [33, 22, 30, 19, 16, 42, 3, 18, 26, 23, 11] >>> [[-1, -1, -1, 6, -1, -1, -1, -1, -1, -1, -1, 10, -1, -1, -1, -1, 4, -1, 7, 3, -1, -1, 1, 9, -1, -1, 8, -1, -1, -1, 2, -1, -1, 0, -1, -1, -1, -1, -1, -1, -1, -1, 5, -1, -1, -1] [n % 45] for n in table] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 目的の数列に対する除数を得られれば検索せずに済むことになります。 除数の検索と逆引き表の生成プログラム コマンドライン引数に数列を渡すと、除数の検索と逆引き表の生成を行っています。 オプションで以下のことができます。 逆引きでの無効を示す値の変更 (-d N) 除数の上限を設定 (-l N) findivisor.py #!/usr/bin/env python3 def find_divisor(table, limit=None): "単射となる除数を探す" ntab = len(table) if not ntab: return 0 if len(set(table)) != ntab: raise Exception('表中の数に重複があります') btab = ntab.bit_length() if limit is None: limit = max(table) divisor = (1 << (btab - 1)) + 1 injective = False while divisor <= limit and not injective: remain = tuple(n % divisor for n in table) used = {(n % divisor) for n in table} injective = (len(used) == ntab) if injective: break divisor += 2 return (injective and divisor or 0) def make_index(divisor, table, defval): "逆引き表を生成する" remain = tuple(n % divisor for n in table) index = [defval] * (divisor + 1) for n in range(len(table)): index[table[n] % divisor] = n return tuple(index) # コマンドライン処理 if __name__ == '__main__': import sys import random import argparse def format_list(data, *, width=16, prefix='', datafmt='%s', msgfmt='%#s', separator=',', space=1, suffix='\n'): fmtdata = [datafmt % d for d in data] msgfmt = msgfmt.replace('#', ('%d' % max([len(s) for s in fmtdata]))) msglist = [(msgfmt % d) + separator for d in fmtdata] return (suffix.join([prefix + (' ' * space).join(msglist[i:min(i+width, len(msglist))]) for i in range(0, len(msglist), width)]) + suffix) parser = argparse.ArgumentParser(description='剰余による数列の逆引き表生成支援道具') parser.add_argument('N', type=int, nargs='+', help='数列の数 (-1 を一つだけ指定すると乱数によるテスト)') parser.add_argument('-d', '--defval', metavar='N', help='表の穴埋め値を指定します (既定:表の大きさ)') parser.add_argument('-l', '--limit', metavar='N', help='除数の上限を指定します (既定:表の最大値)') args = parser.parse_args() table = args.N if len(table) == 1 and table[0] == -1: table = [] for n in range(random.randint(10, 50)): r = random.randint(0, 65535) if r not in table: table.append(r) limit = None if args.limit is None else int(args.limit) defval = len(table) if args.defval is None else int(args.defval) divisor = find_divisor(table, limit) if divisor <= 0: print('除数を見つけられませんでした') sys.exit(2) index = make_index(divisor, table, defval) print('除数:', divisor) print('逆引き:\n' + format_list(index, width=8, prefix=' '*4)[:-2]) print(('元の表: %d 個\n' % len(table)) + format_list(table, width=8, prefix=' '*4)[:-2]) 実行結果(逆引きの8は無効を示す) $ python3 findivisor.py 1 3 5 7 11 13 17 19 除数: 11 逆引き: 4, 0, 5, 1, 8, 2, 6, 3, 7, 8, 8, 8 元の表: 8 個 1, 3, 5, 7, 11, 13, 17, 19 テスト:数列として {-1} を与えると表は乱数生成になります。 実行結果 $ python3 findivisor.py -1 -d -1 除数: 45 逆引き: -1, -1, -1, 6, -1, -1, -1, -1, -1, -1, -1, 10, -1, -1, -1, -1, 4, -1, 7, 3, -1, -1, 1, 9, -1, -1, 8, -1, -1, -1, 2, -1, -1, 0, -1, -1, -1, -1, -1, -1, -1, -1, 5, -1, -1, -1 元の表: 11 個 57183, 26302, 12855, 61489, 21256, 14532, 24168, 37683, 18386, 35798, 7211
- 投稿日:2022-03-03T08:40:13+09:00
規則性の低い数列表に対する無検索逆引き
比較を繰り返す検索処理を避けたい 数に規則性のない数列(テーブル)からインデックスを得るとき、高級言語(死語?)では検索機能(table.index(N)など)がありますが、できれば検索なしでインデックスを得たいときがあります。(ここでの「検索」は比較を繰り返す処理とします) テーブル中の数値が重複して出てこないとき、剰余(と逆引き表)を使うと検索なしにインデックスを得られる場合があります。 例(除数は45) >>> table = [57183, 26302, 12855, 61489, 21256, 14532, 24168, 37683, 18386, 35798, 7211] >>> [n % 45 for n in table] [33, 22, 30, 19, 16, 42, 3, 18, 26, 23, 11] >>> rtab = [ ... -1, -1, -1, 6, -1, -1, -1, -1, -1, -1, ... -1, 10, -1, -1, -1, -1, 4, -1, 7, 3, ... -1, -1, 1, 9, -1, -1, 8, -1, -1, -1, ... +2, -1, -1, 0, -1, -1, -1, -1, -1, -1, ... -1, -1, 5, -1, -1, -1 ... ] >>> [rtab[n % 45] for n in table] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 例では、table の値を除数 45 で剰余したリストも中の数が重複していません(単射)。 目的の数列の剰余が単射になれば、逆引き表を作って検索不要にできます。 この方法の利点と欠点は以下のとおりです。 利点 高速化が期待できる 比較を繰り返す検索ではなくなるため 単純な逆引き表より小さな逆引き表を期待できる rtab[N] より rtab[N % d] の rtab は小さいかも 欠点 テーブル中の数値は重複してはいけない (set に変換しても同じサイズ) 逆引き表の前準備が必要 逆引き表はテーブル(正引き)より大きい 単純逆引きに比べると剰余逆引きは遅い rtab[N] より rtab[N % d] は剰余の分だけ遅い その他 逆引き表は辞書(dictなど)相当なので、インデックス以外に置換えも可 除数の求め方 求めたい除数は「逆引き表の大きさ」でもあるので 初期値: テーブルの大きさ (len(table)) 増加値: 1 として、テーブルの剰余が単射になるまで除数を増やして検索します。 除数の最大値は、テーブル内の最大値 (max(table)) になるので、単純逆引き表を用いた方が良いです。 除数の検索と逆引き表の生成プログラム コマンドライン引数に数列を渡すと、除数の検索と逆引き表の生成を行います。 オプションで以下のことができます。 逆引きでの無効を示す値の変更 (-d N) 除数の上限を設定 (-l N) findivisor.py #!/usr/bin/env python3 def create_remainder_table(table, divisor_max=None, invalid_value=None): "除数検索と逆引き表の生成" tlen = len(table) if not tlen: return (0, tuple()) if type(table) in (tuple, list, set): ltab = tuple(table) table = {ltab[i]: i for i in range(tlen)} tlen = len(table) divisor = tlen if divisor_max is None: divisor_max = max(table) keys = tuple(table.keys()) while True: rtab = {(n % divisor) for n in keys} injective = (len(rtab) == tlen) if injective or divisor >= divisor_max: break divisor += 1 if not injective: return (0, tuple()) vals = tuple(table.values()) rtab = [(tlen if invalid_value is None else invalid_value)] * divisor for i in range(tlen): rtab[keys[i] % divisor] = vals[i] return (divisor, rtab) # コマンドライン処理 if __name__ == '__main__': import re import sys import random import argparse def format_list(data, *, width=16, prefix='', datafmt='%s', msgfmt='%#s', separator=',', space=1, suffix='\n'): fmtdata = [datafmt % d for d in data] msgfmt = msgfmt.replace('#', ('%d' % max([len(s) for s in fmtdata]))) msglist = [(msgfmt % d) + separator for d in fmtdata] return (suffix.join([prefix + (' ' * space).join(msglist[i:min(i+width, len(msglist))]) for i in range(0, len(msglist), width)]) + suffix) parser = argparse.ArgumentParser(description='剰余による数列の逆引き表生成支援道具') parser.add_argument('N', nargs='+', help='数列の数 (-1 を一つだけ指定すると乱数によるテスト)') parser.add_argument('-d', '--defval', metavar='N', help='表の穴埋め値を指定します (既定:表の大きさ)') parser.add_argument('-l', '--limit', metavar='N', help='除数の上限を指定します (既定:表の最大値)') args = parser.parse_args() if len(args.N) == 1 and args.N[0] == '-1': table = {random.randint(0, 65535) for n in range(random.randint(10, 50))} else: RE_NUM = re.compile('(0b(?P<n2>[01]+)|0o(?P<n8>[0-7]+)|(?P<n10>[0-9]+)|0x(?P<n16>[0-9A-Fa-f]+)).*') table = [] for arg in args.N: for s in arg.split(','): m = RE_NUM.match(s.strip()) if not m: continue for kv in filter(lambda kv: kv[1], m.groupdict().items()): table.append(int(kv[1], int(kv[0][1:]))) stab = set(table) if len(stab) != len(table): print('重複があります:', ','.join([ '%d' % d for d in set(filter(lambda n: table.count(n) != 1, table)) ])) sys.exit(2) divisor_max = None if args.limit is None else int(args.limit) invalid_value = len(table) if args.defval is None else int(args.defval) (divisor, index) = create_remainder_table(table, divisor_max, invalid_value) if divisor <= 0: print('除数を見つけられませんでした') sys.exit(2) nrem = tuple(n % divisor for n in table) srem = tuple('%d:%d' % (n, n % divisor) for n in table) print('除数:', divisor) print('逆引き:\n' + format_list(index, width=8, prefix=' '*4)[:-2]) print(('元の表: %d 個\n' % len(table)) + format_list(table, width=8, prefix=' '*4)[:-2]) print('剰余:\n' + format_list(nrem, width=8, prefix=' '*4)[:-2]) print('被除数:剰余:\n' + format_list(srem, width=8, prefix=' '*4)[:-2]) 実行結果(逆引きの8は無効を示す) $ python3 findivisor.py 2, 3, 5, 7, 11, 13, 17, 19 除数: 13 逆引き: 5, 8, 0, 1, 6, 2, 7, 3, 8, 8, 8, 4, 8 元の表: 8 個 2, 3, 5, 7, 11, 13, 17, 19 剰余: 2, 3, 5, 7, 11, 0, 4, 6 被除数:剰余: 2:2, 3:3, 5:5, 7:7, 11:11, 13:0, 17:4, 19:6 実行結果(逆引きの9は無効を示す) $ python3 findivisor.py 461, 353, 419, 523, 139, 13, 31, 53, 89 除数: 37 逆引き: 9, 9, 9, 9, 9, 3, 9, 9, 9, 9, 9, 9, 2, 5, 9, 8, 7, 0, 9, 9, 1, 9, 9, 9, 9, 9, 9, 9, 4, 9, 9, 6, 9, 9, 9, 9, 9 元の表: 9 個 461, 353, 419, 523, 139, 13, 31, 53, 89 剰余: 17, 20, 12, 5, 28, 13, 31, 16, 15 被除数:剰余: 461:17, 353:20, 419:12, 523:5, 139:28, 13:13, 31:31, 53:16, 89:15 テスト:数列として {-1} を与えると表は乱数生成になります。 実行結果(逆引きの-1は無効を示す) $ python3 findivisor.py -1 -d -1 除数: 63 逆引き: -1, -1, -1, -1, -1, -1, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, 4, -1, -1, -1, 11, -1, -1, 13, -1, -1, -1, -1, 2, -1, 9, 3, 15, 12, 1, 14, -1, -1, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 10, 8, -1, -1, -1, -1, -1, -1, -1, -1, 0, -1 元の表: 16 個 23434, 31597, 33733, 60700, 40651, 8574, 25176, 33176, 22795, 47532, 31992, 55586, 1167, 54770, 32165, 56795 剰余: 61, 34, 28, 31, 16, 6, 39, 38, 52, 30, 51, 20, 33, 23, 35, 32 被除数:剰余: 23434:61, 31597:34, 33733:28, 60700:31, 40651:16, 8574:6, 25176:39, 33176:38, 22795:52, 47532:30, 31992:51, 55586:20, 1167:33, 54770:23, 32165:35, 56795:32 HTML+JavaScript による生成 以下、スクリーン ショット HTML <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" > <title>除数検索と逆引き表生成</title> <style> table { border: solid 1px gray; border-collaspe: collaspe; border-spacing: 0; } th, td { border: solid 1px gray; padding: 6px; } th { background-color: #efefef; } </style> </head> <body> <h1>除数検索と逆引き表生成</h1> <p> <textarea id="inp_table" rows="8" cols="80" placeholder="数列を入力"></textarea><br /> 配列の穴埋め値:<input id="inp_defval" type="text" size="8" placeholder="自動"> <input type="button" value="更新" onclick="onUpdate();"> </p> <p>入力配列の幅: <input id="inp_wtab" type="number" size="5" placeholder="10" style="width: 3em;"> <input type="button" value="更新" onclick="onUpdate();"> </p> <p>逆引配列の幅: <input id="inp_wrem" type="number" size="5" placeholder="10" style="width: 3em;"> <input type="button" value="更新" onclick="onUpdate();"> </p> <p>処理結果 => <span id="status">なし</span></p> <table id="result"></table> <h3>入力と剰余の対応</h3> <table id="out_table1"> <tr><th>配列</th><th> </th></tr> <tr><th>入力</th><td> </td></tr> <tr><th>剰余</th><td> </td></tr> </table> <h3>逆引きと入力の対応</h3> <table id="out_table2"> <tr><th>配列</th><th> </th></tr> <tr><th>逆引</th><td> </td></tr> <tr><th>入力</th><td> </td></tr> </table> <p></p> <details> <summary>デバッグ用</summary> <div> <p>入力文字の分解:<span id="dbg_nums"></span></p> <p>上記の数値化:<span id="dbg_table"></span></p> <p>穴埋め値:<span id="dbg_defval"></span></p> <p>除数:<span id="dbg_divisor"></span></p> <p>逆引き:<span id="dbg_index"></span></p> </div> </details> <script type="text/javascript"> <!-- function setStatus(msg) { document.getElementById('status').innerText = msg; } function clearChildren(tag) { while (tag.firstChild) tag.removeChild(tag.firstChild); } function setStyle(tag, style) { if (style) for (const s of style) tag.style.setProperty(s[0], s[1]); return tag; } function setTextRight(tag) { return setStyle(tag, [['text-align', 'right']]); } function trAppendChild(tr, tag, msg, style) { const tx = document.createElement(tag); tx.innerText = msg; tr.appendChild(tx); return setStyle(tx, style); } function trAppendTH(tr, msg, style) { return trAppendChild(tr, 'th', msg, style); } function trAppendTHRight(tr, msg, style) { return setTextRight(trAppendChild(tr, 'th', msg, style)); } function trAppendTD(tr, msg, style) { return trAppendChild(tr, 'td', msg, style); } function trAppendTDRight(tr, msg, style) { return setTextRight(trAppendChild(tr, 'td', msg, style)); } function getInputNumber(id, defval, limit) { const tag = document.getElementById(id); let val = Number(tag.value); if (val < 1 || !Number.isInteger(val)) val = defval; return Math.max(Math.min(val, limit), 1); } function onUpdate() { setStatus('処理中'); const inptable = document.getElementById('inp_table'); const inpdefval = document.getElementById('inp_defval'); const nums = [...inptable.value.matchAll(/[ox_0-9A-Fa-f]+/g)]; const table = nums.map(s => Number(s)); if (table.length == 0) { setStatus('エラー:数列を入力してください'); return; } document.getElementById('dbg_nums').innerText = nums; document.getElementById('dbg_table').innerText = table; const tabchk = new Set(table); if (tabchk.has(NaN)) { setStatus('エラー:数列に扱えない数値があります'); return; } if (tabchk.size != table.length) { setStatus('エラー:重複があります'); return; } // 除数の検索と逆引き表の生成 const divisor_max = Math.max.apply(null, table); let divisor = table.length; // if (divisor > divisor_max) divisor = divisor_max; let remains = table; for (; divisor <= divisor_max; divisor++) { remains = table.map(n => n % divisor); if (new Set(remains).size == table.length) break; } const sindex = [...Array(divisor)].map(() => undefined); for (let i = 0; i < sindex.length; i++) sindex[remains[i]] = i; const ndefval = Number(inpdefval.value != '' ? inpdefval.value : '--'); const defval = (Number.isInteger(ndefval) ? ndefval : table.length); const index = sindex.map(n => (n != undefined) ? n : defval); // 結果出力 document.getElementById('dbg_defval').innerText = defval; document.getElementById('dbg_divisor').innerText = divisor; document.getElementById('dbg_index').innerText = index; { const otag = document.getElementById('result'); clearChildren(otag); { const tr = document.createElement('tr'); trAppendTH(tr, '定数'); trAppendTD(tr, '除数 = ' + divisor + ', 穴埋め値 = ' + defval); otag.appendChild(tr); } { const tr = document.createElement('tr'); trAppendTH(tr, '逆引表'); trAppendTD(tr, ('' + index).replaceAll(',', ', ')); otag.appendChild(tr); } { const tr = document.createElement('tr'); trAppendTH(tr, '正引表'); trAppendTD(tr, ('' + table).replaceAll(',', ', ')); otag.appendChild(tr); } } { const otag = document.getElementById('out_table1'); clearChildren(otag); const width = getInputNumber('inp_wtab', 10, table.length); for (let iy = 0; iy < table.length; iy += width) { const tr_idx = document.createElement('tr'); const tr_fwd = document.createElement('tr'); const tr_rem = document.createElement('tr'); const style = [['background-color', '#f7ffff']]; trAppendTH(tr_idx, '配列'); trAppendTH(tr_fwd, '入力'); trAppendTH(tr_rem, '剰余'); for (let ix = 0; ix < width; ix++) { const i = ix + iy; if ((ix + iy) < table.length) { trAppendTHRight(tr_idx, ix + iy); trAppendTDRight(tr_fwd, table[ix + iy], style); trAppendTDRight(tr_rem, table[ix + iy] % divisor, style); } else { trAppendTH(tr_idx, ''); trAppendTD(tr_fwd, ''); trAppendTD(tr_rem, ''); } } otag.appendChild(tr_idx); otag.appendChild(tr_fwd); otag.appendChild(tr_rem); } } { const otag = document.getElementById('out_table2'); clearChildren(otag); const width = getInputNumber('inp_wrem', 10, index.length); for (let iy = 0; iy < index.length; iy += width) { const tr_idx = document.createElement('tr'); const tr_rev = document.createElement('tr'); const tr_fwd = document.createElement('tr'); trAppendTH(tr_idx, '配列'); trAppendTH(tr_rev, '逆引'); trAppendTH(tr_fwd, '入力'); for (let ix = 0; ix < width; ix++) { const i = ix + iy; if (i < index.length) { const j = index[i]; const valid = (0 <= j && j < table.length); let rev = j; let fwd = ''; if (valid) fwd = table[j]; else rev = '(' + j + ')'; const style = [['background-color', valid ? '#f7ffff' : '#fff7f7']]; trAppendTHRight(tr_idx, i); trAppendTDRight(tr_rev, rev, style); trAppendTDRight(tr_fwd, fwd, style); } else { trAppendTH(tr_idx, ''); trAppendTD(tr_rev, ''); trAppendTD(tr_fwd, ''); } } otag.appendChild(tr_idx); otag.appendChild(tr_rev); otag.appendChild(tr_fwd); } } setStatus('完了'); } // !--> </script> </body> </html>