質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Q&A

解決済

5回答

15626閲覧

配列の中から配列を検索する

htsign

総合スコア870

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

0グッド

2クリップ

投稿2015/11/17 15:20

編集2015/11/17 15:22

お世話になります。
主題の通りなのですが、配列から配列を検索する手法、または直接的なメソッドを探しています。

現在はとりあえず以下のようなコードで実現していますが、よりよい方法があればぜひお教えください。
また、C#に限らず他の言語でも、「この言語なら一行で書ける」等ございましたらお聞かせください。

lang

1// 拡張メソッド定義 2public static class IEnumerableExtensions 3{ 4 public static int IndexOf<T>(this IEnumerable<T> source, IList<T> list) 5 { 6 if (list == null || list.Count == 0) return -1; 7 8 int index = 0; 9 foreach (T item in source) 10 { 11 if (item != null && item.Equals(list[0])) 12 { 13 // 最初の1要素が一致したら切り出して比較 14 var part = source.Skip(index).Take(list.Count); 15 if (part.SequenceEqual(list)) return index; 16 } 17 ++index; 18 } 19 return -1; 20 } 21}

lang

1var sentence = "Lorem ipsum dolor sit amet, consectetur adipisicing elit".Select(Convert.ToByte); 2byte[] searchBytes = { 0x6f, 0x6c }; // 'o', 'l' 3Console.WriteLine(sentence.IndexOf(searchBytes)); // ==> 13

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答5

0

不具合あるかもしれませんが

C#

1public static class IEnumerableExtensions 2{ 3 public static IEnumerable<int> IndicesOf<T>(this IEnumerable<T> source, IList<T> target) 4 { 5 if (target == null || target.Count == 0) 6 return Enumerable.Empty<int>(); 7 return source 8 .Select((x, i) => new { index = i, value = source.Skip(i).Take(target.Count) }) 9 .Where(x => x.value.SequenceEqual(target)) 10 .Select(x => x.index); 11 } 12 13 public static int IndexOf<T>(this IEnumerable<T> source, IList<T> target) 14 { 15 return source.IndicesOf(target).DefaultIfEmpty(-1).First(); 16 } 17}

C#

1var source = new int[] { 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, }; 2var target = new int[] { 3, 4 }; 3 4foreach(var x in source.IndicesOf(target)) 5{ 6 Console.WriteLine(x); 7} 8//3 9//8 10 11Console.WriteLine(source.IndexOf(target)); 12//3

投稿2015/11/18 00:46

ozwk

総合スコア13521

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

htsign

2015/11/18 01:22

ありがとうございます。 私が書いたコードと本質的に同じでありながら、より洗練されていますね。 しかもIndicesOfで複数の結果が欲しいケースも考慮されていて素晴らしいです。
len_souko

2015/11/19 16:29

調べて試してる間により汎用的でいいものが上がっていました きもはLinqのSelectで第2引数にindexを取得することができるのでそれを利用してindexとほしいデータのセットを匿名クラスでまとめると以後のメソッドチェーンでいい感じに利用できるって処です ちなみに自分は以下のサイトを参考にして使ってました http://qiita.com/RyotaMurohoshi/items/118d4aa4d5be8c71c9ce
htsign

2015/11/24 15:39

Selectメソッドの第二引数を使って匿名クラスを作るテクニックは時折役に立ちますよね。 便利なのですが、場合によっては逆に見にくくなることもあり難しいところです…。
guest

0

力押しでいいならRubyだと何も考えずに一行でかけます。

Ruby

1sentence = "Lorem ipsum dolor sit amet, consectetur adipisicing elit".each_byte.to_a 2searchBytes = [0x6f, 0x6c] # 'o', 'l' 3puts sentence.each_cons(searchBytes.size).find_index(searchBytes) # ==> 13

最悪計算量はO(mn)ですし、最悪m個もArray生成するし、速度は度外視です。
(yubaさんが言っているKMP法やBM法を応用したやりかたはちょっと後から試そうかな)

