背景

こちらの記事のコメントにいただいた質問です。

冒頭の図のようなデータがあるとして、オブジェクト.平均値(プロパティ, 抽出条件)のように書けないか? とのことで。おもしろそうだなと思って考えてみました。

クラスを作成

まずはコレクションに構造体を持たせるためのクラス。前の記事と同じような感じです。オブジェクト名はデフォルトのClass1からListDetailに変更してください。

とりあえずプロパティは3つ。必要かはおいといてインデックスと、値が2種類という想定で。

ListDetail クラスだけではコレクション自体をどうこうすることができないので、もう1つクラスモジュールを追加します。

こんな感じにしておくと、こちらのクラスでインスタンス化したオブジェクトに、さっきのListDetailで作ったコレクションを持たせてオブジェクト.Averageと書けば平均値が返ってくる、という想定です。

標準モジュールから構造体コレクションを作成

今度は標準モジュールから、さきほど作った2つのクラスを使ってコレクションを作ります。

まずは5~6行目、ListCalculationクラスでitemsというインスタンスを作成して、items.Listコレクションを作っておきます。

で、9~17行目、ListDetailクラスを使ってコレクションの中身を埋めていきます。上記コードでは、Value1, Value2 ともに、0~99までのランダムな整数を100個設定しているので、ここはお好みで変更してください。

なお、冒頭で紹介した元記事には、CSVファイルからコレクションに追加する方法があるので必要であればそちらもご参照ください。

最後がメインの20行目、コレクションに格納した値の平均値を取得する部分になっています。

平均値を返す関数を書く

では、さきほどListCalculationクラスに作った、平均値を計算するAverage関数の中身を書きましょう。

Value1プロパティ限定

とりあえず、決め打ちのプロパティで書いてみます。

コレクション内のValue1プロパティを全部足して、最後にコレクションの要素数で割って、平均を算出しています。これで、標準モジュールのプロシージャを走らせれば、Value1プロパティの平均値がイミディエイトウィンドウに出力されます。

プロパティを動的に指定

さて、やりたいことは、「指定したプロパティの」平均がほしいので、決め打ちではちょっとな…、という感じです。

標準モジュールの、最後の平均値を取得する部分で、引数としてプロパティ名を指定できるようにします。コレに合わせて、ListCalculationクラスのAverage関数の中身も修正します。

さて、「プロパティを変数で指定する」…、だと…? なにか記憶にひっかかるものが…。。

……。

あっ、CallByNameだー!!!

ちょっと前に別件で「プロパティを変数で」という壁にぶつかってtwitterでぼやいてたら、thom神が教えてくれたヤツだー!! これ進研ゼミで見たヤツだわー!!!

結局そのときのコードは、そもそもの設計がうまくなくてお蔵入りになっちゃったので、とてもテンションがあがりました(どうでもいい余談)。

5行目でプロパティ名を文字列型の変数で受け取って、11行目のCallByNameで、オブジェクト、プロパティ、種類の順で指定します。種類をvbMethodにすれば、プロシージャを文字列型で指定することもできるんだとか。おおー。

条件を設定する

さらに、コレクションの中で条件に一致した要素のみ処理を行う、という件も考えてみます。

パターンを指定する

どんな条件がサンプルとしてわかりやすいものかちょっとアレなんですが…、

標準モジュールから呼び出すときに、いくつかあるパターンを文字列で指定しちゃうという例。

受け取ったパターン名を見て16~29行目のSelect文で処理の可否を判定し、フラグが立ったものだけ計算していきます。

ちなみに5行目のようにOptionalをつけておくと、引数が省略されたときのデフォルト値を設定できます。この場合でitems.Average("Value1")と第二引数を省略して書くと、ptn0と判定されて全要素が対象になる、という感じですね。

指定範囲を設ける

もう1つ。Indexという要素番号プロパティを作ってあるので、それが特定の範囲内だったら、という例。

