challenge テキスト行の正規化

改行文字を複数個含むテキストデータを格納する文字列を
最大長の行を除く各行末に指定したパディング文字を適切な数だけ追加して、
すべての行が最大長の行と同じ長さに揃う文字列に変換する
手続あるいは関数を書いてください。

元の文字列の最後は改行です。
行の長さはその行に含まれる(行末の改行を除く)文字の数です。

変換前の文字列例
"○○○○\n○○○○○○○\n\n○○○○○\n"

上の文字列例をパディング文字'☆'を指定して変換した文字列
"○○○○☆☆☆\n○○○○○○○\n☆☆☆☆☆☆☆\n○○○○○☆☆\n"

必須ではありませんが、
テキストデータをトラバースする回数を減らす工夫をすると面白いかもしれません。

Posted feedbacks - Nested

Flatten Hidden

例題の文字ではうまくいきました。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#変換前の文字列例
a = "○○○○\n○○○○○○○\n\n○○○○○\n"

#上の文字列例をパディング文字'☆'を指定して変換した文字列
ans = "○○○○☆☆☆\n○○○○○○○\n☆☆☆☆☆☆☆\n○○○○○☆☆\n"

#最大長の行の長さを測る。
size = a.lines.max_by{|x|x.size}.size

#各行にパディング文字を入れて表示。
p b = a.lines.map{|i|i.chomp}.map{|j|[j,("☆" * ((size - j.size) / 2)),"\n"].join}.join