投稿2015/11/17 22:23

raccy

総合スコア21735

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

htsign

2015/11/18 00:45

ありがとうございます。 なるほど。each_consメソッドは初めて知りましたが、先頭を1ずつ進めながら指定の長さの配列を列挙するというものですね。そしてfind_indexで一致するまで進める、と。 確かに効率がいいとは言えないでしょうが、Rubyらしい面白い解法だと思いました。 効率度外視であれば流れるように書けるのはRubyのよいところですね。
guest

0

C#よりもC++の方が得意なので、まずはC++だとこんな感じというのをお見せします。

C++

1#include <iostream> 2#include <algorithm> 3#include <vector> 4 5int main() 6{ 7 std::string sentenceText = "Lorem ipsum dolor sit amet, consectetur adipisicing elit"; 8 std::vector<uint8_t> sentence(sentenceText.begin(), sentenceText.end()); 9 std::vector<uint8_t> searchBytes = {0x6f, 0x6c}; 10 11 // 1行で書けます 12 auto iter = std::search(sentence.begin(), sentence.end(), searchBytes.begin(), searchBytes.end()); 13 14 // 結果は13 15 std::cout << iter - sentence.begin() << std::endl; 16 return 0; 17}

C#でこんな感じに書けるのかどうか、私も興味あります。

投稿2015/11/17 15:44

catsforepaw

総合スコア5938

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

htsign

2015/11/17 16:27

ありがとうございます。 C++は勉強し始めたばかりで理解が浅いですが、いただいたコードをググりながら咀嚼しました。vectorはものすごく便利ですね…。 型が制限されていないことも汎用性が高くて素晴らしいと思います。
guest

0

ベストアンサー

文字列探索という課題になりますね。
質問文中でお示しの方法は、長さmの文字列中で長さnの文字列を探すのに最悪でほぼmn回の比較が発生し、効率的ではありません。

よく知られている(情報科学の講義で教わる)効率的な手法としてはKMP法、BM法というのがあります。その他に、BM法の改良型で実装しやすく効率も良い手法としてSunday法、またの名をクイックサーチと呼ぶものがあり、以前C#で書いたコードがあるので明日まだ質問が開いていたら載せます。

.NET Frameworkには文字列探索のメソッド用意されてないんですよね。

--- 追記

c#

1/// <summary> 2/// byte配列の中からbyteパターンと一致する並びを検索する。 3/// アルゴリズムはSunday法。 4/// </summary> 5/// <param name="pattern">探すパターン</param> 6/// <param name="text">探索範囲となる配列</param> 7/// <returns>発見した場合は開始位置、発見できなかった場合は-1</returns> 8public static int SearchBytes(this byte[] text, byte[] pattern) 9{ 10 int patternLen = pattern.Length, textLen = text.Length; 11 12 // 移動量テーブルの作成 13 int[] qs_table = new int[byte.MaxValue + 1]; 14 15 // デフォルト(パターン中に存在しないキャラクタが比較範囲の直後にあった)の場合、 16 // 次の比較範囲はそのキャラクタの次。(=比較範囲ずらし幅はパターン長+1) 17 for (int i = qs_table.Length; i-- > 0; ) qs_table[i] = patternLen + 1; 18 19 // パターンに存在するキャラクタが比較範囲の直後にあった場合、 20 // 次の比較範囲は、そのキャラクタとパターン中のキャラクタを一致させる位置に。 21 for (int n = 0; n < patternLen; ++n) qs_table[pattern[n]] = patternLen - n; 22 23 int pos; 24 25 // 移動量テーブルを用いて、文章の末尾に達しない範囲で比較を繰り返す 26 for (pos = 0; pos < textLen - patternLen; pos += qs_table[text[pos + patternLen]]) 27 { 28 // 一致するか比較。一致したら、そのときの比較位置を返す。 29 if (CompareBytes(text, pos, pattern, patternLen)) return pos; 30 } 31 32 // 文章の末尾がまだ未比較なら、そこも比較しておく 33 if (pos == textLen - patternLen) 34 { 35 // 一致するか比較。一致したら、そのときの比較位置を返す。 36 if (CompareBytes(text, pos, pattern, patternLen)) return pos; 37 } 38 39 // 一致する位置はなかった。 40 return -1; 41} 42 43/// <summary> 44/// 配列(pattern)が別の配列(text)に含まれているかを判定する。 45/// 46/// pos + patternLen が text.Length より大きかったり 47/// pos や patternLen が 0 未満だったり、 48/// needdleLen が pattern.Length より大きかったりすると 49/// ArrayOutOfBoundException が発生する。 50/// </summary> 51/// <param name="text">この配列の pos 番目からを pattern と比較する</param> 52/// <param name="pos">text のどこから比較するか</param> 53/// <param name="pattern">この配列全体が、text の pos 番目からと一致しているかを判定する</param> 54/// <param name="patternLen">patternのうち一致判定する長さ</param> 55/// <returns></returns> 56static bool CompareBytes(byte[] text, int pos, byte[] pattern, int patternLen) 57{ 58 for (int comparer = 0; comparer < patternLen; ++comparer) 59 { 60 if (text[comparer + pos] != pattern[comparer]) return false; 61 } 62 return true; 63} 64

