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

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

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

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

正規表現

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

Q&A

解決済

2回答

2716閲覧

preg_replaceの挙動について

gsuisk

総合スコア72

PHP

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

正規表現

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

0グッド

0クリップ

投稿2018/06/01 10:01

前提・実現したいこと

ローマ字をひらがなに変換するプログラムを作成したいです。
「あ行」と「い行」のみを扱います。

(例) aki → あき  ki → き 

発生している問題

aki → あkい  ki → kい

のように「あ行」しか正しく表示されない。「か行」は始まりの子音kがそのままで、
終わりの母音の部分が「あ行」として表示されてしまう。

該当のソースコード

PHP

1$kana = [ 2 'あ', 'い', 'う', 'え', 'お', 3 'か', 'き', 'く', 'け', 'こ', 4]; 5 6$romaji = [ 7 'a', 'i', 'u', 'e', 'o', 8 'ka', 'ki', 'ku', 'ke', 'ko', 9]; 10 11 12$str = "aki"; // あき 13 14$str = to_kana($str, $romaji, $kana); 15echo $str;

PHP

1function to_kana($str, $romaji, $kana){ 2 3 $patterns = []; 4 foreach($romaji as $value){ 5 $patterns[] = '/' . $value . '/'; 6 } 7 8 $str = preg_replace($patterns, $kana, $str); 9 return $str; 10 11}

結果

あkい

疑問

preg_replece マニュアルより

pattern および replacement のいずれもが配列の場合、 各 pattern は 対応する replacement に置換されます。

この場合、配列のインデックス番号順「a」「i」「ki」の順番で置換するので、
aki → あki → あkい
となってしまうのでしょうか?

preg_replace の挙動としては以下の認識で正しいでしょうか?

1.「aki」の「a」を置換 2.「あki」として先頭の文字からパターンを探す 3.「あ」、「k」は一致せず「i」で初めて一致し置換 4.「あkい」として先頭の文字からパターンを探す

試したこと

'aki' を 'a' と 'ki' に分割して置換するために、まず 'a', 'k', 'i' のように文字を分割して配列に入れる。
配列を回して、子音の時には次の要素と連結させる。(kだったら次のiと連結してkiにする)

これを実装してみましたが、結局「ki」と連結されても preg_replace で最初にiが置換されるので「kい」となってしまいます。

当然ですが実装後の結果は「あkい」で変化ありませんでした。

※あまり意味ないと思いますが実装後のコードも下に記載します。(汚いコードですみません。)

「ki」を「き」に置換させるためにはどうすれば良いでしょうか?
preg_replaceでは難しい場合、他の方法では可能でしょうか?

どうか教えていただけると幸いです。

###実装後のコード

PHP

