challenge 文字列のセンタリング

文字列を指定のカラム幅にセンタリング配置する関数を示してください。文字列の長さが指定した幅より長い場合には文字列の両端をできるだけ均等に切り落して指定幅に収めてください。1文字は1カラムに収まるものと仮定してかまいません。

Posted feedbacks - Flatten

Nested Hidden

一番乗りかな?きわめてけれんみのない実装。

Dan the Perl Monger

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#!/usr/local/bin/perl
use strict;
use warnings;

sub center{
    my $str = shift;
    my $width = shift || 80;
    my $margin = int(($width - length($str))/2);
    return $str if $margin == 0;
    return " " x int($margin) . $str if $margin > 0;
    substr($str, 0, -$margin, '');
    return substr($str, 0, $width);
}

chomp and print center($_, 72), "\n" for(<>)
__END__
0         1         2         3         4         5         6         7
012345678901234567890123456789012345678901234567890123456789012345678901
This line is intentionally longer than 72 chars to test center() works fine.

これまたけれんみのない、#4120と同様の実装。rubyらしくStringを拡張して格調高く。

Dan the Occasional Rubyist

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class String
  def centered(width)
    width ||= 80
    margin = (width - self.length)/2;
    return self if margin == 0
    return " " * margin + self if margin > 0
    self[-margin..margin-1]
  end
end

ARGF.readlines.each{|l| puts l.centered(72) }
__END__
0         1         2         3         4         5         6         7
012345678901234567890123456789012345678901234567890123456789012345678901
This line is intentionally longer than 72 chars to test String#centered works fine.

substr()二度もやるのは無駄じゃん。

Dan the Lazier

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
--- center.pl.orig      2007-11-17 04:35:53.000000000 +0900
+++ center.pl   2007-11-17 05:17:39.000000000 +0900
@@ -8,8 +8,7 @@
     my $margin = int(($width - length($str))/2);
     return $str if $margin == 0;
     return " " x int($margin) . $str if $margin > 0;
-    substr($str, 0, -$margin, '');
-    return substr($str, 0, $width);
+    return substr($str, -$margin, $width);
 }

Pythonは組み込みでstr#center()を持ってたりするので一番楽かも。

Dan the Occasional Pythonista

P.S. perl の while(<>)、RubyのARGV.readlinesの代わりってpythonでどう書くのかにゃ?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#!/usr/bin/python

def center_and_crop(str, width = 80):
    margin = (width - len(str))/2
    if margin >= 0: return str.center(width)
    else:           return str[-margin:width-margin]

if __name__ == '__main__':
  import sys
  for line in sys.stdin:
    print center_and_crop(line[:-1], 72)

#         1         2         3         4         5         6         7
#12345678901234567890123456789012345678901234567890123456789012345678901
#This line is intentionally longer than 72 chars to test center_and_crop works \
fine.

初投稿です。 アカウントを作ってログインしようとしたら This account is inactive. と言われてしまいました。 何でだろう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
(use srfi-13)

(define (string-pad-both string width)
  (let* ((slen (string-length string))
         (left (string-take string (quotient slen 2)))
         (right (string-drop string (quotient slen 2)))
         (llen (quotient width 2))
         (rlen (- width llen)))
    (string-append
     (string-pad left llen)
     (string-pad-right right rlen))))

(define (center string . args)
  (string-pad-both string (get-optional args 80)))

ごめんなさい。ログインできました。 メールが届いてるのに気づきませんでした。 英語のメッセージを真面目に読んでいなかったせい?


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import std.stdio;
import std.string;
import std.utf;

string cropCenter(string str, uint width){
    auto wstr = toUTF16(str);
    int leftMargin = cast(int)(width - wstr.length) / 2;
    int rightMargin = cast(int)(width - wstr.length) - leftMargin;
    return (rightMargin < 0) ?
           toUTF8(wstr[-leftMargin..($ + rightMargin)]) :
           (repeat(" ", leftMargin) ~ str ~ repeat(" ", rightMargin));
}

void main(){
    writefln(cropCenter("ほげら", 2));  //=> "ほげ"
    writefln(cropCenter("ほげら", 1));  //=> "げ"
    writefln(cropCenter("ほげ",   2));  //=> "ほげ"
    writefln(cropCenter("ほげ",   1));  //=> "ほ"
    writefln(cropCenter("ほげら", 5));  //=> " ほげら "
    writefln(cropCenter("ほげら", 4));  //=> "ほげら "
    writefln(cropCenter("ほげ",   5));  //=> " ほげ  "
    writefln(cropCenter("ほげ",   4));  //=> " ほげ "
}

C# だと PadReft っていう指定文字数になるまで空白文字を埋め込むメソッドがあります。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
using System;
static class Program {
    static void Main() {
        string[] texts = {
            "0123456789",
            "abc",
            "abcdefg",
            "abcdefghijklmn",
        };
        foreach(string text in texts) {
            Console.WriteLine(Centering(text, 10));
        }
    }
    static string Centering(string text, int width) {
        int margin = (int)((width - text.Length) / 2);
        if (margin == 0)
            return text;
        if (0 < margin)
            return text.PadLeft(margin + text.Length);
        return text.Substring(-margin, width);
    }
}


	
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
def centering(lst,n)
  return lst if (n <= 0 || lst == "")
  c = (lst = " "*n + lst + " "*n).size/2
  lst[c-n/2..-1][0..n-1]
end