投稿2015/11/17 16:05

編集2015/11/18 03:09
yuba

総合スコア5568

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

htsign

2015/11/17 16:34

ありがとうございます。 本質問は、例として示したコードがたまたま文字列探索になっていただけでして、最終的な目標は型に制限されない配列の検索です。 ただ文字列から検索すればよいだけであれば System.String#IndexOf(string value) で事足りる話ですので…。 とはいえ私はアルゴリズム方面にはとても疎いので、KMP法・BM法等キーワードを出していただいただけでもとても参考になります。 時間を見つけつつ調べてみたいと思います。
catsforepaw

2015/11/17 22:54

> 効率的ではありません。 実際問題として、論理的には効率的でも実装してみると期待したほど速くなかったというケースが意外と多いです。KMP法というのはよく知らないのですが、BM法はたぶん典型例かと。 検索対象と検索パターンのサイズが十分に長ければ威力を発揮しますが、質問にあるコード例のように検索パターンが短い場合は、オーバーヘッドがない分力任せの方が速くなる可能性が高いでしょう。比較してみるのも面白いかもしれません。 速さを追求するなら、用途や状況に応じてアルゴリズムを変える必要があります。
yuba

2015/11/18 03:14

Sunday法で書いた配列内配列探索のコードを置きました。 これはByte配列についてのものになっていまして、文字ごとのジャンプ量というのをテーブル化しているのでこのままだと文字や整数型の配列についてしか探索できませんが、ジャンプ量テーブルをDictionayで実装すればIComparableなすべての型に応用できます。 > catsforepaw さん 短いテキストとパターンではナイーブアルゴリズムの方が効率的な場合があると言うのはおっしゃるとおりです。 この課題の性質の良いところは、ランタイムにテキストとパターンをもらった時点で長さを見てどちらか選択できるところなので、長いテキストが来たときだけテーブル生成方式を使うといったやり方ができます。
catsforepaw

2015/11/18 14:03

コード拝見しました。基本はBM法ですね。 さて、このコード例(他でよく見かけるコード例も)はbyteなのでテーブルのサイズは256ですが、charの場合はサイズ65536になってしまいます。検索のたびに256KiBの領域を初期化することになるのですが。また、このエントリーでは型を特定していないので、もしintだったら? 連想配列使う? Windowsは早い時期からネイティブ文字コードがUTF-16になっているので、同様の問題(疑問)がありまして、私としてはメモリ内で文字列検索をするのにBM法(亜種も含めて)は適さないと考えております。素直に標準の機能(クラスライブラリ等)を使うのが無難かと。 汎用のデータベースの検索エンジンを自前で実装するというのであれば役に立つのかもしれませんが。
yuba