1function to_kana($str, $romaji, $kana){ 2 3 $a = str_split($str); 4 5 $patterns = []; 6 foreach($romaji as $value){ 7 $patterns[] = '/' . $value . '/'; 8 } 9 10 $tmp =""; 11 12 foreach ($a as $key => $word) { 13 14 if($key != 0){ 15 $pre = $key-1; 16 $w = $a[$pre]; 17 $rep = preg_replace($patterns, $kana, $w); 18 //もし1つ前の要素が置換されていなかったら(子音だったら) 19 if($rep === $w){ 20 continue; 21 } 22 23 } 24 25 $append =""; 26 27 $replaced = preg_replace($patterns, $kana, $word); 28 //もし置換されなかったら(子音だったら) 29 if($replaced === $word){ 30 $append = $a[$key].$a[$key+1]; 31 $replaced = preg_replace($patterns, $kana, $append); 32 } 33 34 $tmp .= $replaced; 35 } 36 return $tmp; 37 38}

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

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

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

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

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

guest

回答2

0

ベストアンサー

なんか間違った回答を投稿した気がするけど、見直しました。
あ行の置き換えを最後に回すしか無いと考えます。
kaのaを先に置き換えてしまうとkなど子音のみ残ってしまうのは当たり前です。

php

1<?php 2 3function to_kana($str, $romaji, $kana){ 4 5 $patterns = []; 6 foreach($romaji as $value){ 7 $patterns[] = '/' . $value . '/'; 8 } 9 10 $str = preg_replace($patterns, $kana, $str); 11 return $str; 12 13} 14 15$kana = [ 16 'か', 'き', 'く', 'け', 'こ', 17 'あ', 'い', 'う', 'え', 'お', // あ行は最後! 18]; 19 20$romaji = [ 21 'ka', 'ki', 'ku', 'ke', 'ko', 22 'a', 'i', 'u', 'e', 'o', // あ行は最後! 23]; 24 25 26$str = "aki"; // あき 27 28$str = to_kana($str, $romaji, $kana); 29echo $str;

《実行結果》
イメージ説明

正規表現パターンを駆使しないので、
preg_replace()でなくstr_replace()の方が少しでも処理が早くなる。

php

1<?php 2 3function to_kana($str, $romaji, $kana){ 4 5 //$patterns = []; 6 //foreach($romaji as $value){ 7 // $patterns[] = '/' . $value . '/'; 8 //} 9 10 //$str = preg_replace($patterns, $kana, $str); 11 $str = str_replace($romaji, $kana, $str); 12 return $str; 13 14} 15 16$kana = [ 17 'か', 'き', 'く', 'け', 'こ', 18 'あ', 'い', 'う', 'え', 'お', 19]; 20 21$romaji = [ 22 'ka', 'ki', 'ku', 'ke', 'ko', 23 'a', 'i', 'u', 'e', 'o', 24]; 25 26 27$str = "aki"; // あき 28 29$str = to_kana($str, $romaji, $kana); 30echo $str;

もしもこの先ローマ字領域全体に拡張するとなると、
'pya'みたいな3文字使うものを配列の先の方に置いて、
'n'はあ行らといっしょでいいかもしれません。
(「ん」をnnとするかnとするか、kaniを「かに」にせず「かんい」にするためのローマ字の工夫とか、検討事項はまだあるけども。)

投稿2018/06/01 10:10

編集2018/06/01 12:48
退会済みユーザー

退会済みユーザー

総合スコア0

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

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

yambejp

2018/06/01 10:34

配列でいれかれるならpreg_replaceよりstr_replaceのほうがよいですね print str_replace($romaji,$kana,"aki");
退会済みユーザー

退会済みユーザー

2018/06/01 10:42 編集

そう、正規表現パターンを駆使しないのだから、str_replaceで十分な案件。(回答に反映しました。)
gsuisk

2018/06/02 09:40

ご回答ありがとうございます。 あ行やローマ字が3文字になるものなどは、他で一致してしまうので最初に置いておくと良いのですね。 確かに正規表現は使っていませんでした。str_replaceで十分ですね。 ご丁寧にありがとうございました。
guest

0

正規表現の否定的後読みを使えば、簡単にあ行の抽出ができます。
下記は前方に子音となるアルファベットがない a,i,u,e,o を抽出するパターンです。

/(?<![b-df-hj-np-tv-z])([aiueo])/i

正規表現で抽出できるなら、あとは preg_replace_callback() で変換してやるだけですね。

か行などは普通に

/(k[aiueo])/i

で抽出できます。

php

1$kana = [ 2 'a' => 'あ', 3 'i' => 'い', 4 'u' => 'う', 5 'e' => 'え', 6 'o' => 'お', 7 'ka' => 'か', 8 'ki' => 'き', 9 'ku' => 'く', 10 'ke' => 'け', 11 'ko' => 'こ', 12]; 13 14$input = 'aki'; 15 16// あ行の変換 17$result = preg_replace_callback('/(?<![b-df-hj-np-tv-z])([aiueo])/i', function ($matches) use ($kana) { 18 return $kana[strtolower($matches[1])]; 19}, $input); 20 21// か行の変換 22$result = preg_replace_callback('/(k[aiueo])/i', function ($matches) use ($kana) { 23 return $kana[strtolower($matches[1])]; 24}, $result);

投稿2018/06/01 12:11

yhg

総合スコア2161

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

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

退会済みユーザー

退会済みユーザー

2018/06/01 12:39

せっかくなので質問させていただきますが、 preg_replace_callback()戦法をとった場合、この先ローマ字領域全体に拡張すると 「行」ごとにpreg_replace_callback()を記述する予定でしょうか?
yhg

2018/06/01 12:51

「shi」などの3文字のものや「nn」、促音などの例外を除いた子音は正規表現でまとめて出来ると思います。 全部で4回~ぐらい preg_replace_callback すれば良いんじゃないかと思います。 正規表現を上手くまとめれば1回で出来るでしょうけど、それだと可読性が著しく低くなって回答としては不向きかなと思ってやめました。
gsuisk

2018/06/02 09:38

ご回答ありがとうございます。そのような正規表現があるのですね、勉強になります。 preg_replace_callback() の動きがいまいちわかりません。。 あ行の変換の部分では、以下の認識です。間違っていたら教えてください。 1) $inputからパターンに一致する部分を探す。 2) 見つかり次第、その文字をコールバック関数の引数($matches)として渡し順に配列に格納。 3) コールバック関数の返り値をreplacementとして置換。 1)~3)を繰り返し、すべて置換された文字列が$resultに代入される。 疑問: $matches の中身は配列 ["a", "a"]ですが、なぜ2つの "a" が存在するのか。 $matches[1]とありますが、$matches[0]も "a" なのになぜ[1]を使うのか。 ご回答いただけると幸いです。
yhg

