

文字数のカウントを見たいために、対象の文字列を、ある文字数ごとに、区切りの文字列で置き替えた文字列を作成したいとします。

このとき、文字数毎というのは、たとえば5文字毎に区切り文字を入れたいとする場合、元の文字列を`□□□□□□□□□□□□□□□□□□□□`、区切り文字を`■`とした時、

[code]

    □□□□■□□□□■□□□□■□□□□■
    ~~~~|~~~~|~~~~|~~~~
    4+1 |4+1 |4+1 |4+1
    
[/code]

といった文字列になるようなものを意図としています。

上記の実装例として、`String`クラスをオープンにして、下のようなメソッドを新しく作成したとします:

[code]

     class String
       def step_replace(steps, replace_str)
         split_str = self.split('').each_slice(steps)
         range_max = steps - replace_str.length - 1
         split_str.map { |x| x.slice(0..range_max) + [replace_str] }.join
       end
     end
    
[/code]

このとき、この`step_replace`は下のような形で利用することが可能です。

[code]

     puts ("あいうえお" * 3).step_replace(5, "んん")
     # output: あいうんんあいうんんあいうんん
    
[/code]

しかし、この`step_replace`は「文字列をいちいち`split`で配列にしたりしたものを加工している」という点で、冗長であるように感じます。もうすこし簡潔な書き方があるような気がしましたが、如何でしょうか。それともこれ以上簡潔にはならないのでしょうか。


CC BY-SA 3.0
2014-12-16T18:06:11.170
1810
2014-12-18T07:39:20.897
2014-12-16T19:31:18.667
939
939
question
ruby
任意の文字列に対して、ある文字数毎に区切り文字列を入れたものを取得したい


正規表現を使ってこんなのはどうでしょう。

