2024/09/01

複数のフィールド値をセットでソート

前回 LotusScript を使ってソートする方法を紹介しました。ただ、実際にアプリを作っていると前回の関数だけではうまくいかない問題に直面することがあります。

例えば、ノーツでありがちな複数のフィールドを同じ要素数だけ値を保持、改行表示することで表っぽく見せる場合です。

今回はこのような複数のフィールド値をセットでソートする場合を考えてみましょう。


力技には限界がある

今回の例では、ソート値以外に 2 つのフィールドがあります。これら値もソート順に従い並び替える必要があります。

単純に考えると、前回の関数に引数を追加、値の入れ替えが発生したタイミングで追加した値も入れ替えれば、実現できます。

Function xSort(vvData As Variant) As Variant  '引数に行先と交通費を追加
   Dim i As Integer
   Dim j As Integer
   Dim v As Variant
   Dim vRtn As Variant

   '戻り値配列を準備
   vRtn = vvData

   '値をソート
   For j = 1 To Ubound(vRtn)
      For i = 0 To Ubound(vRtn) - j
         If vRtn(i) > vRtn(i+1) Then
            '値を入れ替え
            v = vRtn(i)
            vRtn(i) = vRtn(i+1)
            vRtn(i+1) = v

            'ここで行先と交通費も入れ替え

         End If
      Next
   Next

   'ソートした配列を返す
   xSort = vRtn
End Function

ただ、今回のようにフィールドが 3 つ程度であればいいですが、10 や 20 フィールドだったらどうでしょう? このような力技は非効率で限界があることがわかります。


配列を活用して回避

この問題は配列をうまく活用すると比較的簡単に解決できます。

まずはソート関数 xSort の改造です。赤字の部分が修正箇所です。基本的な構造はそのままですが、インデックス配列なるものが登場しており、関数の最初でソートしたいデータと同じだけの配列を用意して、添え字番号をセットしています。値の比較では、添え字の指定がカスケードされており、入れ替えは値ではなく、インデックス変数となっています。また、戻り値がインデックス配列となっていますね。

Function xSort(vvData As Variant) As Variant
   Dim i As Integer
   Dim j As Integer
   Dim v As Variant
   Dim aiIdx() As Integer

   'インデックス配列を準備
   Redim aiIdx(Ubound(vvData))
   For i = 0 To Ubound(vvData)
      aiIdx(i) = i
   Next


   'ソート開始
   'インデックス配列経由で値を参照し、インデックスを入れ替え

   For j = 1 To Ubound(vvData)
      For i = 0 To Ubound(vvData) - j
         If vvData(aiIdx(i)) > vvData(aiIdx(i+1)) Then
            'インデックスを入れ替え
            v = aiIdx(i)
            aiIdx(i) = aiIdx(i+1)
            aiIdx(i+1) = v
         End If
      Next
   Next

   'インデックス配列を返す
   xSort = aiIdx
End Function


これがどのような挙動になるのか順にみていきましょう。

まずはソート開始前の状態確認です。引数で渡される配列と準備したインデックス配列は次の通りとなります。

i vData(i) aiIdx(i)
0 2024/08/30 0
1 2024/06/13 1
2 2024/10/25 2
3 2024/09/19 3


続いてソートを開始します。

ポイントとなるのが、vvData(aiIdx(i)) の表現です。例えば、i = 1 の場合では、aiIdx(i) = 1 となり、vvData(aiIdx(i)) = vvData(1) = 2024/06/13 となります。

現時点では意味がなく、ムダに見えますが、もう少し我慢してください。

実際のソートの処理を順に確認しましょう。インデックス配列 aiIdx 、ソートしたい値である vvData をループ変数の進展に合わせて表にまとめています。

j i aiIdx() vvData() 操作
0 1 2 3 aiIdx(i) aiIdx(i+1)
1 0 0 1 2 3 2024/08/30 2024/06/13 入れ替え
1 1 0 2 3 2024/08/30 2024/10/25
2 1 0 2 3 2024/10/25 2024/09/19 入れ替え
2 0 1 0 3 2 2024/06/13 2024/08/30
1 1 0 3 2 2024/08/30 2024/09/19
3 0 1 0 3
2
2024/06/13 2024/08/30
結果 1
0
3
2
ソート完了

いかがですか?

今回の処理では、ソートしたい値 vvData の値は一切変化していません。インデックス変数を並べ替え、そこを経由してアクセスすることで、ソートを実現しています。


インデックス配列の活用

ソート完了後はこのインデックス配列を戻り値としていました。そのインデックス配列と実データを引数で渡し、並び替えた実データを戻り値で返す関数です。

Function xGetSortedVal(vvIdx As Variant, vvData As Variant) As Variant
   Dim i As Integer
   Dim v As Variant

   '戻り値配列の初期化
   v = vvData

   '戻り値生成
   For i = 0 To Ubound(v)
      v(i) = vvData(vvIdx(i))
   Next

   xGetSortedVal = v
End Function

ソートの比較で使用したインデックス配列を経由して取得する方法を使用して、並び替えられた値を取得しています。


メインルーチンでは、これら関数を利用して次のように記述します。

Sub Click(Source As Button)
   Dim nuiw As New NotesUIWorkspace
   Dim nuid As NotesUIDocument
   Dim nd As NotesDocument
   Dim v As Variant

   Set nuid = nuiw.CurrentDocument
   Set nd = nuid.Document

   'ソートされたインデックス配列を取得
   v = nd.Src_Date
   v = xSort(v)


   'ソートされた実データを取得
   nd.Sort_Date = xGetSortedVal(v, nd.Src_Date)  '日付
   nd.Sort_Text = xGetSortedVal(v, nd.Src_Text)  '行先
   nd.Sort_Cost = xGetSortedVal(v, nd.Src_Cost)  '交通費

End Sub


関数のテスト

前回と同様に、簡単なテストフォームを作成して、動作確認します。

実行すると、次のようにすべてのフィールドが日付でソートされます。


これで、日付、行先、交通費の各項目を日付順で並び替えることができました。この方法であれば、セットで操作するフィールド数が増えてもあまり気にならないですね。

0 件のコメント:

コメントを投稿