From: Rick DeNatale Date: 2006-08-22T00:45:19+09:00 Subject: Re: Convert "ThisIsSomeString" to "this_is_some_string"? On 8/19/06, ara.t.howard@noaa.gov wrote: > On Sun, 20 Aug 2006, [UTF-8] Marcin Miel��y�~Dski wrote: > > > Joshua Muheim wrote: > >> Hi all > >> > >> What's the fastest way to convert "ThisIsSomeString" to > >> "this_is_some_string"? > >> > >> Thanks for help, > >> Joshua > >> > > > > p "ThisIsSomeString".scan(/[A-Z][a-z]+/).map{|w|w.downcase}.join('_') > > p "this_is_some_string".split('_').map{|w|w.capitalize}.join > > > > irb(main):001:0> "FooBAR".scan(/[A-Z][a-z]+/).map{|w|w.downcase}.join('_') > => "foo" > > it's trickier than it looks. Oh, look, a mini-quiz! "FooBAR".scan(/[a-z]+|[A-Z](?:[A-Z]+|[a-z]+)?/).map { |w| w.downcase}.join('_') => "foo_bar" "FooBARbaz".scan(/[a-z]+|[A-Z](?:[A-Z]+|[a-z]+)?/).map { |w| w.downcase}.join('_') => "foo_bar_baz" "FooBarBaz".scan(/[a-z]+|[A-Z](?:[A-Z]+|[a-z]+)?/).map { |w| w.downcase}.join('_') => "foo_bar_baz" Now what to do about non-alphas? The above breaks: "Foo123Bar".scan(/[a-z]+|[A-Z](?:[A-Z]+|[a-z]+)?/).map { |w| w.downcase}.join('_') => "foo_bar" "Foo123Bar".scan(/[^a-zA-Z]+|[a-z]+|[A-Z](?:[A-Z]+|[a-z]+)?/).map { |w| w.downcase}.join('_') => "foo_123_bar" "FooBar123".scan(/[^a-zA-Z]+|[a-z]+|[A-Z](?:[A-Z]+|[a-z]+)?/).map { |w| w.downcase}.join('_') => "foo_bar_123" "123FooBar".scan(/[^a-zA-Z]+|[a-z]+|[A-Z](?:[A-Z]+|[a-z]+)?/).map { |w| w.downcase}.join('_') => "123_foo_bar" Or we might want to coalesce non-alpha sub-strings with a preceding alpha string: "Foo123Bar".scan(/[a-z]+|[A-Z](?:[A-Z]+|[a-z]+[^a-zA-Z]*)?/).map { |w| w.downcase}.join('_') => "foo123_bar" "FooBar123".scan(/[a-z]+|[A-Z](?:[A-Z]+|[a-z]+[^a-zA-Z]*)?/).map { |w| w.downcase}.join('_') => "foo_bar123" But: "123FooBar".scan(/[a-z]+|[A-Z](?:[A-Z]+|[a-z]+[^a-zA-Z]*)?/).map { |w| w.downcase}.join('_') => "foo_bar" This covers that case as well: "123FooBar".scan(/[^a-zA-Z]+|[a-z]+|[A-Z](?:[A-Z]+|[a-z]+[^a-zA-Z]*)?/).map { |w| w.downcase}.join('_') => "123_foo_bar" "Foo123Bar".scan(/[^a-zA-Z]+|[a-z]+|[A-Z](?:[A-Z]+|[a-z]+[^a-zA-Z]*)?/).map { |w| w.downcase}.join('_') => "foo123_bar" "FooBar123".scan(/[^a-zA-Z]+|[a-z]+|[A-Z](?:[A-Z]+|[a-z]+[^a-zA-Z]*)?/).map { |w| w.downcase}.join('_') => "foo_bar123" And here's a way to do it with gsub: "FooBar123".gsub(/[^a-zA-Z]+|[a-z]+|[A-Z](?:[A-Z]+|[a-z]+[^a-zA-Z]*)?/) {|w| w.downcase + '_'}[0..-2] => "foo_bar123" "Foo123Bar".gsub(/[^a-zA-Z]+|[a-z]+|[A-Z](?:[A-Z]+|[a-z]+[^a-zA-Z]*)?/) {|w| w.downcase + '_'}[0..-2] => "foo123_bar" "123FooBar".gsub(/[^a-zA-Z]+|[a-z]+|[A-Z](?:[A-Z]+|[a-z]+[^a-zA-Z]*)?/) {|w| w.downcase + '_'}[0..-2] => "123_foo_bar" The scan and join approach seems to be marginally faster: $ ruby benchsnake.rb Rehearsal -------------------------------------------- scanjoin 0.790000 0.030000 0.820000 ( 0.883282) gsub 1.030000 0.020000 1.050000 ( 1.106962) ----------------------------------- total: 1.870000sec user system total real scanjoin 0.810000 0.020000 0.830000 ( 1.011059) gsub 0.970000 0.040000 1.010000 ( 1.050524) == benchsnake.rb === require 'benchmark' include Benchmark iterations = 10000 bmbm do | x | x.report("scanjoin") do iterations.times do "FooBar123".scan(/[^a-zA-Z]+|[a-z]+|[A-Z](?:[A-Z]+|[a-z]+[^a-zA-Z]*)?/).map { |w| w.downcase}.join('_') end end x.report("gsub") do iterations.times do "FooBar123".gsub(/[^a-zA-Z]+|[a-z]+|[A-Z](?:[A-Z]+|[a-z]+[^a-zA-Z]*)?/) {|w| w.downcase + '_'}[0..-2] end end end -- Rick DeNatale My blog on Ruby http://talklikeaduck.denhaven2.com/