2015/11/18 14:43

intどころか、==だけが定義されたクラスかもしれません。ですのでコメントにもあります通り連想配列を使うのを提案しています。 標準クラスライブラリとおっしゃいますが、.NET Frameworkにはそういうメソッドがないので自前で書くしかないんですよ。
catsforepaw

2015/11/18 15:42

単純な方法ならポインタ(インデックス)を進めるのにアセンブリ命令1個(0.5クロックぐらい)ですみますが、連想配列となると実装にもよるでしょうが、まずテーブル作成に時間がかかりますし、値を参照するにも数十クロックぐらい(あるいはそれ以上?)かかるのではないでしょうか。場合によっては連想配列も自分で作らないといけなくなるかもしれません。 ですので、こういうことはアルゴリズムの速さ(O(n)とか)だけを見ても無意味で、実際に実装して計測して比較しないと判らないのです。 で、実際の業務においてはそこまでやる価値があるのかどうかを見極める必要があり、大抵はそんな価値はないので、実装コストが最も低い(単純な)方法で実装することになります。
htsign

2015/11/18 16:03

コードの追記ありがとうございます。 平日はあまり時間が取れないので休日にググって考え方の大枠でも読みながらゆっくり咀嚼していきたいと思います。 ところでIComparableを実装したすべての型で応用可能であると仰いますが、nullを含んだ配列にも使えるのでしょうか。 pattern[n]を添え字に取るqs_tableがあることを考えると、あるいはそれがNullableなTKeyを持つDictionaryを使うことにつながるのかとも推測しましたが…。 またcatsforepaw様が仰るように検索する配列の型が取りうる範囲の初期化を毎回行うということですが、連想配列を使うとこれが回避できるのでしょうか? Dictionaryも要素数が増えればそれだけメモリを消費することには違いないのでは、と思ってしまいます。 近年は富豪的プログラミングが主流であるようにリソースの消費に対して気を払わなくてもよい、という感じでしょうか。
yuba

2015/11/18 22:18

連想配列なら全領域をpatternLen+1で初期化する必要はなくなります。キーが見つからなければpatternLen+1が取れたことにすればいいので。null含みのパターン・テキストを扱うにはDictionaryには入りませんのでqs_table_nullなんて変数を1個用意してここに置いておけば解決です。 連想配列のサイズはパターン中の文字種類数だけですので、言うほど富豪的とはならないかと。
htsign

2015/11/19 06:41

なるほど。そういう意図だったのですね。 そしてやはりnullは特別扱いですか。nullがある場合も処理を工夫すれば可能であることが分かりましたので、このアルゴリズムの利用価値は高そうだと感じました。 本質問の目的のひとつは「よりよい解決方法」を問いかけるものであったため、もう少し募集して特にこれ以上なければ、この回答をBAとさせていただきたいと思います。
htsign

2015/11/24 15:32

本質問をクローズさせていただきました。 C++のcatsforepaw様やRubyのraccy様の回答も非常に興味深いものでしたが、今回はyuba様をBAとさせていただきました。 皆様ありがとうございました。
guest

0

すみません、大勘違いしてしまいました...


csharp

1var elm = sentence.Select((v, i) => new {val = (byte)v, idx = i}).FirstOrDefault(f => searchBytes.Contains(f.val)); 2var index = (elm == null) ? -1 : elm.idx;

投稿2015/11/18 00:33

編集2015/11/18 00:36
hsk

総合スコア728

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

htsign

2015/11/18 01:08

ありがとうございます。 そうですね、このコードですとやりたいことと違う結果が返ってきますね…。 質問にご興味を持っていただいて感謝します。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問