文字列の前後の空白を削除するR標準関数 trimws() では全角空白が消せない
文字列の前後の空白を削除する関数として多くのプログラミング言語にはtrimという関数があります。R にも標準で trimws() 関数( R: Remove Leading/Trailing Whitespace )があります。しかし、全角空白には対応していないようです。
StudentName # ASから取得した文字列 "山田 太郎 " trimws(StudentName) "山田 太郎 "
trimws() をかけても変わっていません。右側の全角空白が残っています。うーむ。
空白ってなんだ?
Trim()について調べていくと、空白とはなんだという命題にぶつかりました。空白は WhiteSpace と言うらしいですが、多くのプログラミング言語がChar.IsWhiteSpace メソッド (Char) (System)を意識(利用)し空白を認識しているらしいと感じました。
Unicode Character Searchでspaceで検索するとたくさん出てくるのですが、一部を抜粋すると…
(1) 半角空白 U+0020 (2) 全角空白 U+3000 (3) \t タブ U+0009 (4) \n LF U+000A (5) \r CR U+000D (6)他多数の空白文字 が該当しています。
多くの言語の trim() はこれらをトリムします。trimws() は半角空白、タブ、LF,CRをトリムしています。
正規表現に[:balnk:]がありますが、これは全角空白、半角空白、タブを表しています。
いろいろな言語を使っているとこういう微妙な挙動差が一番エラーにつながりやすいので注意したいと思います。
ユーザー関数 myTrim,LTrim,RTrim,Trim,xlTrim を作りました
何をトリムしているのかわからないのでは気持ちが悪いのでトリム対象を明示してtrim()関数を作りたいと思います。
下位関数 myTrim()
myTrim <- function(str,.where="both",.what=" \t\n\r"){ # .where トリム位置: l左側 r右側 b両側 a全体 # .what トリム対象: 順不問。 tgt<-paste0("[",.what,"]") # .where 指定に従い、delete .where<-substr(tolower(.where),1,1) if(regexpr(.where,"lba")[1]>0){str<-gsub(paste0("^",tgt,"+"),"",str)} if(regexpr(.where,"rba")[1]>0){str<-gsub(paste0(tgt,"+$"),"",str)} if(.where=="a"){str<-gsub(tgt,"",str)} return(str) }
myTrim()は 第一引数strは元の文字列、第二引数(.where、省略可)としてトリム位置、第三引数(.what、省略可)としてトリムする空白扱い文字を指定します。
第二引数 .where はトリム位置(省略時はb両側)を表し、 l左側 r右側 b両側 a全体 の4パータンです。大文字小文字を区別しません。文字列の中だけという指定はできません。
第三引数 .what はトリムしたい空白扱い文字を直接指定(全角空白 、半角空白 、\tタブ、\rCR、\nLF)します。省略時は全角半角空白、タブ、CR、LFです。順番は不問です。
使用例
まず、以下 空白 として半角/全角/\t/\r/\nすべてを指定(デフォルト)した上で、トリム位置の動作確認をしたいと思います。
str<-" あ い \n \t \r " # .where の動作確認 # 左側の空白削除 myTrim(str,.where="left") [1] "あ い \n \t \r " # 右側の空白削除 myTrim(str,.where="right") [1] " あ い" # 両側の空白削除 myTrim(str,.where="both") [1] "あ い" # 全体の空白削除 myTrim(str,.where="all") [1] "あい"
このように、.whereを指定することで左側/右側/両側/全体のどの位置にある空白を削除するか選べます。全ての空白を削除するという意味の="all"は他のtrimにはあまりないようです。
つづいて.whatを指定して空白扱いする文字を指定したいと思います。
(str<-" あ い \n \t \r ") [1] " あ い \n \t \r " myTrim(str,.where="right",.what=" ")#右側の半角空白 [1] " あ い \n \t \r " myTrim(str,.where="left",.what=" ")#左側の全角空白 [1] "あ い \n \t \r " myTrim(str,.where="right",.what=" \t\r")#右側の全半角空白\t\r [1] " あ い \n" myTrim(str,.where="both",.what=" \n\t\r")#両側の全半角空白\t\r\n [1] "あ い" myTrim(str,.where="all",.what="\n\t\r")#全体の\n\t\r [1] " あ い " myTrim(str,.where="all",.what=" ")#全体の半角空白 [1] " あ い\n\t \r " myTrim(str,.where="all",.what=" ")#全体の全角空白 [1] "あ い \n \t\r " (str<-"\t\t print('Hello world!')") [1] "\t\t print('Hello world!')" myTrim(str,.what="\t")#行頭\tインデント削除 [1] " print('Hello world!')"
このように位置と対象空白文字をきちんと指定できています。
上位関数 LTrim()、RTrim()、Trim()、xlTrim()
下位関数 myTrim() ができたところで、これをユーザーフレンドリーにした上位関数を作りたいと思います。やはり他言語同様の名称がわかりやすいのでLTrim()、RTrim()、Trim()としたいと思います。
# .what トリム対象を直接指定します # myTrim() と一緒にロードして下さい。 LTrim <- function(str,.what=" \t\n\r") { # 文字列の左側の空白を削除 return(myTrim(str,.where="left",.what)) } RTrim <- function(str,.what=" \t\n\r") { # 文字列の右側の空白を削除 return(myTrim(str,.where="right",.what)) } Trim <- function(str,.what=" \t\n\r") { # 文字列の両側の空白を削除 return(myTrim(str,.where="both",.what)) } xlTrim<- function(str,.what=" \t\n\r",.after=" ") { # .after 置き換え後の文字列中空白 str<-myTrim(str,.where="both",.what) if(toupper(.after)=="EXCEL") { str<-gsub(" [[:blank:]]+"," ",str) str<-gsub(" [[:blank:]]+"," ",str) str<-gsub("\t[[:blank:]]+","\t",str) }else{ str<-gsub("[[:blank:]]+",.after,str) } return(str) }
xlTrim(str,.what=" \t\n\r",.after=" ") は、Excelのワークシート関数を意識しています。
Excelのワークシート関数trim()は両側の空白を削除した後、文字列の中の空白が連続する場合には最初の空白を残して残りを削除しますから、1文字目が半角空白なら半角空白が残り、1文字目が全角空白なら全角空白が残ります。
xlTrim()は文字列の左右両側の空白扱い文字を削除。文字列中に空白扱い文字が連続する場合には空白文字1個とします。置き換え後の空白文字は第三引数で指定します。第三引数に"Excel"と指定するとExcelのワークシート関数と同様に連続空白の1文字目だけを残します。
動作確認
(str<-"\t 1.tab\t 2.半角 3.全角 end \t ") [1] "\t 1.tab\t 2.半角 3.全角 end \t " LTrim(str) [1] "1.tab\t 2.半角 3.全角 end \t " RTrim(str) [1] "\t 1.tab\t 2.半角 3.全角 end" Trim(str) [1] "1.tab\t 2.半角 3.全角 end" xlTrim(str) [1] "1.tab 2.半角 3.全角 end" xlTrim(str,.after=" ") [1] "1.tab 2.半角 3.全角 end" xlTrim(str,.after="Excel") [1] "1.tab\t2.半角 3.全角 end" xlTrim("山田 太郎") [1] "山田 太郎" xlTrim("山田 太郎",.after="_") [1] "山田_太郎"
xlTrim()のExcelモードはきちんと動作しています。tabから始まる空白文字列群はtab1個になり、半角空白から始まる空白文字列群は半角空白1個になり、全角空白から始まる空白文字列群は全角空白1個になっています。また.after=に空白でない文字を指定するときちんとその文字で区切られています。