2015年01月07日

C#で文字列を指定した行数に均等分割する

   このエントリーをはてなブックマークに追加 Clip to Evernote
皆さん、あけましておめでとうございます。
本年もどうぞよろしくお願いします。

昨年から懲りずに続けている 「どう書く?orgに感謝を込めて」シリーズ その60 です。


■問題 (出題者:nobsun さん)
一行の文字列を指定した数の行にできるだけ文字数が均等になるように分割してください. ただし,除算や剰余算を使わないで書いてみてください.
sample = "ゆめよりもはかなき世のなかをなげきわびつゝあかしくらすほどに四月十よひにもなりぬれば木のしたくらがりもてゆく"

divid 4 sample =>
"ゆめよりもはかなき世のなかを"
"なげきわびつゝあかしくらすほ"
"どに四月十よひにもなりぬれ"
"ば木のしたくらがりもてゆく"

divid 5 sample => 
"ゆめよりもはかなき世の"
"なかをなげきわびつゝあ"
"かしくらすほどに四月十"
"よひにもなりぬれば木の"
"したくらがりもてゆく"

divid 6 sample => 
"ゆめよりもはかなき"
"世のなかをなげきわ"
"びつゝあかしくらす"
"ほどに四月十よひに"
"もなりぬれば木のし"
"たくらがりもてゆく"
除算や剰余算を使わないでというのがいやらしいですね。
でも、高さ(行数)が分かっているわけですから、文字列を横に順に置いてゆく代わりに、 縦方向に置いてゆき、一番下までいったら、1行目に戻るようにしていけば、 各行の幅が分かります。
実際コードでは、文字を置いてゆくのではなく、カウントアップすることで、各行の幅を求めています。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;


namespace Doukaku.Org {
    class Program {
        static void Main(string[] args) {
            string sample = "ゆめよりもはかなき世のなかをなげきわびつゝあかしくらすほどに四月十よひにもなりぬれば木のしたくらがりもてゆく";
            PrintText(sample, 4);
            Console.WriteLine();
            PrintText(sample, 5);
            Console.WriteLine();
            PrintText(sample, 6);
            Console.ReadLine();
        }

        private static void PrintText(string sample,int height) {
            var list = DivideEqually(sample, height);
            foreach (var s in list)
                Console.WriteLine(s);
        }

        // 除算や剰余算を使わない方法
        static IEnumerable<string> DivideEqually(string s, int height) {
            int[] counts = new int[height];
            // 最初に、一行の幅を求めてしまう
            for (int i = 0, j = 0; j < s.Length; j++) {
                counts[i]++;
                if (++i >= height)
                    i = 0;
            }
            // 求めた幅にあうように、文字列を切り出してゆく。
            int start = 0;
            for (int i = 0; i < height; i++) {
                yield return s.Substring(start, counts[i]);
                start += counts[i];
            }
        }
    }
}

でも、後半部分で、Substringしているところがイヤだったので、Interactive Extensions(Ix) の Shareメソッド 使って 書き直してみました。

static IEnumerable<string> DivideEqually(string s, int height) {
    int[] counts = new int[height];
    // 最初に、一行の幅を求めてしまう
    for (int i = 0,j = 0; j < s.Length; j++) {
        counts[i]++;
        if (++i >= height)
            i = 0;
    }
    // 求めた幅にあうように、文字列を切り出してゆく。
    var q = s.Share();
    foreach (var count in counts) {
          yield return string.Concat(q.Take(count));
    }
}

ちょっとはすっきりしたかな。
でも、実際の仕事で書くとしたら、除算や剰余算を使うと思うので、 そのバージョンも書いてみました。

// 除算や剰余算を使った方法
static IEnumerable<string> DivideEqually2(string s, int height) {
    int width;
    int start = 0;
    for (int length = s.Length; length > 0; length -= width) {
        width = (int)Math.Ceiling((double)length / height--);
        yield return s.Substring(start, width);
        start += width;
    }
}

結局、Substring使うことになっちゃいました(^^;


 

この記事へのトラックバックURL

http://trackback.blogsys.jp/livedoor/gushwell/52398630