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

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

ただいまの
回答率

89.64%

preg_replaceの挙動について

解決済

回答 2

投稿

  • 評価
  • クリップ 0
  • VIEW 1,108

gsuisk

score 70

 前提・実現したいこと

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

(例) aki → あき  ki → き 

 発生している問題

aki → あkい  ki → kい

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

 該当のソースコード

$kana = [
    'あ', 'い', 'う', 'え', 'お',
    'か', 'き', 'く', 'け', 'こ',
];

$romaji = [
    'a', 'i', 'u', 'e', 'o',
    'ka', 'ki', 'ku', 'ke', 'ko',
];


$str = "aki"; // あき  

$str = to_kana($str, $romaji, $kana);
echo $str;
function to_kana($str, $romaji, $kana){

    $patterns = [];
    foreach($romaji as $value){
        $patterns[] = '/' . $value . '/';
    }

    $str = preg_replace($patterns, $kana, $str);
    return $str;

}


結果

あ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では難しい場合、他の方法では可能でしょうか?

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

実装後のコード

function to_kana($str, $romaji, $kana){

  $a = str_split($str);

    $patterns = [];
    foreach($romaji as $value){
        $patterns[] = '/' . $value . '/';
    }

    $tmp ="";

    foreach ($a as $key => $word) {

      if($key != 0){
        $pre = $key-1;
        $w = $a[$pre];
        $rep = preg_replace($patterns, $kana, $w);
        //もし1つ前の要素が置換されていなかったら(子音だったら)
        if($rep === $w){
          continue;
        }

      }

        $append ="";

        $replaced = preg_replace($patterns, $kana, $word);
        //もし置換されなかったら(子音だったら)
        if($replaced === $word){ 
          $append = $a[$key].$a[$key+1]; 
          $replaced = preg_replace($patterns, $kana, $append);
        }

        $tmp .= $replaced;
      }
      return $tmp;

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

checkベストアンサー

+5

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

<?php

function to_kana($str, $romaji, $kana){

    $patterns = [];
    foreach($romaji as $value){
        $patterns[] = '/' . $value . '/';
    }

    $str = preg_replace($patterns, $kana, $str);
    return $str;

}

$kana = [
    'か', 'き', 'く', 'け', 'こ',
    'あ', 'い', 'う', 'え', 'お',  // あ行は最後!
];

$romaji = [
    'ka', 'ki', 'ku', 'ke', 'ko',
    'a', 'i', 'u', 'e', 'o',  // あ行は最後!
];


$str = "aki"; // あき  

$str = to_kana($str, $romaji, $kana);
echo $str;


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

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

<?php

function to_kana($str, $romaji, $kana){

    //$patterns = [];
    //foreach($romaji as $value){
    //    $patterns[] = '/' . $value . '/';
    //}

    //$str = preg_replace($patterns, $kana, $str);
    $str = str_replace($romaji, $kana, $str);
    return $str;

}

$kana = [
    'か', 'き', 'く', 'け', 'こ',
    'あ', 'い', 'う', 'え', 'お',
];

$romaji = [
    'ka', 'ki', 'ku', 'ke', 'ko',
    'a', 'i', 'u', 'e', 'o',
];


$str = "aki"; // あき  

$str = to_kana($str, $romaji, $kana);
echo $str;

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

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/06/01 19:34

    配列でいれかれるならpreg_replaceよりstr_replaceのほうがよいですね

    print str_replace($romaji,$kana,"aki");

    キャンセル

  • 2018/06/01 19:39 編集

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

    キャンセル

  • 2018/06/02 18:40

    ご回答ありがとうございます。
    あ行やローマ字が3文字になるものなどは、他で一致してしまうので最初に置いておくと良いのですね。

    確かに正規表現は使っていませんでした。str_replaceで十分ですね。
    ご丁寧にありがとうございました。

    キャンセル

+2

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

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

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

か行などは普通に

/(k[aiueo])/i

で抽出できます。

$kana = [
  'a' => 'あ',
  'i' => 'い',
  'u' => 'う',
  'e' => 'え',
  'o' => 'お',
  'ka' => 'か',
  'ki' => 'き',
  'ku' => 'く',
  'ke' => 'け',
  'ko' => 'こ',
];

$input = 'aki';

// あ行の変換
$result = preg_replace_callback('/(?<![b-df-hj-np-tv-z])([aiueo])/i', function ($matches) use ($kana) {
    return $kana[strtolower($matches[1])]; 
}, $input);

// か行の変換
$result = preg_replace_callback('/(k[aiueo])/i', function ($matches) use ($kana) {
    return $kana[strtolower($matches[1])];
}, $result);

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/06/03 18:11

    /(?<![b-df-hj-np-tv-z])([aiueo])/i としたときの $matches が

    array(2) { [0]=> string(1) "a" [1]=> string(1) "a" } 

    と "a" が2つ存在するのはどうしてでしょうか?

    キャンセル

  • 2018/06/03 18: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 と表示される

    キャンセル

  • 2018/06/03 20:38

    ありがとうございます。理解しました。

    $matches[0] には全体マッチの "a" が入り、[1] には例外を除く1つ目のグループ ([aiueo]) のにマッチした "a" がキャプチャされ入ったわけですね。

    今回のケースでは、全体マッチ [0] と グループマッチ [1] の値が同じだったので混乱してしまいました、、、

    [aiueo] とグループを消した時は、例外(否定的後読み)を除きグループが存在していなかったので、 $matches[1]以降には何も入らないのですね。 そして全体マッチの $matches[0] にしか値が入らなかったのですね。

    とてもわかりやすかったです!ご親切にありがとうございました。

    キャンセル

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

  • ただいまの回答率 89.64%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る