#結果のチェック
p ans == b
なんのひねりも無いのですが、とりあえず…
(pad "○○○○
○○○○○○○

○○○○○
" #\☆)
;=> "○○○○☆☆☆
○○○○○○○
☆☆☆☆☆☆☆
○○○○○☆☆
"
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
(defun pad (string &optional (padchar #\Space))
  (with-output-to-string (out)
    (with-input-from-string (in string)
      (loop :for line := (read-line in nil) :while line
            :maximize (length line) :into maxlen
            :collect line :into lines
            :finally (dolist (line lines)
                       (let ((base (make-string maxlen 
                                                :initial-element padchar)))
                         (format out "~A~%" (replace base line))))))))
max関数って、keyを指定できたんだ。
これは便利。

出力結果
12345****
123******
123456789
12345****
12*******
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#coding: utf-8

def padding(inputStr, paddingChar = " "):
    maxNum = len(max(inputStr.split("\n"), key=(lambda(x):len(x))))
    outputStr = ""
    for linedata in inputStr.split("\n"):
        outputStr += linedata + paddingChar * (maxNum - len(linedata)) + "\n"
    return outputStr

if __name__ == '__main__':
    s = '''12345
123
123456789
12345
12'''

    print padding(s, "*")
>>> >>> print padding(u"○○○○\n○○○○○○○\n\n○○○○○\n", u'☆')
○○○○☆☆☆
○○○○○○○
☆☆☆☆☆☆☆
○○○○○☆☆
☆☆☆☆☆☆☆

出題通りの答えになっていないようですよ。

>元の文字列の最後は改行です。
これを忘れてた。
1
2
3
4
5
6
7
8
9
#coding: utf-8

def padding(inputStr, paddingChar = " "):
    maxNum = len(max(inputStr.split("\n"), key=(lambda(x):len(x))))
    outputStr = ""
-   for linedata in inputStr.split("\n"):
+   for linedata in inputStr.split("\n")[:-1]:
        outputStr += linedata + paddingChar * (maxNum - len(linedata)) + "\n"
    return outputStr

schemeの勉強で書いてみました

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
(define (padlines s ch)
  (let iter ((ls (string-split s "\n"))
             (maxlen 0)
             (acc 0))
    (if (null? ls)
        (string-join (map (lambda (f) (f maxlen))
                          (reverse (cdr acc))) "")
        (let ((len (string-length (car ls))))
          (iter (cdr ls)
                (if (> len maxlen) len maxlen)
                (cons (lambda (n)
                        (format "~A~A\n"
                                (car ls)
                                (make-string (- n len) ch)))
                      acc))))))
すみません。 ↓のようなつもりでした。
1
2
3
4
5
(define (pad lines ch)
  (let iter ((ls (string-split lines "\n"))
             (maxlen 0)
-            (acc 0))
+            (acc '()))

大げさなわりに、スマートじゃない気がしてます。

記号は'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
#include <string>
#include <vector>
#include <iostream>
#include <sstream>
#include <algorithm>
#include <iterator>
#include <functional>

struct Padding : public std::unary_function<std::string, std::string>
{
    const int  length;
    const char c;

    Padding(int length, int c) : length(length), c(c)
    {
    }

    std::string operator () (const std::string& s)
    {
        if(s.size() < length)
        {
            return s + std::string(length - s.size(), c);
        }
        else
        {
            return s;
        }
    }
};

// 改行を含む文字列を受けて、パディングされた文字列をベクタに格納する関数
void padAllLines(const std::string& src, std::vector<std::string>& strings)
{
    strings.clear();

    std::stringstream ss(src);
    std::string       s;
    int               maxLength = 0;

    while(std::getline(ss, s).good())
    {
        maxLength = maxLength < s.size() ? s.size() : maxLength;
        strings.push_back(s);
    }

    std::transform(strings.begin(), strings.end(), strings.begin(), Padding(maxLength, '*'));
}

int main(int, char* [])
{
    std::string              src("oooo¥nooooooo¥n¥nooooo¥n");
    std::vector<std::string> strings;

    padAllLines(src, strings);

    std::copy(strings.begin(), strings.end(), std::ostream_iterator<std::string>(std::cout, "¥n"));

    return 0;
}

こういうトラバーサルを減らす方法、見覚えがある。ということで見よう見まねで single traversal に。

1
2
3
4
5
6
7
pad ss = ss' where (ss', m) = pad' ss m

pad' [] n = ([], 0)
pad' (s:ss) n = ((take n $ s ++ repeat '*'):ss', max m $ length s)
 where (ss', m) = pad' ss n

main = mapM putStrLn . pad . lines =<< getContents
  • lines で1回
  • length で1回
  • take で1回

System Message: WARNING/2 (<string>, line 4)

Bullet list ends without a blank line; unexpected unindent.

の3回はトラバースしているように見えます

確かに、そう言われれば。

lines と length を展開すれば本当に一回に見えるのかな。考えてみます。

Cで。とくに面白いことはやってない。 全角・半角混じりでも動くように、という条件がついたら死地。

 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
#include<stdio.h>

char *padStr(char *str, char padChar)
{
    int len;
    char *ret;
    int line = 0;
    int maxlen = 0;
    char *p = str;
    char *q;

    while (*p) {
        for (len = 0; *p && *p != '\n'; p++, len++) { }
        if (maxlen < len) maxlen = len;
        if (*p == '\n') p++;
        line++;
    }

    ret = (char*)calloc(line * (maxlen + 1) + 1);

    p = str;
    q = ret;
    while (*p){
        for (len = 0; *p && *p != '\n'; len++) { *q++ = *p++; }
        for (; len < maxlen; len++) { *q++ = padChar; }
        if (*p == '\n') { *q++ = *p++; }
        if (q-ret >= (line * (maxlen + 1) + 1)) puts("******overflow******");
    }

    return ret;
}

int main()
{
    char *data = "oooo\noooooooo\n\nooooo\no\n";

    char *pad = padStr(data, 'x');
    
    printf("[%s]\n", pad);
    free(pad);

    return 0;
}
pad.c: In function ‘padStr’:
pad.c:19: warning: implicit declaration of function ‘calloc’
pad.c:19: warning: incompatible implicit declaration of built-in function ‘calloc’
pad.c:19: error: too few arguments to function ‘calloc’
pad.c: In function ‘main’:
pad.c:40: warning: implicit declaration of function ‘free’
失礼。callocの引数の数ちがってんじゃん。
VCEE2008でノーエラーで通ったから油断してた。
ヘッダファイルは大事ですね。
1
2
3
4
5
6
1a2
> #include<stdlib.h>
19c20
<       ret = (char*)calloc(line * (maxlen + 1) + 1);
---
>       ret = (char*)calloc(line * (maxlen + 1) + 1, 1);

なんか手続き型っぽい。

1
2
3
4
padding c ss = [x ++ (replicate (maxLength - (length x)) c) | x <- ss]
  where maxLength = maximum [length x | x <- ss]

main = getContents >>= return.unlines.padding '*'.lines >>= putStrLn
Split()メソッドが最後に空の文字列を返しちゃうので最後に削除しています。
 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
using System;

class Program {
    static void Main(string[] args) {
        string r = paddingAllLine("○○○○\r\n○○○○○○○\r\n\r\n○○○○○\r\n", '☆');
        Console.WriteLine(r);
    }

    static string paddingAllLine(string str, char chr) {
        string r = "";
        int maxLenghs = 0;

        foreach(string line in str.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries)) {
            maxLenghs = Math.Max(maxLenghs, line.Length);
        }

        foreach(string line in str.Split(new string[] { Environment.NewLine }, StringSplitOptions.None)) {
            r += line.PadRight(maxLenghs, chr) + Environment.NewLine;
        }

        r = r.Remove(r.LastIndexOf(Environment.NewLine, r.LastIndexOf(Environment.NewLine)));

        return r + Environment.NewLine;
    }
}

	
 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
if(!String.prototype.x){
    String.prototype.x = function(n){
        var result="";
        for(var i=0;i<n;i++){
            result += this;
        }
        return result;
    }
}

var str="○○○○\n○○○○○○○\n\n○○○○○\n";

var wk = [];
var re = /(.*)\n/g;
var max = 0;
var matchs;
var len;
var wk_l=0;
while((matchs=re.exec(str))!=null){
//    len=matchs[1].length;
    len=re.lastIndex - matchs.index;
    wk.push({string:matchs[1], length:len});
    ++wk_l;
    if(max<len) max = len;
}
var result="";
for(var i=0;i<wk_l;++i){
    result += wk[i].string + "☆".x(max - wk[i].length) + "\n";
}
print(result);//WScript.Echo(result);

とりあえずベタに。

1
2
3
4
import Data.List

padding text ch = let allLines = lines text
                  in  unlines $ map (\str->take (maximum$map length allLines) (str++repeat ch)) allLines

トラバースを2回にしてみました。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
test = "hoge\nPOPOPOPOPO\n\nPINGPONG\n"

lineLens []         n=[]
lineLens ('\n':xs)  n= n:lineLens xs 0
lineLens (x:xs)     n= lineLens xs (n+1)

padding' []        n ch mlen= []
padding' ('\n':xs) n ch mlen= replicate (mlen-n) ch ++ '\n':padding' xs 0 ch mlen
padding' (x:xs)    n ch mlen= x:padding' xs (n+1) ch mlen

padding text ch = let maxLen = maximum $ lineLens text 0
                  in  padding' test 0 '*' maxLen

Readerモナドを使ってみかけの変数を減らしてみました。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import Control.Monad.Reader
test = "hoge\nPOPOPOPOPO\n\nPINGPONG\n"
data Readable = Readable {pChar::Char,mLength::Int}

maxLineLen []         n= n
maxLineLen ('\n':xs)  n= max n (maxLineLen xs 0)
maxLineLen (x:xs)     n= maxLineLen xs (n+1)

padding' :: [Char] -> Int -> Reader Readable String
padding' []        n = return []
padding' ('\n':xs) n = paddingStr n >>= \padStr->padding' xs 0 >>=return.(padStr ++) 
padding' (x:xs)    n = padding' xs (n+1) >>= return.(x:) 

paddingStr :: Int -> Reader Readable String
paddingStr n = do mlen <- asks mLength 
                  ch   <- asks pChar
                  return (replicate (mlen-n) ch ++ "\n") 

padding text ch = let maxLen = maxLineLen text 0
                  in  runReader (padding' test 0) (Readable ch maxLen)

Cはもう出ちゃったけど,せっかくなので投稿.

マイナーな売りとしては,最後の行がnで終わっていなくても期待通りに動きます.

マルチバイト文字は,やっぱり未対応.

  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
107
108
109
110
111
112
113
114
115
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

#define ARRAY_UNIT_SIZE 16
#define handle_error(str) do { \
    perror(str); exit(EXIT_FAILURE); } while (0)

struct array_t {
    char **value;
    size_t size;
    size_t size_max;
};

struct array_t *
array_init(void)
{
    struct array_t *ret;
    if ((ret = calloc(1, sizeof(*ret))) == NULL)
        handle_error("calloc");
    return ret;
}

void
array_free(struct array_t *ptr)
{
    free(ptr);
}

int
array_push(struct array_t *self, char *value)
{
    if (self->size >= self->size_max) {
        self->size_max += ARRAY_UNIT_SIZE;
        if ((self->value = realloc(self->value,
                                   self->size_max * sizeof(self->value)))
            == NULL)
            handle_error("realloc");
    }
    self->value[self->size++] = value;
    return 0;
}

static int
_do_push(struct array_t *array, char *pos, size_t len)
{
    static int max = 0;
    if (len > max) max = len;
    array_push(array, pos);
    return max;
}

char *
strpad(char *orig, char pad)
{
    char *cur, *head, *ret = NULL;
    struct array_t *array = array_init();
    int len_max = 0;
    int i;

    for (cur = head = orig; *cur != '\0'; cur++) {
        if (*cur != '\n') continue;
        len_max = _do_push(array, cur, cur - head);
        head = cur + 1;
    }
    if (cur > head)  /* last line was not trailed by CR */
        len_max = _do_push(array, cur, cur - head);

    if ((cur = ret = calloc(1, array->size * (len_max + 1) + 1))
        == NULL)
        handle_error("calloc");
    head = orig;
    for (i = 0; i < array->size; i++) {
        size_t str_size = array->value[i] - head;
        size_t remain_size = len_max - str_size;
        char trailer = *array->value[i];

        memcpy(cur, head, str_size); cur += str_size;
        memset(cur, pad, remain_size); cur += remain_size;
        *cur++ = trailer;

        head = array->value[i] + 1;
    }

    array_free(array);
    return ret;
}

int
main(int argc, char **argv)
{
    char *str = "foo\nbar baz\n\nhoge\n";
    char *result = NULL;

    result = strpad(str, '*');
    assert(strcmp(result, 
                  "foo****\n"
                  "bar baz\n"
                  "*******\n"
                  "hoge***\n") == 0);
    printf("Result #1: [%s]\n\n", result);
    free(result);

    str = "\nhoge\nfoo bar baz";
    result = strpad(str, '^');
    assert(strcmp(result, 
                  "^^^^^^^^^^^\n"
                  "hoge^^^^^^^\n"
                  "foo bar baz") == 0);
    printf("Result #2: [%s]\n", result);
    free(result);

    exit(EXIT_SUCCESS);
}

マルチバイト文字に対応しました.

Ambiguous widthな一部の記号('○'とか'☆')について,wcwidth(3)が1を返す問題に対処するため,Markus Kuhn氏が作成されたwcwidthの実装を利用しています.

  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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <wchar.h>
#include <locale.h>

/* wcwidth.c functions */
int mk_wcwidth_cjk(wchar_t);
int mk_wcswidth_cjk(const wchar_t *, size_t);

#define ARRAY_UNIT_SIZE (sizeof(wchar_t) * 16)
#define handle_error(str) do { \
    perror(str); exit(EXIT_FAILURE); } while (0)

#define DEF_ARRAY_NEW(prefix, type) \
    type * prefix ## _new(void) { \
        type *r; \
        if ((r = calloc(1, sizeof(*r))) == NULL) \
            handle_error("calloc"); \
        return r; \
    }
#define DEF_ARRAY_FREE(prefix, type) \
    void _ ## prefix ## _free(type *p) { \
        free(p); \
    }
#define DEF_ARRAY_NPUSH(prefix, type) \
    int prefix ## _npush(type *p, u_char *value, size_t size) { \
        size_t c; \
        for (c = 0; c < size; c++) { \
            if (p->size >= p->size_max) { \
                p->size_max += ARRAY_UNIT_SIZE; \
                if ((p->value = realloc(p->value, p->size_max)) \
                    == NULL) \
                    handle_error("realloc"); \
            } \
            ((u_char *)(p->value))[p->size++] = value[c]; \
        } \
        return 0; \
    }
