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

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

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

PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

正規表現

正規表現とは特定の文字列によるパターンマッチングを行う際に用いられる宣言型プログラミングです。

Q&A

解決済

3回答

1367閲覧

特殊な規則で作られた文字列からCSVを作成する関数を作りたい

salene05

総合スコア13

PHP

PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

正規表現

正規表現とは特定の文字列によるパターンマッチングを行う際に用いられる宣言型プログラミングです。

0グッド

0クリップ

投稿2015/11/19 09:57

以下の規則で作られた文字列があります。

・ '[' , ']' によって入れ子に、もしくは並列に分けられます。
・'['のすぐ後には必ず大文字のアルファベットのみで構成される単語が付きます。これがtypeデータになります。
・typeデータの後にstrデータが続きます。typeデータとstrデータは必ず半角スペースで区切られます。
・strデータ内に '[' , ']' が含まれることはありません。strデータ内に'[' , ']'以外の記号(大文字小文字の英数字、カナ漢字、他の記号)が含まれることはあります。
・strデータが存在する入れ子の「深さ」をrankデータと定義します。あるstrデータの前にある '[' の数 と ']'の数 の差は、rankと等しいです。

たとえば、[A inu [B neko nyan [A tori] [AB kizi]] [B musi kabuto musi [CB taka]]]のような文字列です。

この文字列を解析して、CSVデータを作成する関数を作成したいです。

たとえば

[A inu [B neko nyan [A tori] [AB kizi]] [B musi kabuto musi [CB taka]]]

という文字列を解析すると、

rank,type,str
1,A , inu
2,B , neko nyan
3,A , tori
3,AB ,kizi
2,B , musi kabuto musi
3,CB ,taka

というCSVデータが作成されます。

このような動作を行う関数を教えてください。

自分でも文字列を一文字ずつ読み込んで、ifとpreg_matchで文字を判別しながら動作するものを作りましたが、圧倒的に遅いです。

array_walk_recursiveなどをうまく使えば高速に処理できるのかもしれませんが、自分では思いつきませんでした。

よろしくお願いします。

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

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

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

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

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

guest

回答3

0

ベストアンサー

フォーマット的には正規表現や再帰的な処理も必要ないでしょうから、こんな感じでしょうか。

PHP