(最後のasは端数でも大丈夫という確認用です）

[code]

    irb(main):001:0> ("あいうえお"*3+"as").scan(/(.{1,3}).{,2}/).join("んん")
    => "あいうんんあいうんんあいうんんas"
    
[/code]

これはjoinの前でこのようなリストが得られてそこからのjoinになります。

[code]

    irb(main):002:0> ("あいうえお"*3+"as").scan(/(.{1,3}).{,2}/)
    => [["あいう"], ["あいう"], ["あいう"], ["as"]]
    
[/code]

変数を適用するなら以下の様にできます

[code]

    irb(main):001:0> steps=5
    => 5
    irb(main):002:0> replace_str="んん"
    => "んん"
    irb(main):002:0> str_len=replace_str.length
    => 2
    irb(main):008:0> ("あいうえお"*3+"as").scan(/(.{1,#{steps-str_len}}).{,#{str_len}}/).join(replace_str)
    => "あいうんんあいうんんあいうんんas"
    
[/code]

おまけですが、挿入の場合は以下でできると思います。

[code]

    irb(main):003:0> ("あいうえお"*3).scan(/.{1,5}/).join("んん")
    => "あいうえおんんあいうえおんんあいうえお"
    
[/code]


CC BY-SA 3.0
2014-12-16T19:29:12.567
1816
2014-12-16T19:29:12.567
728
1810
answer


元のコードでは以下のような動作ですがあまり元の動作を気にしないで書いてみました。

[code]

    puts ("あいうえお" * 3 + "あい").step_replace(5, "んん")
    puts ("あいうえお" * 3 + "あい").step_replace(5, "んんんんんん")
    # >> あいうんんあいうんんあいうんんあいんん
    # >> あいうえんんんんんんあいうえんんんんんんあいうえんんんんんんあんんんんんん
    
[/code]

## 案: 文字列の配列に変換してから加工する

`split('')`より、配列に加工してる感が減るかなと思って[String#chars](http://docs.ruby-
lang.org/ja/2.1.0/method/String/i/chars.html)で置き換えました。
その後、`chars.each_slice(steps).join(&:join)`で`steps`文字ごとの文字列の配列に変換したあと、
文字列として編集し、結合。

[code]

    class String
      def step_replace(steps, replace_str)
        chars.each_slice(steps).map(&:join).each {|s|
          s[s.size - replace_str.size..-1] = replace_str if s.size == steps
        }.join
      end
    end
    puts ("あいうえお" * 3 + "あい").step_replace(5, "んん")
    puts ("あいうえお" * 3 + "あい").step_replace(5, "んんんんんん")
    # >> あいうんんあいうんんあいうんんあい
    # >> あいうえんんんんんんあいうえんんんんんんあいうえんんんんんんあい
    
[/code]

`replace_str.size`が`steps`より大きい場合のことを考えると以下のようにしておいた方がよさそう

[code]

    class String
      def step_replace(steps, replace_str) # !> previous definition of step_replace was here
        chars.each_slice(steps).map(&:join).each {|s|
          s[[s.size - replace_str.size, 0].max, replace_str.size] = replace_str if s.size == steps
        }.join
      end
    end
    puts ("あいうえお" * 3 + "あい").step_replace(5, "んん")
    puts ("あいうえお" * 3 + "あい").step_replace(5, "んんんんんん")
    # >> あいうんんあいうんんあいうんんあい
    # >> んんんんんんんんんんんんんんんんんんあい
    
[/code]

## 案: 区切り文字の位置の文字を直接置き換える

[Numeric#step](http://docs.ruby-
lang.org/ja/2.1.0/method/Numeric/i/step.html)で区切り文字の位置を求め、その位置の文字を置き換えて返すとよいのではないでしょうか。

[code]

    class String
      def step_replace(steps, replace_str)
        dup.tap {|s|
          replace_len = replace_str.size
          steps.step(size, steps) {|x|
            s[x - replace_len, replace_len] = replace_str
          }
        }
      end
    end
    puts ("あいうえお" * 3 + "あい").step_replace(5, "んん")
    puts ("あいうえお" * 3 + "あい").step_replace(5, "んんんんんん")
    # >> あいうんんあいうんんあいうんんあい
    # >> あいうえんんんんんんんんんんんあんんんんんん
    
[/code]

しかし`replace_str.size`が`steps`より大きい場合、意図していない結果になってしまいます。


CC BY-SA 3.0
2014-12-16T22:54:49.533
1821
2014-12-16T22:54:49.533
2599
1810
answer


区切る文字数を引数で指定したりはできませんが gsub と後方参照で置換ではどうでしょう。

[code]

    ("あいうえお"*3).gsub(/(...)../, "\\1んん")
    #=> "あいうんんあいうんんあいうんん"
    ("あいうえおかきくけこさし").gsub(/(...)../, "\\1んん")
    #=> "あいうんんかきくんんさし"
    
[/code]


CC BY-SA 3.0
2014-12-17T00:17:14.960
1828
2014-12-17T00:17:14.960
2494
1810
answer


`num`=何文字毎か、`char`=区切り文字

[code]

    class CustomString < String  
        def replace_separator!(num,char)  
            self.size.times{|i| self[i]= char if (i+1) % num == 0}  
            self  
        end  
    end  
    
    str = CustomString.new('あいうえおあいうえおあいうえおかきくけこさ')  
    str.replace_separator!(5,'|')  
     => "あいうえ|あいうえ|あいうえ|かきくけ|さ"  
    str.replace_separator!(3,'|')  
     => "あい|えお|いう|おあ|うえ|かき|けこ|"  
    
[/code]

要約すると

[code]

    str.size.times{|i| str[i]= '|' if (i+1) % 5 == 0}
    
[/code]

文字列を何番目の数字のときだけ指定のものにしています。  
こういうのはいかがでしょうか。


CC BY-SA 3.0
2014-12-18T02:26:38.157
1962
2014-12-18T07:39:20.897
2014-12-18T07:39:20.897
3884
3884
1810
answer