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

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

ただいまの
回答率

90.34%

  • PHP

    21285questions

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

  • 正規表現

    830questions

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

preg_replaceの挙動について

解決済

回答 2

投稿

  • 評価
  • クリップ 0
  • VIEW 280

gsuisk

score 64

 前提・実現したいこと

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

(例) 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/01 21:39

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

    キャンセル

  • 2018/06/01 21:51

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

    キャンセル

  • 2018/06/02 18:38

    ご回答ありがとうございます。そのような正規表現があるのですね、勉強になります。

    preg_replace_callback() の動きがいまいちわかりません。。
    あ行の変換の部分では、以下の認識です。間違っていたら教えてください。

    1) $inputからパターンに一致する部分を探す。
    2) 見つかり次第、その文字をコールバック関数の引数($matches)として渡し順に配列に格納。
    3) コールバック関数の返り値をreplacementとして置換。
    1)~3)を繰り返し、すべて置換された文字列が$resultに代入される。

    疑問:
    $matches の中身は配列 ["a", "a"]ですが、なぜ2つの "a" が存在するのか。
    $matches[1]とありますが、$matches[0]も "a" なのになぜ[1]を使うのか。

    ご回答いただけると幸いです。

    キャンセル

  • 2018/06/02 18:52

    その認識で良いと思います。

    考えてみれば今回の場合はそもそもグループ不要ですね。
    あえて理由を挙げるなら、慣習的に、グループを使う場合にはそのグループのマッチ内容を使うことが多いので、というぐらいです。

    グループ消して、 /(?<![b-df-hj-np-tv-z])[aiueo]/i と $matches[0] でいけますね。

    キャンセル

  • 2018/06/03 18:10

    ありがとうございます。

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

    キャンセル

  • 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で質問しよう!

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

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

  • PHP

    21285questions

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

  • 正規表現

    830questions

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