1<?php 2 3$str = "[A inu [B neko nyan [A tori] [AB kizi]] [B musi kabuto musi [CB taka]]]"; 4 5$tmp = explode("[", $str); 6 7unset($tmp[0]); 8 9$last_depth = null; 10$last_end_count = 0; 11 12echo "rank,type,str".PHP_EOL; 13foreach($tmp as $key => $val){ 14 //$tag_end_count[$key] = mb_substr_count($val,"]"); 15 $current_end_count = mb_substr_count($val,"]"); 16 17 if(!$last_depth){ 18 $current_depth = 1; 19 }else{ 20 21 $current_depth = $last_depth + 1 - $last_end_count; 22 } 23 $last_depth = $current_depth; 24 //出力 25 echo $current_depth.","; 26 //rank以降のデータをスペースで分割し、最初のデータをtypeとする 27 $data_tmp = explode(" ",str_replace("]","",$val)); 28 echo $data_tmp[0].","; 29 //typeを削除 30 unset ($data_tmp[0]); 31 //typeの後はスペースが必要? 32 echo " "; 33 34 echo implode(" ",$data_tmp); 35 36 echo PHP_EOL; 37 38 $last_end_count = $current_end_count; 39 40} 41

bash

1$ php parse.php 2rank,type,str 31,A, inu 42,B, neko nyan 53,A, tori 63,AB, kizi 72,B, musi kabuto musi 83,CB, taka

投稿2015/11/19 10:58

tanat

総合スコア18713

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

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

salene05

2015/11/19 11:44

おおぉ すごいです。 期待通りの結果が得られました。 本当にありがとうございます。
guest

0

いやぁ…初めて見るフォーマットですね。
というわけで自作になるかと思います。

まずifはともかく、preg_matchは遅いです。
explodeだけで大部分はなんとかなりそうですね。
mb_substrやmb_strpos、mb_strlen等を駆使して取る事になるかと思います。

戦略
1.[を分割して配列を作る -> $a
2.$a[0]は[を確認していないのでBaseLv0、
$a[1]は[を通過しているのでBaseLv1、

3.配列$aをループで回しながら1つずつ値を取り出し、]で分割する -> $b
4.$bの要素数が1であれば、]は存在しなかった。
5.$bの要素数が2以上であれば、それ以降の要素はBaseLvを減算してから処理する

PHP

1$word = "[A inu [B neko nyan [A tori] [AB kizi]] [B musi kabuto musi [CB taka]]]"; 2 3$down_lv = 0; 4$results = array(); 5$results[] = array("rank", "type", "str"); 6$a = explode('[', $word); 7foreach ($a as $base_lv => $up_word) { 8 if (trim($up_word) === "") { 9 continue; 10 } 11 $b = explode(']', $up_word); 12 foreach ($b as $key => $core_word) { 13 if ($key > 0) { 14 $down_lv++; // 永続的にレベルダウンするので表示レベルが上がり続ける事はありません。 15 } 16 $target = trim($core_word); 17 if ($target === "") { 18 continue; 19 } 20 $left_word = mb_substr($target, 0, mb_strpos($target, ' ')); 21 $right_word = mb_substr($target, mb_strpos($target, ' ') + 1); 22 $results[] = array( 23 $base_lv - $down_lv, 24 $left_word, // $targetを砕いて得られたType 25 $right_word, // $targetを砕いて得られた文字列 26 ); 27 } 28} 29 30// ここから出力して確認 31foreach ($results as $seeds) { 32 echo "{$seeds[0]}, {$seeds[1]}, {$seeds[2]}\n"; 33} 34/* 35rank, type, str 361, A, inu 372, B, neko nyan 383, A, tori 393, AB, kizi 402, B, musi kabuto musi 413, CB, taka 42*/

ちなみにこの作戦だと、
全ての文字列を配列の中にぶち込んで作業するのでまぁまぁ富豪的で重いです。
本気でやるならmb_substrやmb_strpos、mb_strlenだけで最後までやり切るのが一番速いでしょうね。
やりたくは無いですが、回答者の回答の中で納得の速度が得られなければ試してみてください。

投稿2015/11/19 11:01

編集2015/11/19 11:25
miyabi-sun

総合スコア21158

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

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

miyabi-sun

2015/11/19 11:50 編集

後から関数として切り出し安いように、$resultsに格納するようにしました。 まぁ、このせいでメモリを倍々で浪費する事になってます。 最初の文字列が数十MB以上…ヘタするとGB級のものだとすると本気で太刀打ち出来ないでしょう。 美しさ(変数名以外)はまぁまぁの自信作ですが、 速度はおそらくtanatさんのものより落ちるかと思います。 (10万件の同じデータで計測しても同じくらいでした。。。)
salene05

2015/11/19 11:56

>ifはともかく、preg_matchは遅いです。 なるほどです。str_splitで切り出して、preg_matchで判定~なんてやってましたorz >最初の文字列 たぶん、多いデータでも10kBいかないと思います。 ちなみにさっきまではstr_replaceとpreg_replaceで無理やりjsonに書き換えて、json_decode→配列操作~的な方法でも行けるかななんて考えたりしました。 とても参考になりました。ありがとうございました。
guest

0

1文字ずつ読み込んで解析すればそんなに時間掛かりますか
[ と ] の数はひとしく、この2つはデータ中には出てこない。
データの先頭は必ず[ で始まるのだから、単純に、¥s*¥[
で分割した結果の個数がcsvの行数で、あとは、配列を先頭から
処理するとき、rank += 1 をやりつつ、文字列に ] が見つかるたびに
rank -= 1 を行う。
各文字列の先頭は、後続の半角スペースまで、type が確定する、その後文字列の末尾までが str になる。

hairetu, rank, index, ch, str、index2
で6つ変数があれば行けそうな気がします。

こんなルールのフォーマットは見たことがないのできっと独自の仕様なんでしょうね。
だとしたら、phpには、このフォーマットを解析可能な関数は無いと思いますよ。

ちなみに、このフォーマットの正式名称とかあるんですか。圧倒的に高速動作する見本はどのような言語でできてるんですか。

投稿2015/11/19 10:25

ipadcaron

総合スコア1693

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

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

salene05

2015/11/19 11:57

ありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問