Convert "ThisIsSomeString" to "this_is_some_string"?

On Sun, 20 Aug 2006, Daniel S. wrote:

Daniel S. wrote:

[email protected] wrote:

def snake_case string
return string unless string =~ %r/[A-Z]/
reversed_words = string.reverse.scan(%r/[A-Z]+|[^A-Z]*[A-Z]+?|[^A-Z]+/)
reversed_words.reverse.map{|word|
word.reverse.downcase}.join(’’).gsub(%r/+/,’_’)

This is what I got:

this is what i have - anyone have improvments? comments on camel_case?

harp:~ > cat a.rb
def snake_case string
return string unless string =~ /[A-Z]/
string.split(’’).map do |part|
break “” if part.empty?
reversed_words = part.reverse.scan(/[A-Z]+|[^A-Z]*[A-Z]+?|[^A-Z]+/)
reversed_words.reverse.map do |word|
word.reverse.downcase
end.join(’
’)
end.join(’_’)
end

def camel_case string
return string if string =~ %r/[A-Z]/ and string !~ %r//
words = string.strip.split %r/\s*
+\s*/
words.map!{|w| w.downcase.sub(%r/^./){|c| c.upcase}}
words.join
end

if $0 == FILE
require ‘test/unit’
require ‘enumerator’

class T < Test::Unit::TestCase
tests = {
“snake_case” => %w[
ThisIsSomeString this_is_some_string
FOOBar foo_bar
FOO_BAR foo_bar
FOO_Bar foo_bar
FOO_bar foo_bar
Foo foo
FooBAR foo_bar
FooBar foo_bar
Foo_Bar foo_bar
Foo_bar foo_bar
fooBar foo_bar
foo_BAR foo_bar
foo_Bar foo_bar
],

   "camel_case" => %w[
     foo Foo
     foo_bar FooBar
     foo_bar_foobar FooBarFoobar
     this_is_some_string ThisIsSomeString
   ]
 }

 tests.each do |meth, list|
   testno = -1
   list.each_slice(2) do |arg, expected|
     define_method "test_#{ meth }_#{ testno += 1 }" do
       actual = send meth, arg
       assert_equal expected, actual
     end
   end
 end

end
end

harp:~ > ruby a.rb
Loaded suite snake_camel
Started

Finished in 0.002831 seconds.

17 tests, 17 assertions, 0 failures, 0 errors

thanks for the input!

cheers.

-a

On 8/19/06, [email protected] [email protected] wrote:

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?/).map { |w|
w.downcase}.join(‘_’)
=> “foo_bar”

“FooBARbaz”.scan(/[a-z]+|A-Z?/).map { |w|
w.downcase}.join(‘_’)
=> “foo_bar_baz”

“FooBarBaz”.scan(/[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?/).map { |w|
w.downcase}.join(‘_’)
=> “foo_bar”

“Foo123Bar”.scan(/[^a-zA-Z]+|[a-z]+|A-Z?/).map {
|w| w.downcase}.join(‘_’)
=> “foo_123_bar”

“FooBar123”.scan(/[^a-zA-Z]+|[a-z]+|A-Z?/).map {
|w| w.downcase}.join(‘_’)
=> “foo_bar_123”

“123FooBar”.scan(/[^a-zA-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?/).map {
|w| w.downcase}.join(‘_’)
=> “foo123_bar”

“FooBar123”.scan(/[a-z]+|A-Z?/).map {
|w| w.downcase}.join(‘_’)
=> “foo_bar123”

But:

“123FooBar”.scan(/[a-z]+|A-Z?/).map {
|w| w.downcase}.join(‘_’)
=> “foo_bar”

This covers that case as well:

“123FooBar”.scan(/[^a-zA-Z]+|[a-z]+|A-Z?/).map
{ |w| w.downcase}.join(‘_’)
=> “123_foo_bar”

“Foo123Bar”.scan(/[^a-zA-Z]+|[a-z]+|A-Z?/).map
{ |w| w.downcase}.join(‘_’)
=> “foo123_bar”

“FooBar123”.scan(/[^a-zA-Z]+|[a-z]+|A-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?/)
{|w| w.downcase + ‘_’}[0…-2]
=> “foo_bar123”

“Foo123Bar”.gsub(/[^a-zA-Z]+|[a-z]+|A-Z?/)
{|w| w.downcase + ‘_’}[0…-2]
=> “foo123_bar”

“123FooBar”.gsub(/[^a-zA-Z]+|[a-z]+|A-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?/).map
{ |w| w.downcase}.join(‘_’)
end
end

    x.report("gsub") do
            iterations.times do

“FooBar123”.gsub(/[^a-zA-Z]+|[a-z]+|A-Z?/)
{|w| w.downcase + ‘_’}[0…-2]
end
end
end


Rick DeNatale

My blog on Ruby
http://talklikeaduck.denhaven2.com/