PHPの正規表現の質問です

=======
みかん,愛媛産,1,和歌山産,4,海外産,3,
メロン,北海道産,1,大阪産,2,
レモン,・・・・



=======
というテキストがあります
これを
=======
みかん,愛媛産,1,
みかん,和歌山産,4
みかん,海外産,3,
メロン,北海道産,1,
メロン,大阪産,2,



=======
というように 各行の冒頭を各産別の前につけて 改行したいんですがどうすればいいですか?

回答の条件
  • 1人1回まで
  • 登録:
  • 終了:2016/06/14 00:56:20
※ 有料アンケート・ポイント付き質問機能は2023年2月28日に終了しました。

ベストアンサー

id:tezcello No.2

回答回数460ベストアンサー獲得回数69

ポイント150pt

> 正規表現の質問
正規表現の練習が目的でしょうか?
それとも
> 各行の冒頭を各産別の前につけて 改行したい
こちらが目的でしょうか?
こちらが目的なら、正規表現は使わなくても出来そうですけど。

正規表現を使わなければならない理由があるとします。

<?php
$rows = file('index.dat', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);

$result = '';

foreach ($rows as $row){
  // 品目と産地(と入荷数?)とに分ける
  preg_match('/([^,]+,)(.+)/', $row, $mch);
  // 産地から産地と数字の1ペア単位で取り出す(配列に収める)
  preg_match_all('/[^,]+,[^,]+,/', $mch[2], $habitats);

  // 産地の配列から一つずつ取り出して、品目と連結して出力用変数に収める
  foreach($habitats[0] as $habitat){
    $result .= $mch[1] . $habitat . "\n";
  }
}

var_dump($result);
?>

余程何かの理由が無い限り、テキストファイルから配列に入れるのなら file() を使うのがお手軽でしょう。
そして、配列の全要素について端から順にやっていく際には、for() ではなく foreach() を使うのが、見やすく、やろうとしている事が直感的に読み易いだろうと思います。

行内の並びが、単なる羅列ではなく意味があるので、その意味に従って取り出すというのが、後から読む際にも関連が分かり易いと思います。
なので、1行のデータから品目部分を取出して、後は別に処理します。

行の後半部分は、産地と数字がペアになっているので、そのような形で取り出します。複数ある場合があるので、preg_match_all() で該当する部分を全部取り出すようにします。

ここまで分解できれば、後は関係する部分を連結するだけです。


この流れを理解して頂ければ、正規表現を使う必要が無かったというのもお分かり頂けると思います。
1行を品目と産地関連部とに分けるのはカンマ( , )です。
産地と数値を分けるのも同じです。
ならば、1行をカンマで分けて配列にしてしまえば上と同じようなパーツが得られます。
(最初の例のループ内だけを示しています。)

<?php
  $habitats = explode(',', $row);
  $kind = array_shift($habitats);
  while(count($habitats) > 1){
    $place = array_shift($habitats);
    $qty = array_shift($habitats);
    $result .= $kind . ',' . $place . ',' . $qty . ',' . "\n";
  }
?>


別に分解しなくても、部分文字列を取り出すと考えれば、カンマの位置を strpos() を使って探れば、取り出すべき部分は判ります。
最初の探査結果では、品目の区切り位置。
次の探査結果は産地の、その次はペアになっている数値。
(同じくループ内のみ示します)

<?php
  $pos1 = 0;
  $pos2 = strpos($row, ',');
  $pos2++;
  $kind = substr($row, $pos1, $pos2 - $pos1);
  $pos1 = $pos2;
  while($pos2 = strpos($row, ',', $pos1)) {
    $result .= $kind;
    $pos2++;
    $pos2 = strpos($row, ',', $pos2);
    $pos2++;
    $result .= substr($row, $pos1, $pos2 - $pos1);
    $pos1 = $pos2;
    $result .= "\n";
  }
?>

区切り文字のカンマを含むように取り出す都合上、$pos2++; が目障りですが仕方ないです。
__strpos() や substr() で使う際に +1 する手もアリですが大差ないですよね

何れも結果はこんな感じ。

string 'みかん,愛媛産,1,
みかん,和歌山産,4,
みかん,海外産,3,
メロン,北海道産,1,
メロン,大阪産,2,
レモン,瀬戸内,2,
' (length=144)

その他の回答1件)

id:a-kuma3 No.1

回答回数4974ベストアンサー獲得回数2154

ポイント150pt

こんな感じで。

<?php
$data = file_get_contents(...);     // CSV をファイルから読み込み
$in_array = explode("\n", $data);   // 一行ずつ配列にばらす

$re = "/([^,]+,)/";
for ($i = 0 ; $i < count($in_array) ; ++$i) {
    preg_match_all($re, $in_array[$i], $tmp, PREG_SET_ORDER);
    for ($j = 1 ; $j < count($tmp) ; $j += 2) {
        $out_array[] = $tmp[0][0] . $tmp[$j][0] . $tmp[$j + 1][0];
    }
}

