First a note on naming:
cc2jl is a very cryptic name. Give the public function a clearer name, like
to_snake_case or something similar (note that the function does not do anything to a string that already is in
While I quite like the fact that you used a generator approach, which guarantees that you make only on pass over the string, this seems like the perfect place for a regular expression. Some quick googling finds this rather simple function:
s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()
This is definitely shorter, even though that cryptic regex is not very easy to understand. But following your code, while written quite straightforward, is also not easy, especially with the deep nesting.
It works by first splitting runs of upper-case letters followed by one or more lowercase letters so that the last upper-case letter goes to the lower-case letter run. So
Then it separates runs of not-upper-case letters, followed by a single upper-case letter with a
To get a bit more performance out of this, you should pre-compile the regexes:
first_cap_re = re.compile('(.)([A-Z][a-z]+)')
all_cap_re = re.compile('([a-z0-9])([A-Z])')
s1 = first_cap_re.sub(r'\1_\2', name)
return all_cap_re.sub(r'\1_\2', s1).lower()
Performance wise, all algorithms are similar:
Not quite surprising, they all give a linear behavior (as a function of string length).
My regex version needs to make two passes over the string and is therefore consistently slower. The function written by @Ev.Kounis in his answer outperforms both of ours, but the regex approach by @Peilonrayz is even faster, because it manages to do only one pass, but with a regex.
Note that all functions are very fast, so as long as you need this less than a few thousand times per second, any is fine.
The test strings were generated with this code:
strings = [''.join(random.choice(string.ascii_letters) for _ in range(n))
for n in range(10, 2000, 20)]
In Python 3 it could have been:
strings = [''.join(random.choices(string.ascii_letters, k=k))
for k in range(10, 2000, 20)]