#define DEF_ARRAY_FIT(prefix, type) \
    type * prefix ## _fit(type *p) { \
        if ((p->value = realloc(p->value, p->size)) == NULL) \
            handle_error("realloc"); \
        return p; \
    }
#define DEF_ARRAY_COMMON(prefix, type) \
    DEF_ARRAY_NEW(prefix, type) \
    DEF_ARRAY_FREE(prefix, type) \
    DEF_ARRAY_NPUSH(prefix, type) \
    DEF_ARRAY_FIT(prefix, type)

struct wstr_t {
    wchar_t *value;
    size_t size;     /* in bytes */
    size_t size_max; /* in bytes */
};
DEF_ARRAY_COMMON(wstr, struct wstr_t);

int
wstr_push(struct wstr_t *p, wchar_t value)
{
    return(wstr_npush(p, (u_char *)&value, sizeof(value)));
}

void
wstr_free(struct wstr_t *p)
{
    if (p->value != NULL)
        free(p->value);
    _wstr_free(p);
}

wchar_t *
wstrpad(wchar_t *orig, wchar_t pad)
{
    wchar_t *cur, *ret;
    size_t wlen = 0, width = 0, width_max = 0;
    struct wstr_t *wstr = wstr_new();
    int i;

    for (cur = orig; *cur != L'\0'; cur++) {
        if (*cur == L'\n') {
            width_max = (width > width_max) ? width : width_max;
            width = 0;
        } else {
            width += mk_wcwidth_cjk(*cur);
        }
    }
    wlen = cur - orig;
    if ((wlen > 0) && (cur[-1] != L'\n'))
        width_max = (width > width_max) ? width : width_max;

    width = 0;
    for (cur = orig; cur - orig < (wlen + 1); cur++) {
        if (*cur == L'\n' || (*cur == L'\0' && width > 0)) {
            size_t remain_width = width_max - width;
            size_t pad_width = mk_wcwidth_cjk(pad);
            for (i = 0; i < remain_width / pad_width; i++)
                wstr_push(wstr, pad);
            width = 0;
        } else {
            int w = mk_wcwidth_cjk(*cur);
            if (w > 0) width += w;
        }
        wstr_push(wstr, *cur);
    }
    wstr_fit(wstr);

    if ((ret = calloc(1, wstr->size)) == NULL)
        handle_error("calloc");
    wmemcpy(ret, wstr->value, wstr->size / sizeof(wchar_t));
    wstr_free(wstr);

    return ret;
}

