18

問題: 1 次元配列に正確に一致する値があるかどうかを見つけるより効率的な方法を探しています。基本的には booleantrue/falseです。

私は明らかな何かを見落としていますか?それとも、おそらくコレクション オブジェクトまたは辞書を使用する必要があるときに配列を使用して、間違ったデータ構造を使用しているだけですか? 後者では、.Containsまたは.Existsメソッドをそれぞれ確認できました

Excel では、次のようなベクトル配列の値を確認できます。

If Not IsError(Application.Match(strSearch, varToSearch, False)) Then
' Do stuff
End If

これは完全一致インデックスを返しますが、明らかに、このコンテキストで最初Matchに一致する値のみを見つける関数の制限を受けます。これは一般的に使用される方法であり、私も長い間使用してきた方法です。

Excel ではこれで十分ですが、他のアプリケーションではどうでしょうか。

他のアプリケーションでも基本的に同じことができますが、Excel オブジェクト ライブラリへの参照を有効にする必要があります。

   If Not IsError(Excel.Application.match(...))

ばかげているように思えますが、アクセス許可やトラスト センターなどが原因で、分散ファイルを管理するのは困難です。

Filter()関数を使用しようとしました:

 If Not Ubound(Filter(varToSearch, strSearch)) = -1 Then
    'do stuff
 End If

しかし、このアプローチの問題はFilter、完全一致の配列ではなく、部分一致の配列を返すことです。(部分文字列/部分一致を返すと便利な理由がわかりません。)

Matchもう 1 つの方法は、配列内の各値を文字通り反復処理することです (これも非常に一般的に使用されていると思います)。これは、Excel の関数を呼び出すよりも不必要に面倒に思えます。

For each v in vArray
   If v = strSearch Then
    ' do stuff
   End If
Next
4

3 に答える 3

1

Application.Match「文字列値が配列に存在するかどうかを見つけるより効率的な方法 (と比較して)」:

あなたが使用している方法よりも効率的な方法はないと思いますApplication.Match

要素のインデックスがわかっている場合、配列を使用すると、その要素に効率的にアクセスできます。要素の値で何かをしたい場合 (要素が存在するかどうかをチェックする場合でも)、最悪の場合、配列のすべての要素をスキャンする必要があります。したがって、最悪のケースではn要素の比較が必要になります。ここnで、 は配列のサイズです。したがって、要素が存在するかどうかを見つけるために必要な最大時間は、入力のサイズ、つまりO(n). これは、従来の配列を使用するすべての言語に適用されます。

より効率的にできる唯一のケースは、配列が特別な構造を持つ場合です。あなたの例では、配列の要素が(アルファベット順になど)ソートされている場合、すべての配列をスキャンする必要はありません。中央の要素と比較してから、配列の左側または右側の部分と比較します(バイナリ検索)。しかし、特別な構造を想定しないと、希望はありません..

あなたDictionary/Collectionが指摘したように、それらの要素への一定のキーアクセスを提供します(O(1))。おそらく十分に文書化されていないのは、辞書要素 (キーと項目) へのインデックス アクセスも可能であるということです。要素が に入力される順序Dictionaryは保持されます。主な欠点は、要素ごとに 2 つのオブジェクトが格納されるため、より多くのメモリを使用することです。

まとめると、If Not IsError(Excel.Application.match(...))ばかげているように見えますが、それでも (少なくとも理論的には) より効率的な方法です。許可の問題について、私の知識は非常に限られています。ホスト アプリケーションによっては、常にいくつかのFindタイプの関数 ( C++hasfindfind_ifなど) があります。

それが役立つことを願っています!

編集

投稿の修正版とティムの回答を読んだ後、いくつかの考えを追加したいと思います。上記のテキストは、さまざまなデータ構造の理論的な時間の複雑さに焦点を当てており、実装の問題を無視しています。むしろ、「あるデータ構造(配列)が与えられたとき」、実際に存在を確認する最も効率的な方法は何か、という質問の精神だったと思います。

この目的のために、ティムの答えは驚くべきものです。

「自分でできるなら自分で書くな」という従来のルールVBAは、常に正しいとは限りません。ループや比較などの単純な操作は、関数を「一致させる」よりも高速ですVBAここここに 2 つの興味深いリンクがあります。

于 2013-09-12T09:08:02.010 に答える
1

以前は、最適な代替ソリューションを探していました。単純な検索でも機能するはずです。

文字列の最初のインスタンスを見つけるには、次のコードを使用してみてください。

Sub find_strings_1()

Dim ArrayCh() As Variant
Dim rng As Range
Dim i As Integer

 ArrayCh = Array("a", "b", "c")

With ActiveSheet.Cells
    For i = LBound(ArrayCh) To UBound(ArrayCh)
        Set rng = .Find(What:=ArrayCh(i), _
        LookAt:=xlPart, _
        SearchOrder:=xlByColumns, _
        MatchCase:=False)

        Debug.Print rng.Address

    Next i
End With

End Sub

すべてのインスタンスを見つけたい場合は、以下を試してください。

Sub find_strings_2()

Dim ArrayCh() As Variant
Dim c As Range
Dim firstAddress As String
Dim i As Integer

 ArrayCh = Array("a", "b", "c") 'strings to lookup

With ActiveSheet.Cells
    For i = LBound(ArrayCh) To UBound(ArrayCh)
        Set c = .Find(What:=ArrayCh(i), LookAt:=xlPart, LookIn:=xlValues)

        If Not c Is Nothing Then
            firstAddress = c.Address 'used later to verify if looping over the same address
            Do
                '_____
                'your code, where you do something with "c"
                'which is a range variable,
                'so you can for example get it's address:
                Debug.Print ArrayCh(i) & " " & c.Address 'example
                '_____
                Set c = .FindNext(c)

            Loop While Not c Is Nothing And c.Address <> firstAddress
        End If
    Next i
End With

End Sub

1 つのセル内に検索された文字列のインスタンスが複数ある場合、FindNext の特性により、1 つの結果のみが返されることに注意してください。

それでも、見つかった値を別の値に置き換えるコードが必要な場合は、最初の解決策を使用しますが、少し変更する必要があります。

于 2013-09-12T08:58:04.467 に答える