// 配列で
print_r($out_array);

// 改行でくっつけた文字列が欲しいなら、implode() で
$result = implode("\n", $out_array);
print_r($result);

?>

/([^,]+,)(([^,]+,)([^,]+,))+/ ってな感じの正規表現でいけるはずだったのですが、他の処理系と違って、繰り返しはマッチした最後のものしか返してくれないという...
# ちょっと、はまったのは、内緒だ

他2件のコメントを見る
id:gaugjiuaej

できました
ありがとうございました

2016/06/14 00:53:50
id:a-kuma3

因みに、先のコメントの時点でできなかったのは何が原因だったのでしょうか。
そういった辺りを書き込んでもらえると、後でこのページに迷い込んできた人にも有益なものになるんじゃないかと思うのですが :-)

2016/06/14 00:58:32
id:tezcello No.2

回答回数460ベストアンサー獲得回数69ここでベストアンサー

ポイント150pt

> 正規表現の質問
正規表現の練習が目的でしょうか?
それとも
> 各行の冒頭を各産別の前につけて 改行したい
こちらが目的でしょうか?
こちらが目的なら、正規表現は使わなくても出来そうですけど。

正規表現を使わなければならない理由があるとします。

<?php
$rows = file('index.dat', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);

$result = '';

foreach ($rows as $row){
  // 品目と産地(と入荷数?)とに分ける
  preg_match('/([^,]+,)(.+)/', $row, $mch);
  // 産地から産地と数字の1ペア単位で取り出す(配列に収める)
  preg_match_all('/[^,]+,[^,]+,/', $mch[2], $habitats);

  // 産地の配列から一つずつ取り出して、品目と連結して出力用変数に収める
  foreach($habitats[0] as $habitat){
    $result .= $mch[1] . $habitat . "\n";
  }
}

var_dump($result);
?>

余程何かの理由が無い限り、テキストファイルから配列に入れるのなら file() を使うのがお手軽でしょう。
そして、配列の全要素について端から順にやっていく際には、for() ではなく foreach() を使うのが、見やすく、やろうとしている事が直感的に読み易いだろうと思います。

行内の並びが、単なる羅列ではなく意味があるので、その意味に従って取り出すというのが、後から読む際にも関連が分かり易いと思います。
なので、1行のデータから品目部分を取出して、後は別に処理します。

行の後半部分は、産地と数字がペアになっているので、そのような形で取り出します。複数ある場合があるので、preg_match_all() で該当する部分を全部取り出すようにします。

ここまで分解できれば、後は関係する部分を連結するだけです。


この流れを理解して頂ければ、正規表現を使う必要が無かったというのもお分かり頂けると思います。
1行を品目と産地関連部とに分けるのはカンマ( , )です。
産地と数値を分けるのも同じです。
ならば、1行をカンマで分けて配列にしてしまえば上と同じようなパーツが得られます。
(最初の例のループ内だけを示しています。)

<?php
  $habitats = explode(',', $row);
  $kind = array_shift($habitats);
  while(count($habitats) > 1){
    $place = array_shift($habitats);
    $qty = array_shift($habitats);
    $result .= $kind . ',' . $place . ',' . $qty . ',' . "\n";
  }
?>


別に分解しなくても、部分文字列を取り出すと考えれば、カンマの位置を strpos() を使って探れば、取り出すべき部分は判ります。
最初の探査結果では、品目の区切り位置。
次の探査結果は産地の、その次はペアになっている数値。
(同じくループ内のみ示します)

<?php
  $pos1 = 0;
  $pos2 = strpos($row, ',');
  $pos2++;
  $kind = substr($row, $pos1, $pos2 - $pos1);
  $pos1 = $pos2;
  while($pos2 = strpos($row, ',', $pos1)) {
    $result .= $kind;
    $pos2++;
    $pos2 = strpos($row, ',', $pos2);
    $pos2++;
    $result .= substr($row, $pos1, $pos2 - $pos1);
    $pos1 = $pos2;
    $result .= "\n";
  }
?>

区切り文字のカンマを含むように取り出す都合上、$pos2++; が目障りですが仕方ないです。
__strpos() や substr() で使う際に +1 する手もアリですが大差ないですよね

何れも結果はこんな感じ。

string 'みかん,愛媛産,1,
みかん,和歌山産,4,
みかん,海外産,3,
メロン,北海道産,1,
メロン,大阪産,2,
レモン,瀬戸内,2,
' (length=144)

コメントはまだありません

この質問への反応(ブックマークコメント)

「あの人に答えてほしい」「この質問はあの人が答えられそう」というときに、回答リクエストを送ってみてましょう。

これ以上回答リクエストを送信することはできません。制限について

回答リクエストを送信したユーザーはいません