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;
}

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