If you're interested in performance, and your particular circumstances restrict the characters in the string to those in the ASCII range, then there is something to be said for avoiding the overhead of multibyte character operations and assuming single byte operations will work for you.
This is 2.4.2 on a MacBook Pro, and you'll see that method 4, using bytesize
and byteslice
, and avoiding regexp
and map
, is twice as fast as the next fastest on longer strings, and five times as fast on the original example.
2.4.2 :001 > def method1(string, n)
2.4.2 :002?> string.gsub(/.{#{n}}/){ |sub| sub.chop }
2.4.2 :003?> end
=> :method1
2.4.2 :004 >
2.4.2 :005 > def method2(string, index)
2.4.2 :006?> # Here I use a regular expression to split the string every n characters
2.4.2 :007 > substrings = string.split(%r{(.{#{index}})})
2.4.2 :008?> .reject(&:empty?) # And cut out any empty strings that appear
2.4.2 :009?>
2.4.2 :010 > # Then we can merge the substrings together, without the list character in each substring
2.4.2 :011 > substrings.map do |substring|
2.4.2 :012 > substring.length < index ? substring : substring[0..-2]
2.4.2 :013?> end.join
2.4.2 :014?> end
=> :method2
2.4.2 :015 >
2.4.2 :016 > def method3(string, index)
2.4.2 :017?> string.gsub(/(.{#{index-1}})./, '\\1')
2.4.2 :018?> end
=> :method3
2.4.2 :019 >
2.4.2 :020 > def method4(string, n)
2.4.2 :021?> length = n - 1
2.4.2 :022?> (0..(string.bytesize / n)).each_with_object("") do |x, new_string|
2.4.2 :023 > new_string << string.byteslice(x*n, length)
2.4.2 :024?> end
2.4.2 :025?> end
=> :method4
2.4.2 :026 >
2.4.2 :027 > require 'benchmark'
=> true
2.4.2 :028 >
2.4.2 :029 > runs = 100000
=> 100000
2.4.2 :030 > Benchmark.bm(7) do |x|
2.4.2 :031 > string = '1234A1234B1234C'
2.4.2 :032?> n = 5
2.4.2 :033?> x.report("method 0") { runs.times {}}
2.4.2 :034?> x.report("method 1") { runs.times {method1(string, n)}}
2.4.2 :035?> x.report("method 2") { runs.times {method2(string, n)}}
2.4.2 :036?> x.report("method 3") { runs.times {method3(string, n)}}
2.4.2 :037?> x.report("method 4") { runs.times {method4(string, n)}}
2.4.2 :038?> end ; ""
user system total real
method 0 0.000000 0.000000 0.000000 ( 0.003366)
method 1 0.570000 0.000000 0.570000 ( 0.572950)
method 2 0.670000 0.000000 0.670000 ( 0.666871)
method 3 0.750000 0.000000 0.750000 ( 0.763856)
method 4 0.120000 0.000000 0.120000 ( 0.118647)
=> ""
2.4.2 :039 >
2.4.2 :040 > runs = 50000
=> 50000
2.4.2 :041 > Benchmark.bm(7) do |x|
2.4.2 :042 > string = '1234A1234B1234C'*50
2.4.2 :043?> n = 2
2.4.2 :044?> x.report("method 0") { runs.times {}}
2.4.2 :045?> x.report("method 1") { runs.times {method1(string, n)}}
2.4.2 :046?> x.report("method 2") { runs.times {method2(string, n)}}
2.4.2 :047?> x.report("method 3") { runs.times {method3(string, n)}}
2.4.2 :048?> x.report("method 4") { runs.times {method4(string, n)}}
2.4.2 :049?> end ; ""
user system total real
method 0 0.000000 0.000000 0.000000 ( 0.001685)
method 1 7.110000 0.010000 7.120000 ( 7.131064)
method 2 11.450000 0.010000 11.460000 ( 11.475658)
method 3 9.640000 0.070000 9.710000 ( 9.721599)
method 4 3.750000 0.010000 3.760000 ( 3.758784)
=> ""
2.4.2 :050 >
delete_each_n("abcabc", 2) #=> "acbc"
, when I expected that you wanted "acb". \$\endgroup\$ – thesecretmaster Feb 11 '18 at 20:21