if __FILE__ == $0
  p (s = centering("end",4))  != " end" ? s : :ok
  p (s = centering("end",3))  != "end"  ? s : :ok
  p (s = centering("end",2))  != "en"   ? s : :ok
  p (s = centering("end",1))  != "n"    ? s : :ok
  p (s = centering("end",0))  != "end"  ? s : :ok
  p (s = centering("end",-1)) != "end"  ? s : :ok
  p (s = centering("",0))     != ""     ? s : :ok
  p (s = centering("",1))     != ""     ? s : :ok
end

CL の format で。もっときれいに書けそうですが。
1
2
3
4
5
(defun center (str &optional (width 80))
  (let ((margin (- (length str) width)))
    (if (> margin 0)
        (format t "~a" (subseq str (floor (/ margin 2)) (+ width (floor (/ margin 2)))))
      (format t (format nil "~~~a:@<~~a~~>" width) str))))

Squeak Smalltalk で。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
| centeredOf |
centeredOf := [:line :width |
    | size offset centered start end delta |
    size := line size.
    delta := (width - size + 1) // 2.
    offset := (delta min: 0) negated + 1.
    start := (delta max: 0) + 1.
    end := delta + size min: width.
    centered := String new: width withAll: Character space.
    centered replaceFrom: start to: end with: line startingAt: offset].

centeredOf value: '123456789' value: 15.  "=> '   123456789   ' "
centeredOf value: '123456789' value: 5.   "=> '34567' "

特にひねりはありません。さらに簡単にできそうな気がします。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char *center( char *str, int width, char *out ){
   int len,margin,i=0;
   char *p = out;
   if( !out ){
      return NULL;
   }
   len = strlen(str);
   margin = (abs(width-len)+1)/2;
   if( margin == 0 || len > width ){
      strncpy( p, &str[margin], width );
   }
   else{
      while( i++ < margin ) *p++ = ' ';
      strcpy( p, str );
      p += len;
      while( i++ < width - len + 1 ) *p++ = ' ';
   }
   return out;
}

int main ( int argc, char *argv[] ){
   int n;
   char *out;
   if( argc < 3 ){
      fprintf(stderr, "usage: %s str num\n", argv[0]);
      return EXIT_FAILURE;
   }

   n = atoi( argv[2] );
   if( n <= 0 || (out = malloc( sizeof(char)*n )) == NULL ){
      return EXIT_FAILURE;
   }
   printf("%s#\n", center( argv[1], n, out ) );
   free(out);
   return EXIT_SUCCESS;
}

PC-E500のベーシックで書いたなこんなの。
今は、schemeで。
実行結果
------
$ gosh 87.scm
1234567890
    abc
  abcdef
  abcdefg
