BETA

数字の読み方(42→よんじゅうに)を返すロジックをSQLとストアドで実装してみた。(PostgreSQL)

投稿日:2019-01-14
最終更新:2019-01-14

SQLでの実装

PostgreSQL9.2以降ではSQL関数でも引数に名前を指定できますが、便宜上$nスタイルを使用してます。

CREATE OR REPLACE FUNCTION numerical_to_reading(integer) RETURNS varchar AS $$  
  -- 数字の読み方を出力する。  
  WITH RECURSIVE  
    -- 数字の読み方を定義  
    -- 参考: https://www.coscom.co.jp/learnjapanese101/wordcategory/basicwords_numbers-c.html  
    numeral_reading(numeral, reading) AS (  
      VALUES  
        (0, ''),  
        (1, 'いち'), (2, 'に'), (3, 'さん'),  
        (4, 'よん'), (5, 'ご'), (6, 'ろく'),  
        (7, 'なな'), (8, 'はち'), (9, 'きゅう')  
    ),  
    -- 1桁ごとの読み方を定義(一、十、百、千)  
    small_digit(digit, reading) AS (  
      VALUES  
        (0, ''),                     -- 一の位の数については呼称なし。  
        (1, 'じゅう'), (2, 'ひゃく'), (3, 'せん')  
    ),  
    -- 例外的な数字の読み方  
    patch_small_digit_numeral(digit, numeral, reading) AS (  
      VALUES  
        (1, 1, ''), (2, 1, ''), (3, 1, 'いっ'),  -- 十、百、千(いっせん)の読み方を定義  
        (2, 6, 'ろっ'),                          -- 6百(ろっぴゃく)の読み方を定義  
        (2, 8, 'はっ'), (3, 8, 'はっ')            -- 8百(はっせん)、8千(はっせん)の読み方を定義  
    ),  
    -- 例外的な桁の読み方  
    patch_small_digit(digit, numeral, reading) AS (  
      VALUES  
        (1, 0, ''), (2, 0, ''), (3, 0, ''), -- 0の場合は十百千は読まない  
        (2, 3, 'びゃく'), (3, 3, 'ぜん'),  -- 3百(さんびゃく)、3千(さんぜん)の読み方を定義  
        (2, 6, 'ぴゃく'), (2, 8, 'ぴゃく')  -- 6百(ろっぴゃく)、8百(はっぴゃく)の読み方を定義  
    ),  
    -- 4桁区切りの桁ごとの読み方を定義(万、億、兆、...)  
    large_digit(digit, reading) AS (  
      VALUES  
        (4, 'まん'), (8, 'おく') -- 「兆」以降は省略。  
    ),  
    -- args.numeralに指定された数字を桁ごとに分割する。  
    digit_numeral_mapping(digit, numeral) AS (  
      SELECT  
          0,  
          substring(  
            abs($1)::varchar from char_length(abs($1)::varchar) for 1  
          )::integer  
       UNION ALL  
      SELECT  
          dnm.digit + 1,  
          substring(  
            abs($1)::varchar from char_length(abs($1)::varchar) - (dnm.digit + 1) for 1  
          )::integer  
        FROM digit_numeral_mapping AS dnm  
       WHERE dnm.digit < char_length(abs($1)::varchar) - 1  
    ),  
    -- 桁ごとに数字の読み方を出力する。  
    readings_per_digit(digit, reading) AS (  
      SELECT  
          dnm.digit,  
          COALESCE(psdn.reading, nr.reading)     -- 0~9までの数字  
            || COALESCE(psd.reading, sd.reading) -- 十百千の位  
            || COALESCE(ld.reading, '')          -- 万、億の位  
        FROM digit_numeral_mapping AS dnm  
     NATURAL JOIN numeral_reading AS nr  
        JOIN small_digit AS sd  
          ON sd.digit = dnm.digit % 4  
        LEFT JOIN large_digit AS ld  
          ON ld.digit = dnm.digit  
        LEFT JOIN patch_small_digit_numeral AS psdn  
          ON psdn.digit = dnm.digit % 4  
         AND psdn.numeral = dnm.numeral  
        LEFT JOIN patch_small_digit AS psd  
          ON psd.digit = dnm.digit % 4  
         AND psd.numeral = dnm.numeral  
       ORDER BY dnm.digit DESC  
    )  
  -- 数字の読み方を出力  
  SELECT  
      CASE  
        WHEN $1 = 0 THEN 'ぜろ'  
        WHEN $1 < 0 THEN 'まいなす'  
        ELSE ''  
      END || array_to_string(array_agg(reading ORDER BY digit DESC), '') AS reading  
    FROM readings_per_digit  
  ;  
$$ LANGUAGE SQL  
   IMMUTABLE  
   RETURNS NULL ON NULL INPUT;  

VALUES  
  (0, numerical_to_reading(0)),  
  (-15056, numerical_to_reading(-15056)),  
  (10001000, numerical_to_reading(10001000)),  
  (58095165, numerical_to_reading(58095165)),  
  (418313801, numerical_to_reading(418313801))  
;  

-- 0    "ぜろ"  
-- -15056    "まいなすいちまんごせんごじゅうろく"  
-- 10001000    "いっせんまんいっせん"  
-- 58095165    "ごせんはっぴゃくきゅうまんごせんひゃくろくじゅうご"  
-- 418313801    "よんおくいっせんはっぴゃくさんじゅういちまんさんぜんはっぴゃくいち"