2018/06/02 09:52

その認識で良いと思います。 考えてみれば今回の場合はそもそもグループ不要ですね。 あえて理由を挙げるなら、慣習的に、グループを使う場合にはそのグループのマッチ内容を使うことが多いので、というぐらいです。 グループ消して、 /(?<![b-df-hj-np-tv-z])[aiueo]/i と $matches[0] でいけますね。
gsuisk

2018/06/03 09:10

ありがとうございます。 /(?<![b-df-hj-np-tv-z])[aiueo]/i だと $matches は array(1) { [0]=> string(1) "a" } となりました。
gsuisk

2018/06/03 09:11

/(?<![b-df-hj-np-tv-z])([aiueo])/i としたときの $matches が array(2) { [0]=> string(1) "a" [1]=> string(1) "a" }  と "a" が2つ存在するのはどうしてでしょうか?
yhg

2018/06/03 09:45

() で囲むことをグループ化と呼んで、一部の例外 (?:...) や (?<!...) などを除いたグループは、キャプチャと言ってストックされていきます。そして $matches には、[0] に全体、[1] 以降はグループ1から順にストックされたものが入ってます。 したがって、今回の場合はグループが2つありますが、1つ目のグループは例外にあたるのでキャプチャされず、2番目のグループが最初にストックされて[1]に入るわけです。 preg_replace_callback ではなく preg_replace を使った場合には、キャプチャしたグループは $1, $2 のように第2引数に入れることができます。これを後方参照と呼びます。 echo preg_replace('#(.+).(gif|jpe?g|png)$#i', '$1.webp', 'abcd.jpg'); // -> abcd.webp と表示される
gsuisk

2018/06/03 11:38

ありがとうございます。理解しました。 $matches[0] には全体マッチの "a" が入り、[1] には例外を除く1つ目のグループ ([aiueo]) のにマッチした "a" がキャプチャされ入ったわけですね。 今回のケースでは、全体マッチ [0] と グループマッチ [1] の値が同じだったので混乱してしまいました、、、 [aiueo] とグループを消した時は、例外(否定的後読み)を除きグループが存在していなかったので、 $matches[1]以降には何も入らないのですね。 そして全体マッチの $matches[0] にしか値が入らなかったのですね。 とてもわかりやすかったです!ご親切にありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問