void
test_wstrpad(char *test, wchar_t *src, wchar_t pad, wchar_t *compare)
{
    wchar_t *padded = wstrpad(src, pad);
    char *result = (wcscmp(padded, compare) == 0) ? "Succeeded" : "Failed";
    printf("%s => %s.\n[%ls]\n\n", test, result, padded);
    free(padded);
}

int
main(int argc, char **argv)
{
    if (setlocale(LC_ALL, "") == NULL)
        handle_error("setlocale");

    test_wstrpad("Test case #1",
                 L"foo\nbar baz\n\nhoge\n", L'*',
                 L"foo****\n"
                 L"bar baz\n"
                 L"*******\n"
                 L"hoge***\n");

    test_wstrpad("Test case #2",
                 L"\nhoge\nfoo bar baz", L'^',
                 L"^^^^^^^^^^^\n"
                 L"hoge^^^^^^^\n"
                 L"foo bar baz");

    test_wstrpad("Test case #3",
                 L"○○○○\n○○○○○○○\n\n○○○○○\n", L'☆',
                 L"○○○○☆☆☆\n"
                 L"○○○○○○○\n"
                 L"☆☆☆☆☆☆☆\n"
                 L"○○○○○☆☆\n");

    test_wstrpad("Test case #4",
                 L"○○○○\n○○○○○○○\n\n○○○○○\n", L'*',
                 L"○○○○******\n"
                 L"○○○○○○○\n"
                 L"**************\n"
                 L"○○○○○****\n");

    test_wstrpad("Test case #5",
                 L"foo\nbar baz\n\nhoge", L'■',
                 L"foo■■\n"
                 L"bar baz\n"
                 L"■■■\n"
                 L"hoge■");

    exit(EXIT_SUCCESS);
}

