[topic] 末尾の空白文字を取り除く

与えられた文字列の末尾の空白文字を取り除く方法と、その操作が与えられた文字列を破壊するかどうか。取り除かれる空白文字の種類。

Posted feedbacks - Nested

Flatten Hidden

Pythonは文字列にrstrip(右側の空白文字を取り除く)というメソッドがある。 非破壊的。

取り除かれる空白文字はstring.whitespaceで定義されていて、 通常は'\t\n\x0b\x0c\r '。 ASCIIコードで表現すれば[9, 10, 11, 12, 13, 32]。 取り除く文字の種類を変える場合は引数に与える。

1
2
3
line.rstrip()

line.rstrip('\r\n') # remove \r and \n

Rubyは文字列がchomp,chomp!という2つのメソッドを持っている。chompは非破壊的でchomp!は破壊的。返る値は改行の取り除かれた文字列。 取り除かれる文字はASCIIコードで表現すれば[10, 13]のみ。

1
2
line.chomp
line.chomp!

取り除かれる文字はASCIIコードで表現すると[0, 9, 10, 11, 13, 32]。

1
rtrim(line)

JavaScriptには専用のメソッドはなさそうだったので正規表現で置換。取り除かれる文字はASCIIコードで表現すると[9, 10, 11, 12, 13, 32]。

1
line.replace(/\s+$/, "")
Squeak Smalltalk では、文字列に withoutTrailingBlanks を送信。非破壊的。

取り除かれる文字は isSeparator という問い合わせに対して true を返してくる文字。具体的には ASCII コードで表現すると #(32 13 9 10 12) 。
1
line withoutTrailingBlanks

Javaでは両側の空白文字を削除する String#trim() しかないので、正規表現で実装。 取り除かれる文字は『空白文字: [ tnx0Bfr]』になります。

1
line.replaceAll("\\s+$", "");

『空白文字: [ tnx0Bfr]』の中のバックスラッシュが消えてしまったので、ASCIIコード表記に修正 ASCIIコードの10進表記で 『32, 9, 10, 11, 12, 13』 になります。

Perlがなかったので投稿。 Rubyと同じで 末尾の改行文字を取り除く関数があります。取り除かれる文字は [10, 13] のみ。

1
chomp($line);

Cがなかったので。 正規表現でやると、こんな感じでしょうか。。。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include <string.h>
#include <regex.h>

