none
連想配列におけるマッチング処理について RRS feed

  • 質問

  • いつもお世話になっております。梅崎と申します。

    Powershellについて確認させてください。

    2つの連想配列で、配列にデータが含まれているかをチェックしようとしています。

    <連想配列1>$UserList1
    ユーザID 所属コード フラグ
    0001  aaaa  False
    0002  bbbb  False

    <連想配列2>$UserList2
    ユーザID 所属コード
    0001  aaaa
    0003  cccc


    $UserList1のユーザIDが$UserList2に含まれているかチェックし、含まれている場合は
    フラグにTrueをセットしようとしています。
    よい方法はございませんでしょうか?

    現在は、foreachで$UserList1、$UserList2をまわして、
    全件チェックしている状態です。

    -----サンプルスクリプト----
    foreach($User1 in $UserList1){
     foreach($User2 in $UserList2){
      if($User1.ユーザID -eq $User2.ユーザID){
       $User1.フラグ=True
      }
     }
    }
    ----------------------------

    この手順では、効率が悪く、時間がかかってしまうと思います。
    何かよい関数や、よりよい手順などがございましたら、アドバイス頂けると助かります。
    よろしくお願いいたします。

    2013年12月5日 1:46

回答

  • そもそも連装配列でもないように思いますが…。
    $list = $UsrList2 | Select-Object ユーザID
    foreach($User1 in $UserList1){
      if($list -contains $User1.ユーザID){
        $User1.フラグ = True
      }
    }
    先に該当するユーザIDリストを作ってしまうと-contains演算子を利用できるのでスクリプトの見た目が向上し、また少しだけ検索の効率がよくなります。
    # 本質的には2重ループであることに変わりはありません。
    • 回答としてマーク 梅崎 2013年12月6日 1:13
    2013年12月5日 2:05
  • ContainsKeyやContainsValueを使えば二重ループは避けられますが、どの程度効率が良くなるかはわかりません。
    ロジックで効率を求めるのであれば、例えば以下の方法がとれます。
    2つの配列をユーザIDの昇順でソートしておけば、他方の配列を最初から探す必要がなくなり、探すユーザIDがブレイクするところまで探せば良いため、効率よく探せるようになります。ソートはSort-Objectコマンドレットを使用すれば良いでしょう。


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/


    • 編集済み trapemiya 2013年12月5日 2:28 誤字修正
    • 回答としてマーク 梅崎 2013年12月6日 1:13
    2013年12月5日 2:27
  • こんにちは、

    $UserList2が本当に連想配列ならば以下のようにダイレクトに判定できるかと思います。

    $UserList2 = @{"0001" = "aaaa";"0003" = "bbbb"}
    $k1 = "0003"
    $k2 = "0005"
    $UserList2[$k1] -eq $null
    $UserList2[$k2] -eq $null
    
    --結果
    False
    True

    • 回答としてマーク 梅崎 2013年12月6日 1:13
    2013年12月5日 4:52
  • あーSelect-Objectじゃないですね。

    $list = $UsrList2 |% {$_.ユーザID}

    か。あと、他の方も指摘されていますが、質問者さんが連想配列と誤解しているものは単なる配列です。そして本当の連想配列であれば効率よく検索できます。
    # その場合、ソートもブレイクも必要ありません。

    ただ残念なことにPowerShellは構文が非公開なので、連装配列の正しい作り方が不明です。やり方自体はいろいろあるみたいですが…フォーラムで回答できるような「正しい」方法となると…。

    • 回答としてマーク 梅崎 2013年12月12日 8:17
    2013年12月10日 0:37

すべての返信

  • そもそも連装配列でもないように思いますが…。
    $list = $UsrList2 | Select-Object ユーザID
    foreach($User1 in $UserList1){
      if($list -contains $User1.ユーザID){
        $User1.フラグ = True
      }
    }
    先に該当するユーザIDリストを作ってしまうと-contains演算子を利用できるのでスクリプトの見た目が向上し、また少しだけ検索の効率がよくなります。
    # 本質的には2重ループであることに変わりはありません。
    • 回答としてマーク 梅崎 2013年12月6日 1:13
    2013年12月5日 2:05
  • ContainsKeyやContainsValueを使えば二重ループは避けられますが、どの程度効率が良くなるかはわかりません。
    ロジックで効率を求めるのであれば、例えば以下の方法がとれます。
    2つの配列をユーザIDの昇順でソートしておけば、他方の配列を最初から探す必要がなくなり、探すユーザIDがブレイクするところまで探せば良いため、効率よく探せるようになります。ソートはSort-Objectコマンドレットを使用すれば良いでしょう。


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/


    • 編集済み trapemiya 2013年12月5日 2:28 誤字修正
    • 回答としてマーク 梅崎 2013年12月6日 1:13
    2013年12月5日 2:27
  • こんにちは、

    $UserList2が本当に連想配列ならば以下のようにダイレクトに判定できるかと思います。

    $UserList2 = @{"0001" = "aaaa";"0003" = "bbbb"}
    $k1 = "0003"
    $k2 = "0005"
    $UserList2[$k1] -eq $null
    $UserList2[$k2] -eq $null
    
    --結果
    False
    True

    • 回答としてマーク 梅崎 2013年12月6日 1:13
    2013年12月5日 4:52
  • ありがとうございました。

    ご回答いただきました通り、本質的には2重ループは必要となり、

    ソートとブレイクで処理を省略可できるのですね。

    ご回答ありがうございました。

    2013年12月6日 1:16
  • ご回答ありがとうございます。

    頂いたソースを基に実装を試みたのですが、
    連想配列のcontainsがうまく動作しないようです。

    もし何か、作成したコーディングに誤りがあるようでしたら、ご指摘、アドバイスを頂けると大変助かります。

    よろしくお願いします。

    $list
    
    ユーザーID
    ------
    user1
    user2
    user3
    
    $list -contains "user1"
    False
    • 編集済み 梅崎 2013年12月9日 13:23
    2013年12月9日 13:22
  • あーSelect-Objectじゃないですね。

    $list = $UsrList2 |% {$_.ユーザID}

    か。あと、他の方も指摘されていますが、質問者さんが連想配列と誤解しているものは単なる配列です。そして本当の連想配列であれば効率よく検索できます。
    # その場合、ソートもブレイクも必要ありません。

    ただ残念なことにPowerShellは構文が非公開なので、連装配列の正しい作り方が不明です。やり方自体はいろいろあるみたいですが…フォーラムで回答できるような「正しい」方法となると…。

    • 回答としてマーク 梅崎 2013年12月12日 8:17
    2013年12月10日 0:37
  • ご回答ありがとうございます。

    パーセント文字 (%) は、ForEach-Object コマンドレットのエイリアスとなっており、

    ForEach-Object コマンドレットを使用すると、コレクション内の各アイテムをループ処理し、

    動作を実行することができるようになると理解しました。

    単純な配列として扱うには、ForEach-Objectを利用する必要があるのですね

    勉強になりました。

    2013年12月12日 8:17
  • trapemiyaさんの挙げているContainsKeyを使う方法をコードにしておくと

    $hash = @{}
    $UsrList2 |% {$hash[$_.ユーザID] = 1}
    foreach($User1 in $UserList1){
      if($hash.ContainsKey($User1.ユーザID)){
        $User1.フラグ = True
      }
    }

    になるとコード的にはほとんど同じに見えますが、内側がハッシュ探索になり2重ループではなくなります。

    2013年12月13日 1:37