とりあえず、普通に。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Sample222 {
    public static String paddingRight(String input, char padding) {
        String[] splited = input.split("\n", 0);
        int maxLen = 0;
        for (String s: splited) {
            maxLen = Math.max(maxLen, s.length());
        }
        StringBuilder builder = new StringBuilder();
        for (String s: splited) {
            builder.append(s);
            for (int index = 0, len = maxLen - s.length(); index < len; index++) {
                builder.append(padding);
            }
            builder.append("\n");
        }
        return builder.toString();
    }

    public static void main(String[] args) {
        System.out.println(paddingRight("○○○○\n○○○○○○○\n\n○○○○○\n", '☆'));
    }
}

GNU coreutilsのwc前提です。

マルチバイト文字の場合はUTF-8ロケール前提です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#!/bin/bash

function q222() {
    local padchar=$1
    local text=$2

    local maxlen=$(echo -n "$text" | wc -L)
    local padstr=$(yes "$padchar" | head -n $maxlen | tr -d '\n')

    echo -n "$text" | sed -e "s/\$/$padstr/;s/^\(.\{$maxlen\}\).*/\1/"
}

q222 '☆' $'○○○○\n○○○○○○○\n\n○○○○○\n'

マルチバイト処理に間違い。wc -Lは文字数ではなくカラム数を返すのに対し、"○"がEast Asian WidthでAmbiguousなだけでした。

以下、修正版です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/bin/bash