bcdefghijk
-------
奇数文字の時は、右に寄ります。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
(define (centering s w)
  (let* ((p (make-string (quotient w 2) #\ ))
         (ss (string-append p s p))
         (sw (string-length ss))
         (mg (quotient (- sw w) 2)))
    (substring ss mg (- sw mg))))

(print "1234567890")
(print (centering "abc" 10))
(print (centering "abcdef" 10))
(print (centering "abcdefg" 10))
(print (centering "abcdefghijkl" 10))

if文もcenterメソッドも使わずに。

1
2
3
4
5
def center(s, n):
  return (' ' * n + a + ' ' * n)[(len(a)+n)/2:][:n]

print center('abc', 9)
print center('abc', 1)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
function center(s, w) {
    var r=[], d=w-s.length;
    if (d < 0) { s=s.substring(d/2*-1, s.length+d/2); }
    for (var i=0; i<=d/2-1; i++) { r.push(" "); }
    r.push(s);
    for (var i=0; i<=d/2-1+d%2; i++) { r.push(" "); }
    return r.join("");
}
console.log("[" + center("test", 0) + "]");    // => []
console.log("[" + center("test", 5) + "]");    // => [test ]
console.log("[" + center("test", 6) + "]");    // => [ test ]
console.log("[" + center("test", 10) + "]");    // => [   test   ]
console.log("[" + center("testtest", 10) + "]");    // => [ testtest ]

> for(s in c("1234567890", "abc", "abcdef", "abcdefg", "abcdefghijkl")){
+     print(center(s, 10))
+ }
[1] "1234567890"
[1] "   abc    "
[1] "  abcdef  "
[1] " abcdefg  "
[1] "bcdefghijk"
1
2
3
center <- function(s, n){
    format(substring(s, (i<-(nchar(s)-n)/2+1), i+n-1), width=n, justify="centre")
}

Javaがなかったので初投稿です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
public class Answer87 {
    public static String formatCenter(String str, int length) {
        if (length <= 0) return str;
        StringBuilder builder = new StringBuilder(str);
        for (int index = 0; index < length; index++) {
            builder.insert(0, ' ');
            builder.append(' ');
        }
        int start = (builder.length() - length) / 2;
        return builder.substring(start, start + length);
    }

    public static void main(String[] args) {
        System.out.println(formatCenter("abcde", 5));    // "abcde"
        System.out.println(formatCenter("abcde", 7));    // " abcde "
        System.out.println(formatCenter("abcde", 8));    // "  abcde "
        System.out.println(formatCenter("abcde", 1));    // "c"
        System.out.println(formatCenter("abcde", 2));    // "bc"
        System.out.println(formatCenter("abcde", 0));    // "abcde"
    }
}

最後の行みたいなときは V を使うと短くなりますよ
(format t "~V:@<~A~>" width str)

sayはprint lnだと思ってください。 仕様と有ってるのか・・・どうか

1
2
3
4
5
6
7
8
var src = '123456789'
say( '[' + centering(src,100)  + ']' );
say( '[' + centering(src,5)    + ']' );
function centering( s , w ){
  if( s.length < w ) return space( ( w / 2 - s.length / 2 ) ) + s + space( ( w / 2 - s.length / 2 ) );
  else               return s.substring( ( s.length - w ) / 2, s.length - ( ( s.length - w ) / 2 ) );
  function space (l){ var r = ''; while( r.length < l ) r += ' '; return r; }
}

String#centerを使って少し修正しました.
また,入力行がちょうどwidth文字 + \nのときに,頭の一文字と\nを切り落としてしまっていたので,centeredに渡す前にchompするようにしました.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class String
  def centered(width = 80)
    margin = (width - self.length)/2;
    return center(width) if margin >= 0
    self[-margin..margin-1]
  end
end

ARGF.readlines.each{|l| puts l.chomp.centered(72) }
__END__
0         1         2         3         4         5         6         7
012345678901234567890123456789012345678901234567890123456789012345678901
This line is intentionally longer than 72 chars to test String#centered works fine.

C++ では boost::format でセンタリングができるようです。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <cstdlib>
#include <iostream>
#include <sstream>
#include "boost/format.hpp"

using namespace std;
using namespace boost;

string &center( string &str, int width ){
   int len = str.length();
   if( len > width ){
      str = str.substr( (len-width+1)/2, width );
   }
   ostringstream oss;
   oss << "%1$=" << width << "s";
   str = ( format(oss.str()) % str ).str();
   return str;
}

int main ( int argc, char *argv[] ){
   int width( atoi(argv[2]) );
   string str( argv[1] );
   cout << center( str, width ) << endl;
   return EXIT_SUCCESS;
}

1
2
3
4
5
6
7
8
9
center :: Int -> [Char] -> [Char]
center n str | slen < n  = center'
             | slen > n  = clop
             | otherwise = str
  where
    slen = length str
    center' = lmargin ++ str ++ rmargin
    (lmargin, rmargin) = splitAt ((n-slen) `div` 2) $ replicate (n-slen) ' '
    clop = take n $ drop ((slen-n) `div` 2) str

kozima さん、ご指摘感謝です。
format での~v ってよく調べたら基本的機能なのですね。参考になりました。
1
2
3
4
5
6
(defun center (str &optional (width 80))
  (let ((margin (- (length str) width)))
    (format t "~v:@<~a~>" width 
        (if (> margin 0)
        (subseq str (floor (/ margin 2)) (+ width (floor (/ margin 2))))
          str))))

ビット演算(必ず整数が返る)で曖昧さを除く。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
String.prototype.center = function(width){ var d, s;
  return (d = (width |= 0) - this.length) > 0
    ? (s = Array((d >> 1) + 1).join(' ')) + this + (d & 1 ? s + ' ' : s)
    : this.substr(-d >> 1, width);
};

(typeof alert != 'undefined' ? alert :
 typeof print != 'undefined' ? print :
 function($){ typeof WSH == 'object' && WSH.echo($) })((function(s, f, t){
   for(var r = []; f <= t;) r.push(s.center(f++));
   return '['+ r.join(']\n[') +']' })('hoge', -1, 8));

文字列として取得して整形。
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
    begingroupundo;
    if ( getconfig( "AutoAdjustOrikaeshi" ) == 2 ) {
        #width = windowwidth;
    } else {
        #width = getconfig( "Orikaeshi" );
    }
    if ( !selecting ) {
        selectall;
    } else {
        #l1 = seltoplineno;
        #l2 = selendlineno;
        movetolineno 1, #l1;
        beginsel;
        movetolineno 1, #l2;
        movetolineno linelen2 + 1, #l2;
        endsel;
    }
    $text = gettext( seltopx, seltopy, selendx, selendy, 1 );
    delete;
    call center $text, #width;
    insert $$return;
    endgroupundo;
    endmacro;

center:
    $$result = "";
    while( $$1 != "" ) {
        ##lineLen = strstr( $$1, "\x0d\x0a" );
        ##isLastLine = ##lineLen == -1;
        if ( ##isLastLine ) {
            ##lineLen = strlen( $$1 );
        }
        $$line = leftstr( $$1, ##lineLen );
        call center_line $$line, ##2;
        $$result = $$result + $$return;
        $$1 = rightstr( $$1, strlen( $$1 ) - ##lineLen );
        if ( !##isLastLine ) {
            $$1 = rightstr( $$1, strlen( $$1 ) - strlen( "\x0d\x0a" ) );
            $$result = $$result + "\n";
        }
    }
    return $$result;

center_line:
    ##margin = ##2 - strlen( $$1 );
    if ( ##margin >= 0 ) {
        $$result = "";
        ##i = ##margin / 2;
        while( ##i > 0 ) {
            $$result = $$result + " ";
            ##i = ##i - 1;
        }
        $$result = $$result + $$1;
    } else {
        ##idx = -##margin / 2;
        call midstr2, $$1, ##idx, ##2;
        $$result = $$return;
        // ±1バイトとる範囲をずらして、その中から一番長いテキストをとる
        if ( strlen( $$result ) < ##2 ) {
            call midstr2, $$1, ##idx + 1, ##2;
            if ( strlen( $$return ) > strlen( $$result ) ) {
                $$result = $$return;
            }
            if ( strlen( $$result ) < ##2 ) {
                call midstr2, $$1, ##idx - 1, ##2;
                if ( strlen( $$return ) > strlen( $$result ) ) {
                    $$result = $$return;
                }
            }
        }
    }
    return $$result;

midstr2: // マルチバイトを考慮した midstr
    $$src = $$1;
    ##idx = ##2;
    ##len = ##3;
    if ( ##idx < 0 ) {
        ##idx = 0;
    }
    if ( ##idx > strlen( $$src ) ) {
        ##idx = strlen( $$src );
    }
    if ( ##len < 0 ) {
        ##len = 0;
    }
    if ( ##len > strlen( $$src ) - ##idx ) {
        ##len = strlen( $$src ) - ##idx ;
    }
    
    ##end = ##idx + ##len;
    ##i = 0;
    $$dest = "";
    while( ##i < ##end ) {
        ##char = ascii( rightstr( $$src, strlen( $$src ) - ##i ) );
        ##byte = strlen( char( ##char ) );
        if ( ( ##i >= ##idx ) && ( ##i + ##byte <= ##end ) ) {
            $$dest = $$dest + char( ##char );
        }
        ##i = ##i + ##byte;
    }
    return $$dest;

Emacs Lisp
実行結果:
(string-centering "abc" 0)
""
(string-centering "abc" 1)
"b"
(string-centering "abc" 2)
"ab"
(string-centering "abc" 3)
"abc"
(string-centering "abc" 4)
"abc "
(string-centering "abc" 5)
" abc "
(string-centering "abc" 6)
" abc  "
(string-centering "abc" 7)
"  abc  "
1
2
3
4
5
6
7
8
(defun string-centering (s c)
  (let ((p (lambda (k) (make-string k ? )))
        (d (- c (length s))))
    (let ((n (/ d 2))
          (m (% d 2)))
      (if (>= d 0)
          (concat (funcall p n) s (funcall p (+ n m)))
        (substring s (abs n) (- (length s) (abs (+ n m))))))))

バグの報告です。
ヌル文字が足りず、変なオマケが後に付きます。
mallocの実装によって気づかないことがあるかもしれません。
私の場合は#が出力されました。丁寧に範囲外の1byteめに'#'をセットしてくれているようです。

#4160は、はじめにgaucheで書いてみて、そのままEmacs Lispに直したものです。gaucheで書いたのも投稿しちゃいます。
1
2
3
4
5
6
7
(define (string-centering s c)
  (define (p k) (make-string k #\ ))
  (let ((d (- c (string-length s))))
    (receive (n m) (quotient&remainder d 2)
      (if (>= d 0)
          (string-append (p n) s (p (+ n m)))
          (substring s (abs n) (- (string-length s) (abs (+ n m))))))))

 まず指定されたカラム幅分をスペースで埋め、さらにヌル文字をつけて作業エリアを初期化します。
 次に幅から文字列長を引いて2で割り、初めの文字のオフセットを求めます。このとき幅より大きな文字列が渡されるとマイナスになりますが、そのままにしておきます。
 最後にオフセットの位置から順番にコピーしますが、作業エリア範囲外の場合は無視します。すると文字列長に関係なく欲しい結果が得られます。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <stdio.h>
#include <string.h>

#define SPACE ' '
//#define SPACE '*'

char *centering(char *out, const char *str, int width){
    int i, len, offset;
    len = strlen(str);
    memset(out, SPACE, width);
    out[width] = '\0';
    offset = (width - len) / 2;
    for(i=0; i<len; i++){
        int ind = offset + i;
        if(ind >= 0 && ind <width)
            out[ind] = str[i];
    }
    return out;
}

int main(){
    char buf[256];
    int i;
    for(i=1; i<20; i++)
        puts(centering(buf, "abcde", i));
    return 0;
}

空白埋めしてから、部分コピー

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
#include <string.h>

// dest のサイズは w + 1 以上とする事
char *centering(char *dest, const char *s, size_t w) {
    size_t len;

    len = strlen(s);
    sprintf(dest, "%*s", w, "");
    
    if (len >= w) strncpy(dest, s + (len - w) / 2, w);
    else          strncpy(dest + (w - len) / 2, s, len);

    return dest;
}

int main(void) {
    char buff[256];
    printf("|%s|\n", centering(buff, "123456789", 15));
    printf("|%s|\n", centering(buff, "123456789",  5));
}

Stringを拡張。 削るときは後ろを優先。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class String
    def neko_center (w)
        if (d = self.size - w) > 0
            #self.gsub(/\A.{#{d/2}}|.{#{(d+1)/2}}\Z/, '')
            self[d/2..-d/2-1]
        else
            self.center(w)
        end
    end
end

(width.odd? xor self.odd?) のときに、余計に1文字削ってしまうと思います。

1
p "0123456789".centered(3)  => "45"

#4164を見てたら、sprintfを1回呼ぶだけでも出来そうだと思ったので、やってみました。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include <string.h>

char *centering(char *dest, char *s, int w) {
  int d = w - strlen(s);
  
  sprintf(dest, "%*s%.*s%*s", ( d >0 ? d/2 : 0 ), "", ( d >0 ? strlen(s) : w ), s + ( d >0 ? 0 : -d/2 ), ( d >0 ? d/2 : 0 ), "" );
  
  return dest;
}

int main(void) {
  char buff[256];

  printf("|%s|\n", centering(buff, "123456789", 20));
  printf("|%s|\n", centering(buff, "123456789", 5));
}

投稿者を修正しておきました。


1
2
3
4
5
6
7
let center str width =
  let len = String.length str in
  if width>len then
    let res = String.make width ' ' in
    (String.blit str 0 res ((width-len)/2) len; res)
  else
    String.sub str ((len-width)/2) width ;;

短くて素敵ー。 sがaになってますよ。


あ、ほんとだ。ご指摘感謝です。


"%.*s" で幅制限出来たのか... ただ、d > 0 で d が奇数のときに、1桁足りないようです。 それを踏まえて、自分ならこう書きます。

1
2
3
4
5
6
7
8
char *centering(char *dest, char *s, int w) {
  int d = w - strlen(s);
  
  if (d > 0) sprintf(dest, "%*s%*s", w - d/2, s, d/2, "");
  else       sprintf(dest, "%.*s", w, s - d/2);
  
  return dest;
}

あ、確かに。 ご指摘ありがとうございます。 sprintfの所を下記のように修正します。 ですが、youheiさんのような書き方の方が、自分も好みです。
1
2
3
4
  sprintf(dest, "%*s%.*s%*s", 
      ( d>0 ? d - d/2 : 0 ), "", 
      ( d >0 ? strlen(s) : w ), s + ( d >0 ? 0 : -d/2 ), 
      ( d >0 ? d/2 : 0 ), "" );

さらに修正しました. #4173 で,ご指摘を受けた不具合も直っています.

p "0123456789".centered(3)  # "345"
p "012".centered(5)  # " 012 "
p "012".centered(4)  # "012 "
p "012".centered(3)  # "012"
p "012".centered(2)  # "01"
p "012".centered(1)  # "1"
p "012".centered(0)  # ""
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class String
  def centered(width = 80)
    return center(width) if width > size
    self[(size - width) / 2, width]
  end
end

ARGF.readlines.each{|l| puts l.chomp.centered(72) }
__END__
0         1         2         3         4         5         6         7
012345678901234567890123456789012345678901234567890123456789012345678901
This line is intentionally longer than 72 chars to test String#centered works fine.

Haskellらしく無限リストと高階関数で。

1. 文字列の右に無限個の空白を連結
2. 左側を揃える
3. 幅の分だけ切り取る
1
2
3
4
5
center width str = take width . adjust lmargin $ str ++ repeat ' '
    where 
    lmargin = (width - length str) `div` 2
    adjust n | n > 0      = (replicate n ' ' ++)
             | otherwise  = drop (-n)

String#centerは右側からパディングを入れるようですので,それにあわせて,widthをはみ出す場合は「左側から削る」ようにしました.

Rubyでは,(CやPerlと異なり)負の整数の除算の場合,剰余の符号が除数(divisor)の符号と一致するような商が得られますが,4行目で文字列を削る際にその性質を利用しています. (4171 を見てぱくりました.勉強になります:-)

p "0123456789".centered(3)  # "456"
p "012".centered(5)   # " 012 "
p "012".centered(4)   # "012 "
p "012".centered(3)   # "012"
p "012".centered(2)   # "12"
p "012".centered(1)   # "1"
p "012".centered(0)   # ""
p "012".centered(-1)  # "012"
p "012".center(4) == " 012 ".centered(4)  # true
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
--- center.rb.orig      2007-11-19 17:17:47.000000000 +0900
+++ center.rb   2007-11-19 17:17:39.000000000 +0900
@@ -1,7 +1,7 @@
 class String
   def centered(width = 80)
-    return center(width) if width > size
-    self[(size - width) / 2, width]
+    return center(width) if width > size or 0 > width
+    self[-((width - size) / 2), width]
   end
 end

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
    begingroupundo;
    if ( getconfig( "AutoAdjustOrikaeshi" ) == 2 ) {
        #width = windowwidth;
    } else {
        #width = getconfig( "Orikaeshi" );
    }
    if ( !selecting ) {
        #top = 1;
        #end = linecount2;
    } else {
        #top = seltoplineno;
        #end = selendlineno;
        escape;
    }
    #i = #top;
    while( #i <= #end ) {
        movetolineno 1, #i;
        #margin = #width - linelen2;
        if ( #margin >= 0 ) {
            #j = #margin / 2;
            $margin = "";
            while( #j > 0 ) {
                $margin = $margin + " ";
                #j = #j - 1;
            }
            insert $margin;
        } else {
            beginsel;
            movetolineno linelen2 + 1, #i;
            $text = gettext( seltopx, seltopy, selendx, selendy, 1 );
            delete;
            call cutstr, $text, #width;
            insert $$return;
        }
        #i = #i + 1;
    }
    endgroupundo;
    endmacro;

cutstr:
    $$src = $$1;
    ##width = ##2;
    if ( ##width < 0 ) {
        ##width = 0;
    }
    if ( ##width > strlen( $$src ) ) {
        ##width = strlen( $$src );
    }
    ##start = strlen( $$src ) - ##width;
    ##end = strlen( $$src ) + ##width;
    ##i = 0;
    $$dest = "";
    $$left = "";
    $$right = "";
    while( ##i * 2 < ##end ) {
        ##char = ascii( $$src );
        ##bytes = strlen( char( ##char ) );
        if ( ##i * 2 < ##start ) {
            $$left = char( ##char );
        }
        if ( ( ( ##i + ##bytes ) * 2 > ##end ) && ( $$right == "" ) ) {
            $$right = char( ##char );
        }
        if ( ( ##i * 2 >= ##start ) && ( ( ##i + ##bytes ) * 2 <= ##end ) ) {
            $$dest = $$dest + char( ##char );
        }
        $$src = rightstr( $$src, strlen( $$src ) - ##bytes );
        ##i = ##i + ##bytes;
    }
    if ( $$right == "" ) {
        $$right = char( ascii( $$src ) );
    }
    if ( strlen( $$dest ) >= ##width ) {
        return $$dest;
    }
    if ( ( strlen( $$left + $$dest ) <= ##width ) && ( $$left != "" ) ) {
        return $$left + $$dest;
    }
    if ( ( strlen( $$dest + $$right ) <= ##width ) && ( $$right != "" ) ) {
        return $$dest + $$right;
    }
    return $$dest;

覚えたてのControl.Monad.Fixを使って。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import Control.Monad.Fix

center :: Int -> String -> String
center n = fix (\f x -> g f x)
  where g f x | length x1 == n = x1
              | length x2 == n = x2
              | length x2 > n = f x2
              | otherwise = f $ head $ zipWith3 (\x y z -> x ++ y ++ z) [" "] [x] [" "]
          where x1 = tail x
                x2 = init x1

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
    if ( getconfig( "AutoAdjustOrikaeshi" ) == 2 ) {
        #width = windowwidth;
    } else {
        #width = getconfig( "Orikaeshi" );
    }
    disabledraw;
    begingroupundo;
    if ( !selecting ) {
        selectall;
    }
    #i = seltoplineno;
    #end = selendlineno;
    escape;
    while( #i <= #end ) {
        movetolineno 1, #i;
        #margin = #width - linelen2;
        if ( #margin >= 0 ) {
            #j = #margin / 2;
            while( #j > 0 ) {
                insert " ";
                #j = #j - 1;
            }
        } else {
            while( linelen2 > #width ) {
                golineend2;
                backspace;
                if ( !linelen2 > #width ) {
                    break;
                }
                golinetop2
                delete;
            }
        }
        #i = #i + 1;
    }
    endgroupundo;
    enabledraw;

明示的算術演算なし、lengthで文字列の長さも測っていない、「そこまでしたからってどうよ」版 ^^;
あ。それから、このセンタリングでは空白は前より入り、切り落としは後ろよりになります。

center 2 "a" → "_a"
center 3 "a" → "_a_"
center 3 "ab" → "_ab"
center 4 "ab" → "_ab_"
center 3 "abcde" → "bcd"
center 4 "abcde" → "bcde"
center 3 "abcdef" → "bcd"
center 4 "abcdef" → "bcde"
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
center n s = uncurry (c n []) $ halve s
  where c 0 a p q = take n (a ++ q)
        c i a p q = d (pred i) (head p:a) (tail p) q
        d 0 a p q = take n (a ++ q)
        d i a p q = c (pred i) a p q

halve s = e (repeat ' ') (s++repeat ' ') s
  where e a b [] = (a,b)
        e a b c  = o (head b:a) (tail b) (tail c)
        o a b [] = (a,b)
        o a b c  = e a b (tail c)

1
2
3
4
def center(s:String, n:int) = {
  val pad = List.make(n," ").mkString("")
  (pad + s + pad).toList.drop((s.size+n)/2).take(n).mkString("")
}

Io で普通に書いてみました。alignCenter があるのできりつめるところだけです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
Sequence centered := method(width,
    margin := (width - self size) / 2
    if (margin < 0,
        self slice(-margin, self size + margin)
    ,
        self alignCenter(width)
    )
)


# なんか手元のだと定義されていないので
Number to := method(i,
    Range clone setRange(self, i)
)

0 to(7) foreach(i,
    "proto" asMutable centered(i) justSerialized
    "" println
)

/* output

""
"o"
"ro"
"rot"
"prot"
"proto"
"proto "
" proto "

*/

なでしこで素直に

1
2
3
4
5
6
7
8
9
"abcdefg"を3でセンタリングして表示
●センタリング(SをNで)
    もし(文字数(S)<=N)ならば
        文字列センタリング(S,N)で戻る
    1の間
        Sから1文字右端削除
        もし(文字数(S)<=N)ならば,Sで戻る
        Sの1から1文字削除
        もし(文字数(S)<=N)ならば,Sで戻る

気づくのが遅れてしまいましたが、修正ありがとうございました。


馬鹿正直に作ってみました。 あらかじめ条件を洗い出して、最後に処理をまとめて行う、というのが個人的なスタイルです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include<stdio.h>
#include<string.h>

void centering( char *buffer, int col_num, char* string );

int main( int argc, char *argv[] )
{
    char buf[256];
    
    memset( buf, 0, sizeof(buf) );
    
    centering( buf, 10, "abcdefg" );
    printf( "%s\n", buf );
    
    centering( buf, 8, "0123456789" );
    printf( "%s\n", buf );
    
    centering( buf, 7, "0123456789" );
    printf( "%s\n", buf );
    return 0;
}

void centering( char *buffer, int col_num, char* string )
{
    int copy_len = 0;
    int source_index = 0;
    int target_index = 0;
    
    /* output initialize (using '@' instead of SPACE for check) */
    memset( buffer, '@', col_num );
    buffer[col_num] = '\0';
    
    /* length of string for output */
    copy_len = strlen( string );
    
    /* check which is longer */
    if( copy_len < col_num )
    {
        int diff = col_num - copy_len;
        target_index = diff / 2;
    }
    else
    {
        int diff = copy_len - col_num;
        source_index = diff / 2;
        copy_len = col_num;
    }
    
    memcpy( &buffer[target_index], &string[source_index], copy_len );
    return;
}

身も蓋もなくstring:centreを使いました。

1> c(center).
{ok,center}
2> center:center("center", 20).
"       center       "
3> center:center("center", 3).
"ent"
1
2
3
4
-module(center).
-export([center/2]).

center(Str, Len) -> string:centre(Str, Len).

文字列の前に指定幅分の空白を足して、文字列長から指定幅を引いて、
その半分を切り下げて、正なら右へ負なら左へローテイトして、
先頭から指定幅を取り出だしています。

   ('"'&,@,&'"')  1 center '123456789'
"5"
   ('"'&,@,&'"')  2 center '123456789'
"45"
   ('"'&,@,&'"')  3 center '123456789'
"456"
   ('"'&,@,&'"')  4 center '123456789'
"3456"
   ('"'&,@,&'"')  5 center '123456789'
"34567"
   ('"'&,@,&'"')  9 center '123456789'
"123456789"
   ('"'&,@,&'"')  10 center '123456789'
" 123456789"
   ('"'&,@,&'"')  11 center '123456789'
" 123456789 "
   ('"'&,@,&'"')  12 center '123456789'
"  123456789 "
1
center=.4 :'x{.((<.@-:@#)|.])(x#'' ''),y'

Mac OS X (PowerPC 32bit) アセンブリで。strcenter.o をリンクして使います。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
;; ------------------------------------------------
;; strcenter.s for Mac OS X (PowerPC 32bit)
;; % as -o strcenter.o strcenter.s
;; ------------------------------------------------

        .machine ppc
        .globl  _strcenter

;; char *strcenter(char *dest, const char *s, size_t width)
;;
;; r3: char *dest
;; r4: const char *s
;; r5: size_t width
;;
;; r7:  文字列 s のサイズ
;; r8:  文字列の前のスペースのサイズ
;; r9:  文字列の後のスペースのサイズ
;; r10: スペースのASCIIコード
;; r11: r3 のコピー
;; r12: r4 のコピー
;; 
_strcenter:
        ;; 初期化
        li      r7, 0
        li      r10, 32         ; ' '
        mr      r11, r3
        mr      r12, r4
        b       _strlen

_init_sizes:
        ;; 基準となるスペースのサイズ
        sub     r2, r5, r7      ; r2 = width - strlen(s)
        li      r0, 2
        divw    r2, r2, r0      ; r2 = r2 / 2

        ;; 余り
        sub     r0, r5, r7      ; r0 = width - strlen(s)
        andi.   r0, r0, 1

        ;; 前後のスペースのサイズを決定、コピー
        add     r8, r2, r0      ; 余りがあれば前のスペースに追加
        addi    r9, r2, 0

        ;; 基準のスペースのサイズが負の場合
        cmpi    cr7, r2, 0
        blt     cr7, _shorten_strcenter

;; 前のスペースをコピー
_copy_former_spaces:
        stb     r10, 0(r11)
        addi    r11, r11, 1
        subi    r8, r8, 1
        cmpli   cr7, r8, 0
        bgt     cr7, _copy_former_spaces

;; 文字列をコピー
_copy_str:
        lbz     r0, 0(r12)
        stb     r0, 0(r11)
        addi    r11, r11, 1
        addi    r12, r12, 1
        subi    r7, r7, 1
        cmpli   cr7, r7, 0
        bgt     cr7, _copy_str

;; 後のスペースをコピー
_copy_latter_spaces:
        stb     r10, 0(r11)
        addi    r11, r11, 1
        subi    r9, r9, 1
        cmpli   cr7, r9, 0
        bgt     cr7, _copy_latter_spaces

;; 終端をコピーして終了
_end_strcenter:
        li      r0, 0
        stb     r0, 0(r11)
        blr                     ; 関数を終了

;; 文字列の前後をカットしてコピー
_shorten_strcenter:
        sub     r12, r12, r8    ; 文字列の前半をカット
        add     r7, r7, r8
        add     r7, r7, r9      ; 文字列の後半をカット

_copy_shorten_str:
        lbz     r0, 0(r12)
        stb     r0, 0(r11)
        addi    r11, r11, 1
        addi    r12, r12, 1
        subi    r7, r7, 1
        cmpli   cr7, r7, 0
        bgt     cr7, _copy_shorten_str
        b       _end_strcenter

_strlen:
        mr      r2, r4
        li      r7, 0
        
__strlen:
        lbz     r0, 0(r2)       ; 1文字レジスタに移す
        cmpli   cr7, r0, 0
        beq     cr7, _init_sizes
        addi    r2, r2, 1
        addi    r7, r7, 1
        b       __strlen

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
fun center s width =
  let
    open StringCvt

    val len = size s
    val m = abs (width - len)
    val (left, right) = (m div 2, m div 2 + m mod 2)
  in
    if width < len then substring (s, left, len - left - right)
    else (padLeft #" " (left + right + len) o padRight #" " (right + len)) s
  end

マルチバイト非対応。

ex)
echo sprintf('<pre>[%s]</pre>', Centering('test', 4));

('test', 4) => [test]
('test', 8) => [  test  ]
('test', 5) => [test ]
('test', 2) => [es]
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<?php
function Centering($str, $width)
{
    $slen = strlen($str);
    if ($slen < $width) {
        $str = str_pad($str, $width, ' ', STR_PAD_BOTH);
    } else {
        $str = substr($str, (int)(($slen - $width)/2), $width);
    }
    return $str;
}
?>

バッチで書いてみました。文字列が半角空白を含まない場合は、引数をダブルクォーテー
ションで括る必要はありません。なお、18行目と20行目の行末に半角空白が 1つあるので
注意してください。

  e.g.
    C:\>@echo off & (for /l %i in (0,1,7) do center "abcde" %i) & @echo on
    []
    [c]
    [bc]
    [bcd]
    [abcd]
    [abcde]
    [abcde ]
    [ abcde ]

遅延環境変数展開を利用しているので、Windows NTでは動作しません。Windows 2000, XP, 
2003で動作を確認。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
:: center.bat

@echo off
  setlocal enabledelayedexpansion
    set l=0
    set m=0
    set n=0
    set t=%1
    set t=%t:"=%

    call :length "%t%" l

    if %2 gtr %l% (
      set /a m=%2-%l%
      set /a n=!m!/2
      :: パディング
      for /l %%i in (1,1,!n!) do set t= !t!
      for /l %%i in (1,1,!n!) do set t=!t! 
      set /a n=!m!%%2
      if !n! equ 1 set t=!t! 
    )

    if %l% gtr %2 (
      set /a m=%l%-%2
      set /a n=!m!/2
      :: トリミング
      for /l %%i in (1,1,!n!) do set t=!t:~1!
      for /l %%i in (1,1,!n!) do set t=!t:~0,-1!
      set /a n=!m!%%2
      if !n! equ 1 set t=!t:~0,-1!
    )

  endlocal & echo [%t%]
goto :EOF

:length
  setlocal
    set i=0
    set t=%1
    set t=%t:"=%

    :loop
      set t=%t:~1%
      set /a i+=1
    if not "%t%" == "" goto loop
  endlocal & set %2=%i%
goto :EOF

対象文字列の長さによって分岐しています。 露骨すぎてスマートとは言えないでしょう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#runtime "hsp3cl"
#module
#defcfunc spaces int len
    if len <= 0 : return ""

    sdim s, len + 1
    repeat len
        poke s, cnt, ' '
    loop
    return s

#defcfunc centered_text str target, int len
    if len <= 0 : return ""

    target_length = strlen(target)
    result = target
    if target_length < len {
        result = spaces((len - target_length)/2) + target + spaces((len - target_length + 1)/2)
    } else : if target_length > len {
        result = strmid(result, (target_length - len)/2, len)
    }
    return result
#global

    s = "*"
    repeat 10
        mes centered_text(s, 15)
        s += " *"
    loop
    stop

より単純なスクリプトです。 まず指定された長さの半角スペース列を用意し、次に文字列をpokeで書きこんでいます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#module
#defcfunc centered_text str _source, int len
    if len <= 0 : return ""
    source = _source

    source_strlen = strlen(source)
    sdim result, len + 1
    memset result, ' ', len
    poke result, limit((len - source_strlen)/2, 0, len/2), strmid(source, limit((source_strlen - len)/2, 0, source_strlen), len)
    return result
#global

実行例:
arc> (centering "hoge" 10)
"   hoge   "
arc> (centering "hogefuga" 5)
"ogefu"
1
2
3
4
5
6
7
(def mklist (n (o fill nil)) (map (fn (x) fill) (range 1 n)))

(def centering (str length)
  (withs (plen (- length (len str)) tlen (abs plen) l (trunc (/ tlen 2)) r (- tlen l))
    (if (positive plen)
        (string (mklist l #\space) str (mklist r #\space))
        (cut str l (- (len str) r)))))

PostScript で。 PostScript で文字数単位でやる実用性はほとんど無いでしょうね。 通常はフォント指定して文字列幅を計算、幅にあわせて切り捨てるなり圧縮するなり、でしょうから。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
%!PS

/FillSpace { % (String) FillSpace (String')
    dup length 1 sub 0 1 3 -1 roll {
        1 index exch 32 put
    } for
} bind def

/Centering { % (String) length Centering (NewString)
    dup string FillSpace dup
    3 index length 4 -1 roll
    2 copy le {
        sub neg 2 idiv 4 -1 roll putinterval
    } {
        2 copy sub 2 idiv 
        exch 6 -1 roll 3 1 roll getinterval
        exch pop 0 exch putinterval
    } ifelse
} bind def

% -------------------- Test Code ------------------
(ABC) 10 Centering ==
(ABCDEF) 10 Centering ==
(ABCDEF) 3 Centering ==
(ABCDEF) 6 Centering ==

組み込まれている関数で

1
2
// 例(Stringクラスのメソッド)
"Japanese".center(20)

なかなか面白い問題でした。

最初は文字列が指定より長い場合、短かい場合の処理を別々に書いてたのですが、 (空白を)「くわえる」と「削る」以外の部分は共通にできる事に気がつきました。

1
2
3
4
5
6
7
center n str = let s = (n - length str)
                   s1 = div s 2
                   s2 = s - s1
               in reverse.format s2.reverse.format s1 $ str
  where format n 
          | n >= 0   =(replicate n ' '++)
          |otherwise = drop (-n)

Index

Feed

Other

Link

Pathtraq

loading...