void rstrip( char* dest, const char* src ) {
  regex_t preg;
  regmatch_t pmatch;
  
  regcomp(&preg, "\\ +$", REG_EXTENDED|REG_NEWLINE);
  regexec(&preg, src, 1, &pmatch, 0);
  regfree(&preg);
  
  strncpy( dest, src, pmatch.rm_so );
}
マイナス評価を付けても反応が無かったので、ちゃんと指摘します。
次のような場合|123  |と出力されず、|123|と出力されます。文字列が空白だけの時は...((((;゜Д゜)))
 char s[] = "123       ";
 rtrim(s + 5);
 printf("|%s|\n", s);

僕の勘違いでなければ問題の書き方にも問題があるように思いますね。 トピック全体にもいえることですけど お題を簡素に書きすぎてやるべきことがわかりにくい気がします。

その他の投稿でもchop系とtrim系が混ざっているので 判断つきにくいのではないでしょうか。

このお題は、末尾の空白文字を1文字削除する出よいのですか? それとも連続する空白をすべて削除する?

chop系とtrim系が混ざってる問題は確かにあるんですが、#4192はそれ以前の問題であることをyoheiさんは言っているのでは。(文字列の開始点をチェックしてないので渡されたデータより前にあるメモリにアクセスし、最悪破壊する可能性がある)。

boost を使えば、boost::algorithm に trim が用意されています。trim_right_copy が非破壊で、trim_right が破壊的です。削除される文字は isspace が true になるものと同じです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#include <iostream>
#include "boost/algorithm/string/trim.hpp"

using namespace std;
using namespace boost::algorithm;

int main ( int argc, char *argv[] ){
   string str1( argv[1] );
   string str2 = trim_right_copy( str1 );
   cout << str1 << "#" << endl;
   cout << str2 << "#" << endl;
   trim_right( str1 );
   cout << str1 << "#" << endl;
}
Common Lisp には string-right-trim という関数があります。非破壊的です。
取り除く文字は第一引数に文字列、リスト、ベクタのいずれかで指定します。
1
(string-right-trim '(#\Space #\Tab #\Newline) line)
一番わかりやすいのは,
・文字列を反転
・前から文字を辿って空白である間それを落す
・もう一度反転する
これが,trim0 なのですが,この場合与えられた文字列を2回たどることになります.

与えられた文字列をどるのを1回にするには foldr を使って畳み込むという手があります.
これが trim1です.

Haskellですから当然文字列を破壊することはできません.

空白文字を判定するのは Data.Char モジュールの の isSpace です
isSpace で空白と判定される文字コードはghciのプロンプトで以下のようにすると列挙できます。

% ghci -v0
Prelude> :m + Data.Char Text.Printf
Prelude Text.Printf Data.Char> mapM_ (printf "0x%02x\n") $ map ord $ filter isSpace [minBound .. maxBound]
0x09
0x0a
0x0b
0x0c
0x0d
0x20
0xa0
0x1680
0x180e
0x2000
0x2001
0x2002
0x2003
0x2004
0x2005
0x2006
0x2007
0x2008
0x2009
0x200a
0x200b
0x202f
0x205f
0x3000
1
2
3
4
5
import Data.Char

trim0 = reverse . dropWhile isSpace . reverse

trim1 = foldr (\ c cs -> if null cs && isSpace c then "" else c:cs) ""
毎回 null cs をチェックするのが癪なのでチェックしないバージョン。
末尾が空白でなければコピーでなく与えられた文字列そのものを返します。

実質は状態遷移してるだけです。
読みづらい。もしかしたら実行効率も落ちてるかも。
でもそんなの関………
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import Char (isSpace)

data T a = T [a] (a -> [a] -> T a)

trim2 xs = str  where
  T str _ = foldr (\c (T cs f) -> f c cs) (T xs lastCheck) xs
  lastCheck  c cs = if (isSpace c) then (T [] trailCheck) else (T cs  pass)
  trailCheck c _  = if (isSpace c) then (T [] trailCheck) else (T [c] cons)
  pass _ cs = T cs pass
  cons c cs = T (c:cs) cons
効率なんか関係ねえといいながらもやはり気になって 100000バイトの文字列で
計測してみました。

全部空白文字のとき trim1 と trim2 はほぼ同時。(trim2 がちょい早く終わる)
末尾の空白が少なくなるにつれて trim2 は段々早く終わるようになるが trim1 は変わらず。

trim2 の方が効率よい、よしよしと喜んで、さて問題外の trim0 もついでに
計測してやるかとやってみたところ常に trim0 が時間もメモリも圧倒的に
効率よい・・・ orz
reverse も dropWhile も組み込みだから(本当?)なんでしょうか。
凝ったことせずに組み込み関数組み合わせてシンプルなコード書きましょうという教訓でした。

ちなみに trim の結果文字列が完全に評価されたことを確認するのには
f xs = foldl' (\b c -> b + ord c) 0 xs
を使いました。
C# では String.TrimEnd メソッドがあります。非破壊的。 取り除かれる文字を引数に指定すると、その文字を取り除きます。 null を指定すると、Unicode の空白文字 = {0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x20, 0xA0, 0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006, 0x2007, 0x2008, 0x2009, 0x200A, 0x200B, 0x3000, 0xFEFF} の21個が消えます。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
using System;
static class Program {
    static void Main() {
        string text = "abcdefg\n   \n   \n\t\t\t";
        Console.WriteLine("[{0}]", text.TrimEnd(null));
            // [abcdefg]
        Console.WriteLine("[{0}]", text.TrimEnd('\n', '\r', ' ', ' ', '\t', 'g', 'f'));
            // [abcde]
    }
}

Schemeではsrfi-13のstring-trim-rightが使えます。 取り除く文字種はデフォルトではlexerによって空白と見なされる文字(Gauche 0.8.12ではspace, tab, linefeed, vertical tab, form feed, return. いずれUnicode全般の空白文字も含む予定)。 3番目の例のように、オプショナル引数で文字種の指定もできます。

1
2
3
4
5
6
7
8
gosh> (use srfi-13)
#<undef>
gosh> (string-trim-right "abcd    ")
"abcd"
gosh> (string-trim-right "abcd    \n")
"abcd"
gosh> (string-trim-right "aBcDeFgh   \n" #[a-z\s])
"aBcDeF"

見当たらなかったので自作しました。両方非破壊です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
let rtrim ?(ch=" \t\n\011\012\013") str =
  let rec loop i =
    if i>=0 && String.contains ch str.[i]
    then loop (i-1)
    else String.sub str 0 (i+1)
  in loop (String.length str - 1);;
    
(*正規表現版*)
#load "str.cma";;
let rtrim2 str =
  let cond = Str.regexp "[ \009-\013]+$" in
  Str.global_replace cond "" str;;

手頃なオペレータがなかったので自作。取り除く文字は5行目で定義しています。 返り値はもとの文字列の部分文字列(参照)です。 元の文字列実体が保持される、という意味では非破壊です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
%!PS

/RTrim { % (String   ) RTrim (String)
    dup length 1 sub
    <<9 true 10 true 11 true 12 true 13 true 32 true>>
    3 1 roll
    -1 0 {
        2 copy get
        3 index exch known {
            0 exch getinterval
        } {pop exit} ifelse
    } for
    exch pop
} bind def

(|) print
(ABCD   ) RTrim print
(|) =

Index

Feed

Other

Link

Pathtraq

loading...