ストアド(PL/pgSQL)での実装

CREATE OR REPLACE FUNCTION numerical_to_reading(numerical_value integer) RETURNS varchar AS $$  
  DECLARE  
    numerical_char CONSTANT varchar := abs(numerical_value)::varchar;  
    numerical_length CONSTANT integer := char_length(numerical_char);  
    digit integer;  
    currval integer;  
    ret varchar := '';  
  BEGIN  
    IF numerical_value = 0 THEN  
      -- 一桁の0の場合のみ「ぜろ」が読まれるため、すぐにRETURNで返す。  
      RETURN 'ぜろ';  
    ELSIF numerical_value < 0 THEN  
      -- マイナス符号の読み方を定義  
      ret := ret || 'まいなす';  
    END IF;  

    -- 整数部の読み方を求める  
    FOR i IN 1 .. numerical_length LOOP  
      digit := numerical_length - i + 1;  
      currval := substring(numerical_char FROM i FOR 1)::integer;  

      -- 数字の読み方を逐次解釈  
      -- 参考: https://www.coscom.co.jp/learnjapanese101/wordcategory/basicwords_numbers-c.html  
      CASE currval  
        WHEN 1 THEN  
          CASE digit % 4  
            WHEN 0 THEN  
              -- 1,000(いっせん)の読み方を定義  
              ret := ret || 'いっ';  
            WHEN 1 THEN  
              ret := ret || 'いち';  
            ELSE  
              -- 10, 100の場合は1自体は読まない。  
              NULL;  
          END CASE;  
        WHEN 2 THEN  
          ret := ret || 'に';  
        WHEN 3 THEN  
          ret := ret || 'さん';  
        WHEN 4 THEN  
          ret := ret || 'よん';  
        WHEN 5 THEN  
          ret := ret || 'ご';  
        WHEN 6 THEN  
          IF digit % 4 = 3 THEN  
            -- 6百(ろっぴゃく)の読み方を定義  
            ret := ret || 'ろっ';  
          ELSE  
            ret := ret || 'ろく';  
          END IF;  
        WHEN 7 THEN  
          ret := ret || 'なな';  
        WHEN 8 THEN  
          IF digit % 4 IN (3, 0) THEN  
            -- 8百(はっせん)、8千(はっせん)の読み方を定義  
            ret := ret || 'はっ';  
          ELSE  
            ret := ret || 'はち';  
          END IF;  
        WHEN 9 THEN  
          ret := ret || 'きゅう';  
        ELSE  
          NULL;  
      END CASE;  

      -- 1桁ごとの読み方を定義(一、十、百、千)  
      IF currval = 0 THEN  
        NULL;  
      ELSE  
        CASE digit % 4  
          WHEN 2 THEN  
            ret := ret || 'じゅう';  
          WHEN 3 THEN  
            IF currval = 3 THEN  
              -- 3百(さんびゃく)の読み方を定義  
              ret := ret || 'びゃく';  
            ELSIF currval IN (6, 8) THEN  
              -- 6百(ろっぴゃく)、8百(はっぴゃく)の読み方を定義  
              ret := ret || 'ぴゃく';  
            ELSE  
              ret := ret || 'ひゃく';  
            END IF;  
          WHEN 0 THEN  
            IF currval = 3 THEN  
              -- 3千(さんぜん)の読み方を定義  
              ret := ret || 'ぜん';  
            ELSE  
              ret := ret || 'せん';  
            END IF;  
          ELSE  
            NULL;  
        END CASE;  
      END IF;  

      -- 4桁ごとにつく単位の読み方を定義(万、億)※兆以降はサポート対象外  
      CASE digit  
        WHEN 5 THEN  
          ret := ret || 'まん';  
        WHEN 9 THEN  
          ret := ret || 'おく';  
        ELSE  
          NULL;  
      END CASE;  
    END LOOP;  
    RETURN ret;  
  END  
$$ LANGUAGE plpgsql  
   IMMUTABLE  
   RETURNS NULL ON NULL INPUT;  

VALUES  
  (0, numerical_to_reading(0)),  
  (-15056, numerical_to_reading(-15056)),  
  (10001000, numerical_to_reading(10001000)),  
  (58095165, numerical_to_reading(58095165)),  
  (418313801, numerical_to_reading(418313801))  
;  

-- 0    "ぜろ"  
-- -15056    "まいなすいちまんごせんごじゅうろく"  
-- 10001000    "いっせんまんいっせん"  
-- 58095165    "ごせんはっぴゃくきゅうまんごせんひゃくろくじゅうご"  
-- 418313801    "よんおくいっせんはっぴゃくさんじゅういちまんさんぜんはっぴゃくいち"
技術ブログをはじめよう Qrunch(クランチ)は、プログラマの技術アプトプットに特化したブログサービスです
駆け出しエンジニアからエキスパートまで全ての方々のアウトプットを歓迎しております!
or 外部アカウントで 登録 / ログイン する
クランチについてもっと詳しく

この記事が掲載されているブログ

勉強したことのメモ

よく一緒に読まれる記事

0件のコメント

ブログ開設 or ログイン してコメントを送ってみよう