function linemax() {
    local line max len
    while read -r line; do
        len=${#line}
        ((max = max < len ? len : max))
    done
    echo $max
}

function q222() {
    local padchar=$1
    local text=$2

    local maxlen=$(echo -n "$text" | linemax)
    local padstr=$(yes "$padchar" | head -n $maxlen | tr -d '\n')

    echo -n "$text" | sed -e "s/\$/$padstr/;s/^\(.\{$maxlen\}\).*/\1/"
}

q222 '☆' $'○○○○\n○○○○○○○\n\n○○○○○\n'

これでトラバースは1回になってるんじゃないでしょうか。 書いてて混乱してきたので、検証していただけると幸いです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
test = "hoge\nPOPOPOPOPO\n\nPINGPONG\n"

padding' []         i  m ch= (0,[])
padding' ('\n':xs)  i  m ch= let (cMax,cStr) = padding' xs 0 (max i m) ch
                             in  (max m cMax,replicate ((max cMax m)-i) ch++'\n':cStr)
padding' (x:xs)     i  m ch= let (cMax,cStr) = padding' xs (i+1) (max i m) ch
                             in  (max m cMax,x:cStr)

padding text ch = snd $ padding' text 0 0 ch

{-
*Main> padding test '-'
"hoge------\nPOPOPOPOPO\n----------\nPINGPONG--\n"
-}
トラバースは1回になっていますね。
パディング文字列を replicate で作ってしまってから
連結するのは、ちょっともったいないかもしれませんね。

確認ありがとうございます。 とりあえず、replicateを使わないように書き直してみました。

勉強がてらArrowやらApplicativeやらMonadやら使ってます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import Control.Arrow
import Control.Monad.Reader
import Control.Applicative
test = "hoge\nPOPOPOPOPO\n\nPINGPONG\n"

padding' :: String->Int->Int->Reader Char (Int,String)
padding' []         i  m = return (0,[])
padding' ('\n':xs)  i  m = do (cMax,cStr) <- padding' xs 0 (max i m) 
                              newStr      <- padding'' ((max cMax m) -i) ('\n':cStr)  
                              return (max m cMax,newStr)
padding' (x:xs)     i  m = (arr(max m)*** arr(x:))  <$> padding' xs (i+1) (max i m) 

padding'' :: Int->String->Reader Char String
padding'' 0 str = return str
padding'' n str = ask>>= padding'' (n-1).(:str) 

padding text ch = snd $ runReader (padding' text 0 0) ch

	
1
2
3
4
5
6
def padRightAll(String s, String p, n = '\n'){
  s.split(n).with{ it*.padRight(it*.size().max(), p[0]).join(n) + n }
}

print r = padRightAll('○○○○\n○○○○○○○\n\n○○○○○\n', '☆')
assert r == '○○○○☆☆☆\n○○○○○○○\n☆☆☆☆☆☆☆\n○○○○○☆☆\n'

Squeak Smalltalk で。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
| in out width |
in := '○○○○
○○○○○○○

○○○○○
'.

out := String new writeStream.
width := 0.
in linesDo: [:line | width := width max: line size].
in linesDo: [:line | out nextPutAll: (line forceTo: width paddingWith: $☆); cr].
out contents

Haskellのナイーブなコード

  1. lines で行に分解する
  2. maximum . map length で各行の長さをカウントして最大長を求める
  3. map (++repeat c) で各行末にパディング文字の無限列を連結
  4. 3.で作った各行の先頭から2.で求めた最大長分だけとる
  5. unlines で1行にもどす
1
2
3
4
5
6
7
8
9
normLines0 :: String -> String
normLines0 = unlines . norm0 . lines

norm0 :: [String] -> [String]
norm0 = map . take . maximum . map length <*> map (++repeat c)

(<*>) :: (a -> b -> c) -> (a -> b) -> (a -> c) -- S コンビネータ
(f <*> g) x = (f x) (g x)
infixl 4 <*>

パディング文字を定義しわすれました。 パディング文字を引数で渡すバージョンを書きましたので、こちらをどうぞ。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
normLines0 :: Char -> String -> String
normLines0 c = unlines . norm0 c . lines

norm0 :: Char -> [String] -> [String]
norm0 c = map . take . maximum . map length <*> map (++repeat c)

(<*>) :: (a -> b -> c) -> (a -> b) -> (a -> c)
(f <*> g) x = f x (g x)

infixl 4 <*>
ナイーブ版の unlines と lines の間の部分を1トラバースでやる
(*+) は2つの文字列を連結するが同時に前の文字列の長さもカウントする

1
2
3
4
5
6
7
8
9
norm1 :: Char -> [String] -> [String]
norm1 c xs = xs'
  where (m,xs') = f xs
        f []     = (0,[])
        f (y:ys) = let
                     (i,y')  = y *+ replicate (m-i) c
                     (j,ys') = f ys
                   in (max i j, y':ys')
        xs *+ ys = foldr (\ z (p,zs) -> (p+1,z:zs)) (0,ys) xs

unlines も lines もなしにして、全体を1トラバースでする方法 #8241 と(たぶん)同じ方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
normLines :: Char -> String -> String
normLines c xs = xs'
  where (m,xs') = f 0 xs
        f _ []     = (0,"")
        f i (x:xs) | x == '\n' = let
                                   (j,ys) = f 0 xs
                                 in (max i j, g (m-i) ('\n':ys))
                   | otherwise = let
                                   (j,ys) = f (i+1) xs
                                 in (j,x:ys)
        g 0 xs = xs
        g i xs = c:g (i-1) xs
C:\temp>cscript /nologo textJustify.js
○○○○☆☆☆
○○○○○○○
☆☆☆☆☆☆☆
○○○○○☆☆
 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
Array.prototype.each = function(iterator){
  for(var i=0, length=this.length; i<length; i++) iterator(this[i],i);
}
String.prototype.width = function(){
  var result = this.length;
  for(var i=0; i<this.length; i++) {
    if(this.charCodeAt(i) >= 128) result++;
    if(this.charAt(i).match(/[。-゚]/)) result--;
  }
  return result;
}
String.prototype.times = function(count){
  var result = '';
  for(var i=0; i<count; i++) result += this;
  return result;
}
//----------------------------
function textJustify(text,padding){
  var result = text.split("\n");
  var last = result.length - 1;
  var maxLength = 0;
  result.each(function(v){
    maxLength = v.width()>maxLength? v.width(): maxLength;
  });
  result.each(function(v,i){
    if((i!=last)||(v!="")){
      var restLength = (maxLength - v.width()) / padding.width();
      result[i] = v + padding.times(restLength|0);
    }
  });
  return result.join("\n");
}
//----------------------------
//test
WScript.Echo(textJustify("○○○○\n○○○○○○○\n\n○○○○○\n","☆"));
LINQを使って処理を行いました。
変数の数を減らすことに留意しました。
これ以上変数を減らすとパフォーマンス落ちそうなので、この程度で。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
    class MyMain
    {
        public static void Main(string[] args)
        {
            Console.WriteLine(TextRegex.action("○○○○\n○○○○○○○\n\n○○○○○\n", '☆'));
            Console.ReadLine();
        }
    }

    class TextRegex
    {
        static public string action(string str, char c)
        {
            List<string> lineList = str.Split(new string[] { Environment.NewLine, "\n" }, StringSplitOptions.RemoveEmptyEntries).ToList();
            int maxLen = lineList.Max(s => s.Length);
            return string.Join( Environment.NewLine,  lineList.Select(s => s.PadRight(maxLen, c)).ToArray() );
        }
    }
1
2
3
4
5
6
(use srfi-13)

(define (padlines s ch)
  (let* ((ss (string-split s #\newline))
         (n (apply max (map string-length ss))))
    (string-join (map (cut string-pad-right <> n ch) ss) "\n")))
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/usr/bin/perl

use strict;
use warnings;
use utf8;
use Encode;
use Getopt::Long;
use List::Util qw/max/;

binmode(STDIN, ':utf8');
binmode(STDOUT, ':utf8');

my $pad_char = '☆';
GetOptions('padding-char=s' => sub { $pad_char = decode 'utf8', $_[1] });

sub pad {
  my $pad_char = shift;
  my @lines = split /\n/, shift;
  my $len = max map length, @lines;
  join '', map { $_ . $pad_char x ($len - length($_)) . "\n" } @lines;
}

print pad($pad_char, do { local $/; <> }), "\n";

ひねりが無いですが...文字列から文字列へとのことでしたので、awkらしい標準入力→標準出力ではなく、あえて関数にしてみました。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
BEGIN {
  a = "1234\n1234567\n\n12345\n";
  pad = "@";
  print padding( a, pad );
}

function padding( src, pad ) {
  maxlen=0;

  cnt = split(src, b, "\n");
  for(i=1;i<=cnt;i++) {
    if( length(b[i])>maxlen )
      maxlen=length(b[i]);
  }

  output = "";
  for (i=1; i<cnt; i++) {
    for(;maxlen>length(b[i]);)
      b[i] = b[i] pad;
    output = output b[i] "\n";
  }

  return output;
}
   '*' pad data
oooo***
ooooooo
*******
ooooo**

もしパディング文字が空白固定でよければ
   ,,.&LF,;._2 data
oooo   
ooooooo
       
ooooo  
1
2
3
4
5
6
7
8
pad=:4 :',,.&LF a{.!.x~&>>./#&>a=.<;._2 y'

data=:0 :0
oooo
oooooooo

ooooo
)

なでしこで書いてみました。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
「○○○○{\n}○○○○○○○{\n}{\n}○○○○○{\n}」を「☆」でテキスト行正規化して表示

●テキスト行正規化(対象文字列を,パディング文字で)
  変数1とは整数。変数2とは文字列
  対象文字列を反復。もし(対象の文字数)>変数1ならば、変数1に対象の文字数を代入。
  最大文字数に変数1を代入
  対象文字列を反復
    変数2=変数2&対象
    もし(最大文字数-(対象の文字数))>0ならば、(最大文字数-(対象の文字数))回、変数2=変数2&パディング文字
    変数2=変数2&改行
  それに変数2を代入

Haskellのデータ再帰が大活躍ですね…

Arrow.loopを使ったCircular Programmingでやってみました。皆さんのO(n)の解とそれ以外は一緒だと思います。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
module Main where

import Control.Arrow

_normLine :: Char -> Int -> (String, Int) -> (String, Int)
_normLine _ i ([], _) = ([], i)
_normLine c i (ch:chs, m) = (repl' cch (ch : str'), max i m')
    where
        (str', m') = _normLine c i' (chs, m)

        (cch, i') = case (ch) of
            '\n'      -> (max 0 $ m - i, 0)
            otherwise -> (0, i + 1)

        repl' 0 s = s
        repl' j s = c : repl' (j - 1) s

main = putStr $ (loop $ _normLine '*' 0) "oooo\nooooooo\n\nooooo\n"
 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
#module

#define ctype MAX(%1,%2) ((%1)*((%1) > (%2)) | (%2)*((%1) <= (%2)))
#define ctype numrg(%1,%2=0,%3=MAX_INT) (((%2) <= (%1)) && ((%1) <= (%3)))
#define ctype IsSJIS1st(%1) (numrg(%1, 0x81, 0x9F) || numrg(%1, 0xE0, 0xFC))
#define ctype IsSJIS2nd(%1) (numrg(%1, 0x40, 0x7E) || numrg(%1, 0x80, 0xFC))

#deffunc MakeTheSameLineLength var code, \
;    local buf, local stmp, local index, local maximize, local len, local c
    
    // 最長の行を探す
    dim index, 2
    repeat
        c = peek(code, index(0))
        
        // 終了か
        if ( c == 0 ) {
            // 最長の座を争う ( index(1) がこの行のバイト数になっている )
            maximum = MAX(maximum, index(1))
            break
        }
        
        if ( IsSJIS1st(c) ) {
            index(0) += 2
            index(1) += 2
            continue
        }
        
        // 改行か
        if ( c == 0x0D || c == 0x0A ) {
            // 最長の座を争う ( index(1) がこの行のバイト数になっている )
            maximum = MAX(maximum, index(1))
            if ( c == 0x0D && peek(code, index(0) + 1) == 0x0A ) {
                index(0) ++    // CR + LF
            }
            index(1) = 0
        }
        index(0) ++
        index(1) ++
    loop
    
    // 変数を確保
    sdim stmp,  maximum + 64
    sdim buf , (maximum + 2) * 80 + 1
    
    // 文字数をそろえる
    dim index, 2
    repeat
        getstr stmp, code, index : index += strsize
        len = strlen(stmp)
        
        if ( strsize == 0 ) { break }
        
        // 足りない分半角スペースで埋める
        repeat maximum - len, len
            poke stmp, cnt, ' '
        loop
        
        wpoke stmp, maximum, 0x0A0D
        
        // buf に追加する
        memcpy buf, stmp, maximum + 2, index(1)
        index(1) += maximum + 2
    loop
    
    // code に移し替える
    sdim   code, index(1) + 1
    memcpy code, buf, index(1)
    
    return maximum
    
#global

*main
    sdim text
    buf = "○○○○\n○○○○○○○\n\n○○○○○\n"
    MakeTheSameLineLength buf
    
    text = "最大文字列長は "+ stat +" でした。\n"
    notesel buf
    repeat notemax
        noteget stmp, cnt
        text += strf("%02d行目:", cnt) +"{"+ stmp +"}\n"
    loop
    noteunsel
    
    objmode 2
    mesbox text, ginfo(12), ginfo(13)
    stop
バッチで。

ファイルやコマンドの実行結果を解析する際、 for文が空行を暗黙
的に読み飛ばしてしまうので、その点を findstrコマンドで補って
います。

なお、パディング用の文字に半角空白を指定することはできません。
これはechoコマンドの仕様に依存するためです。

あと、バッチではマルチバイト文字をバイト単位で扱うことができ
ないため、シングルバイトとマルチバイトが混在するファイルには
対応できていません。

Windows XPで動作を確認しました。
 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
@echo off
  setlocal enabledelayedexpansion
    set c=%~2
    set f=%~1
    set i=0
    set l=0
    set m=0
    
    if not exist "%f%" (
      echo usage: %~n0 FILE CHARACTER >&2
      exit /b 1
    )
    
    call :length "%c%" l
    if %l% neq 1 (
      echo usage: %~n0 FILE CHARACTER >&2
      exit /b 1
    )
    
    for /f "tokens=1,2 delims=:" %%i in ('findstr /n /v /r "^$" %f%') do (
      set s[%%i]=%%j
      call :length "%%j" l[%%i]
      if !l[%%i]! gtr !m! set m=!l[%%i]!
      set /a i+=1
    )
    :: 空行
    for /f "delims=:" %%i in ('findstr /n /r "^$" %f%') do (
      set s[%%i]=
      set l[%%i]=0
      set /a i+=1
    )
    
    for /l %%i in (1,1,%i%) do (
      set /a n=m-!l[%%i]!
      for /l %%j in (1,1,!n!) do set s[%%i]=!s[%%i]!%c%
      echo !s[%%i]!
    )
  endlocal & exit /b 0
goto :EOF

:length
  setlocal enabledelayedexpansion
    set i=0
    set t=%~1
    
    if not "%t%" == "" (
      :_
        set t=!t:~1!
        set /a i+=1
      if not "!t!" == "" goto _
    )
  endlocal & set %~2=%i%
goto :EOF

 scalaがまだの様なので。

1
2
3
4
5
6
7
8
9
object NormalizeText {
    def normalize(s:String,p:String):String = {
        val    t:List[String] = s.lines.toList
        val    m:Int = t.map(l => l.size).sort((a,b) => a < b).last
        t.map(l => l + p * (m - l.size)).mkString("\n") + "\n"
    }
    def main(args:Array[String]):Unit =
        print(normalize("○○○○\n○○○○○○○\n\n○○○○○\n","△"))
}

 Haskellの投稿を参考に1トラバースで書いてみました。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
object NormalizeText {
    def normalize(s:String, p:Char):String = {
        def _normalize(s:List[Char], l:Int, m:Int):Pair[List[Char],Int] = s match {
                case List() => (List(), m)
                case '\n'::r => {
                    val    (t, x) = _normalize(r, 0, l.max(m))
                    (Stream.const(p).take(x - l).toList:::('\n'::t), x)
                }
                case c::r => {
                    val    (t, x) = _normalize(r, l + 1, m)
                    (c::t, x)
                }
            }
        _normalize(s.toList, 0, 0)._1.mkString("")
    }
    def readLines:List[String] = readLine match {
            case null => List("")
            case l => l::readLines
        }
    def main(args:Array[String]):Unit = print(normalize(readLines.mkString("\n"), '△'))
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
def padding(inputStr, paddingChar = " "):
    maxNum = max(len(line) for line in inputStr.split("\n"))
    return "\n".join(line.ljust(maxNum, paddingChar)
                      for line in inputStr.split("\n"))

if __name__ == '__main__':
    s = '''12345
123
123456789
12345
12'''
    
    print padding(s, "*")
言語はViViScript
素直に改行で分割し、最大行長をもとめ、パディング文字を付加しただけ
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
$ar="oooo\nooooooo\n\nooooo\n".split('\n');        //    改行で分割
$maxlen = 0;
for($i = 0; $i < $ar.length; ++$i) {
    if( $ar[$i].length > $maxlen )
        $maxlen = $ar[$i].length;        //    最大行長を求める
}
$out = "";
for($i = 0; $i < $ar.length; ++$i) {
    $out += $ar[$i];
    if( $ar[$i].length < $maxlen )        //    パディング文字を付加
        $out += "*".repeat($maxlen - $ar[$i].length);
    $out += "\n";
}
cout << $out;
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#! /usr/bin/ruby

class String
    def normalize chr
        array = split("\n")
        length = array.map{|l| l.length}.max
        array.map{|l| l.ljust length, chr}.join("\n")+("\n")
    end
end

p "a\nbb\nccc\ndddd\n".normalize('-')
単純な実装です。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#light

let pad ch (text : string) =
    let lines = text.Split([|'\n'|])
    let maxLength = lines |> Array.map String.length |> Array.max
    let paddedLines = lines.[0..(lines.Length - 2)]
                      |> Array.map (fun x -> x.PadRight(maxLength, ch))
    String.concat "\n" paddedLines ^ "\n"

"○○○○\n○○○○○○○\n\n○○○○○\n" |> pad '☆' |> printfn "%s"

Index

Feed

Other

Link

Pathtraq

loading...