呼び出す側では、プロパティ名、Indexの開始番号、終了番号の順で指定。

開始番号と終了番号を受け取って、13行目でIndexが範囲に入ってるか判定しています。この方法なら、日付型のプロパティがあれば、日付が指定範囲のものだけ計算するとか、そういうこともできそうですね。

おわりに

自分でも「わー! こんなことできるんだー!!」とウキウキして書けて楽しかったです。ただ、ここまで勢いで書いたものの、実際コレ、スピード的にはどうなんだろう…。。大規模データでのテストはしてないので、もし実用に耐えなかったらすみません…!

まぁでも、大規模なデータを扱うなら、そもそもExcelなんだから普通にシート使ったり、外部DBのテーブルと連携させたりするほうが健全ですねw 大規模なデータからちょこっと読み込んできてコレクションに入れて処理する、くらいな使い方がいいんじゃないかと思います。

  • このエントリーをはてなブックマークに追加
  • follow us in feedly 652
  • RSSを登録

公開日:2017/10/16


3件のコメント

  • 通りすがり
    2017年10月16日 11:39 PM

    *youさん、

    早速のご回答ありがとうございます。
    クラスを2つ使う。こんなこともできるんですね。勉強になります!!

    さて、実際の使われ方としては、あるプロパティに対して検索条件で抽出し、それに対応する別のプロパティの演算をしたい、というのがあるので、こんな風なコードを作ってみました。

    ここで質問があります。

    オートフィルターのメソッドのように、検索条件の比較演算子 (=, <, など) を標準モジュールで記述できるような汎用性のあるコードにすることはできるのでしょうか?

    今のコードの書き方だと標準モジュールのAverageメソッドの引数を見ても、どのような検索条件なのかはクラスモジュールを見ないと分からないので、標準モジュールのメソッドの引数を見るだけで比較演算子が分かるようになれば汎用性があっていいかと思います。

    あんまり込み入った検索条件にするならそもそもオートフィルタを使ったほうが良い、と言われそうですが^^

    よろしくお願いします。

    • *you
      2017年10月18日 12:23 PM

      こんにちは、ご要望に沿った内容になれたようで安心しました。

      > あるプロパティに対して検索条件で抽出し、それに対応する別のプロパティの演算
      これはアツイですね…! VLookUpな使い方もできるんだと、こちらこそ勉強になります(*゚ω゚*)

      さて、引数に比較演算子の件ですが、VBAのクラスでオートフィルタみたいな複雑な定義…、うーん、どこまでできるのかなー。。残念ながら現状の私の理解の範疇ではあれほど汎用的にはできなさそうだったのですが、シンプルに「=10」「<=10」「<>10」程度の、「比較演算子&数値」限定ならば、こんな感じになるかなーと、考えてみました。

      条件で指定できる演算子は「<, <=, >, >=, =, <>」のみにしてあります。

      コード拝見して、アッそうか平均って WorksheetFunction.Average でいいんだ…! と思ったので倣わせていただきました。演算子だけ取り出すのに、チェックがてらもう1つ関数を使っています。いろいろ自己流なので、もっとスマートな方法があるかもしれませんすみません…_(:3 」∠)_

      私も今回初めて知ったんですが、23行目のEvaluateという関数が便利すぎてびっくりしました。

      こんな感じでいかがでしょうか。

  • 通りすがり
    2017年10月31日 12:40 PM

    *youさん、

    すっかり返事が遅くなりすみません。ここ最近ずっとチェックできていませんでした。

    Evaluate(lookupPropValue & operator & number)
    こんなのができるんですね!! すごいです。

    これでやりたいことはできたので、解決とさせていただきます。

    ありがとうございました。

コメントを残す




*印は必須項目です。コメントは承認制ですので、反映までしばらくお待ち下さい。(稀にですがスパムの誤判定にて届かないこともあるようですので、必要な際はお問い合わせからお願い致します。)


back to top