備忘ログ

チラシの裏的備忘録&メモ

Rでアラビア数字から漢数字に変換する方法を考えてみたその1

追記・いろいろ機能追加したアラビア数字から漢数字に変換する関数をパッケージ化してGithubに上げた

indenkun.hatenablog.com

Rで{zipnagu}というパッケージのkansuji2arabicという関数を使うとアラビア数字が漢数字になる。

そこで逆に、アラビア数字を漢数字に変換するには……と考えてみた。すでに既存のいい関数があるかもしれないとは思うが見つけられなかった。

アラビア数字を一文字一文字漢数字に置き換えるもの(1234なら一二三四となる)と、9999までの範囲で1234なら千二百三十四と変換するものを考えた。

追記: 1万以上に対応できる要因改良した

indenkun.hatenablog.com

自作関数

依存関係は{tidyverse}がインストールされて入れば解決される。

具体的には{magrittr}{stringr}がインストールされていると大丈夫。

library("magrittr")
arabic2kansuji <- function(num, zero = c("〇", "零")){
  
  zero <- match.arg(zero)
  
  arabicn <- "1234567890"
  if(zero == "〇") kansuji <- "一二三四五六七八九〇"
  else if(zero == "零") kansuji <- "一二三四五六七八九零"
  arabicn <- stringr::str_split(arabicn, "") %>% unlist()
  kansuji <- stringr::str_split(kansuji, "") %>% unlist()
  names(kansuji) <- arabicn
  
  num %>% stringr::str_replace_all(kansuji)
  
}

arabic2kansuji_num <- function(num){
  n <- stringr::str_split(num,
                     stringr::boundary(type = "character")) %>% 
    purrr::reduce(c) %>% 
    as.numeric()

  if(length(n) >= 5) warning("too long to convert.")
  
  m <- max(which(n >= 0)) - which(n >= 0) + 1
  names(n) <- m
 
  for(i in 1:length(n)){
    if(as.numeric(names(n[i])) %% 4 == 1){
      if(n[i] >= 1) n[i] <- arabic2kansuji(n[i])
    }
    else if(as.numeric(names(n[i])) %% 4 == 2){
      if(n[i] >= 2) n[i] <- paste0(arabic2kansuji(n[i]), "十")
      else if(n[i] == 1) n[i] <- "十"
    }
    else if(as.numeric(names(n[i])) %% 4 == 3){
      if(n[i] >= 2) n[i] <- paste0(arabic2kansuji(n[i]), "百")
      else if(n[i] == 1) n[i] <- "百"
    }
    else if(as.numeric(names(n[i])) %% 4 == 0){
      if(n[i] >= 2) n[i] <- paste0(arabic2kansuji(n[i]), "千")
      else if(n[i] == 1) n[i] <- "千"
    }
  
  n <- gsub("0", NA, n)  
  stringr::str_flatten(na.omit(n))  
}

自作関数について、arabic2kansujiRで文字列中の旧字体を新字体にまとめて変換する - Qiita を参考にほぼ流用した。

arabic2kansuji_num{zipnagu}kansuji2arabic_allを参考にした。

使ってみる

arabic2kansujiは単純に数字を漢数字に置き換えるだけ。

> arabic2kansuji(1234)
[1] "一二三四"

と返す。

漢数字以外を含んでいてもその部分は保持される。

> arabic2kansuji("1989年は1月7日までが昭和64年です")
[1] "一九八九年は一月七日までが昭和六四年です"
> arabic2kansuji("東京都新宿区西新宿2丁目8−1")
[1] "東京都新宿区西新宿二丁目八−一"

半角数字にしか対応していないので、全角は変換されない。

> arabic2kansuji("東京都新宿区西新宿2丁目8−1")
[1] "東京都新宿区西新宿2丁目8−1"

デフォルトでは0が〇に変換されるが引数で指定すると零にも変換できる。

> arabic2kansuji("今年は西暦2020年で令和2年です")
[1] "今年は西暦二〇二〇年で令和二年です"
arabic2kansuji("今年は西暦2020年で令和2年です", zero = "零")
[1] "今年は西暦二零二零年で令和二年です"

単純に一文字一文字置換しているだけなので、2020で二千二十は返せないので、そこはarabic2kansuji_numで対応(正しい答えを返すのは9999まで、1万以上も入力受け付けるが正しい答えを返さない)。

arabic2kansuji_numは数字しか受け付けない。

> arabic2kansuji_num(2020)
[1] "二千二十"
> arabic2kansuji_num(2020)
 エラー:  想定外のシンボルです  in "arabic2kansuji_num(2020年"
> arabic2kansuji_num("2020年")
 if (as.numeric(names(n[i]))%%4 == 1) { でエラー: 
   TRUE/FALSE が必要なところが欠損値です 
 追加情報:  警告メッセージ: 
1:  function_list[[k]](value):   強制変換により NA が生成されました 
2:  arabic2kansuji_num("2020年"):  too long to convert

1万以上を入力すると万とか億とかが入ってこないので正しい答えを返せない(今後の課題、アイディアはありそれができそうなコードを組んでいるが実装できていない)。

> arabic2kansuji_num(123456789)
[1] "一二千三百四十五六千七百八十九"
 警告メッセージ: 
 arabic2kansuji_num(123456789):  too long to convert

あとarabic2kansuji_numはループ処理を中でやっているので大量に数字を流し込むとちょっと遅い。

今後の課題

  • arabic2kansuji_numで1万以上も対応できるようにしたい。
  • arabic2kansuji_numで漢数字以外の文字列を保持したまま変換できるようにしたい。
  • ループ処理じゃない方法でスマートにやりたい。

ざっとこんなところ。