20
$\begingroup$

The following examples only work for the first character of the whole string, but not of words.

s = "words are lowercase";
StringJoin[MapAt[ToUpperCase, Characters[s], 1]]   (* ==> "Words are lowercase" *)
StringReplacePart[#, ToUpperCase[StringTake[#, 1]], 1]&@s  (* ==> "Words are lowercase" *)

Is there any method along these lines to produce correct title case strings of the form:

"Words Are Lowercase"
$\endgroup$
  • 5
    $\begingroup$ See the first example in WordBoundary. $\endgroup$ – Michael E2 Sep 10 '13 at 13:24
  • $\begingroup$ @Kuba ah, of course, there are many many words to capitalize. $\endgroup$ – HyperGroups Sep 10 '13 at 13:24
  • $\begingroup$ @MichaelE2 Ah, I think of one example in Help page. $\endgroup$ – HyperGroups Sep 10 '13 at 13:25
  • 4
    $\begingroup$ To the close-voters, how exactly is this easily found in the documentation? What did you search by, or by which path did you navigate to this example? $\endgroup$ – Mr.Wizard Sep 10 '13 at 13:36
  • 4
    $\begingroup$ I didn't think it should be closed, but I looked up "WordBoundary" just to check on using it to make up an answer. However, if you search for "capitalize", WordBoundary is the second hit in V9. But see István's answer - those sorts of unexpected complications are reasons such questions should not be closed, at least not right away. $\endgroup$ – Michael E2 Sep 10 '13 at 13:54
24
$\begingroup$

From the documentation, though IMHO not easy to find:

StringReplace["this is a test", WordBoundary ~~ x_ :> ToUpperCase[x]]
"This Is A Test"

István Zachar highlighted a problem with WordBoundary that I'm still trying to understand. Nevertheless it seems that one can use:

strAcc = "árv ízt űr őt ük örf úr óg ép";

StringReplace[strAcc, z : (StartOfString | WhitespaceCharacter ~~ _) :> ToUpperCase[z]]
"Árv Ízt Űr Őt Ük Örf Úr Óg Ép"

It appears that the PCRE library at least as used by Mathematica does not recognize certain characters as letters. A few examples:

StringReplace[strAcc, z : RegularExpression["(?:\\A|\\s)."] :> ToUpperCase[z]]
StringReplace[strAcc, z : RegularExpression["\\b."] :> ToUpperCase[z]]
"Árv Ízt Űr Őt Ük Örf Úr Óg Ép"

"Árv Ízt űR őT Ük Örf Úr Óg Ép"  (* note odd handling *)
StringCases["abcőű", RegularExpression["\\w"]]
{"a", "b", "c"}                  (* ő and ű missing *)
$\endgroup$
  • 1
    $\begingroup$ Actually, LetterQ /@ Characters@"áéíóöőüű" returns all True, so it is only LetterCharacter that does not consider them letters... $\endgroup$ – István Zachar Sep 10 '13 at 13:58
  • $\begingroup$ @IstvánZachar Thank you. I'm guessing this has something to do with compilation to PCRE. Do you happen to know if Perl considers these to be letters? $\endgroup$ – Mr.Wizard Sep 10 '13 at 14:00
  • $\begingroup$ Sorry, no idea about Perl. $\endgroup$ – István Zachar Sep 10 '13 at 14:05
  • $\begingroup$ This isn't a separate answer, but it's worth knowing that "Title case" usually does not capitalize certain small words, so you'd do something like: replacelittlewords = z : (StartOfString | WhitespaceCharacter ~~ # ~~ WhitespaceCharacter) :> ToLowerCase[z] & /@ {"A", "An", "The", "Is", "On", "To"} and then StringReplace[ StringReplace["This is a test about which you will be amazed", z : (StartOfString | WhitespaceCharacter ~~ _) :> ToUpperCase[z]], replacelittlewords]. $\endgroup$ – Verbeia Sep 11 '13 at 3:18
  • 1
    $\begingroup$ Note that at least in version 11.3 RegularExpression["\\b."] correctly recognizes "ű" and "ő" as letters (but in version 8.0.4 it doesn't). $\endgroup$ – Alexey Popkov Nov 2 '18 at 7:06
19
$\begingroup$

Actually, WordBoundary won't always work correctly (see this thread):

str = "the lazy dog jumped over the quick brown fox.";
strAcc = "árv ízt űr őt ük örf úr óg ép";

StringReplace[str, WordBoundary ~~ x_ :> ToUpperCase[x]]
StringReplace[strAcc, WordBoundary ~~ x_ :> ToUpperCase[x]]
"The Lazy Dog Jumped Over The Quick Brown Fox."

"Árv Ízt űR őT Ük Örf Úr Óg Ép"   (* note ű,ő instead of Ű,Ő *)

Use instead this custom made toTitleCase:

toTitleCase[str__] := StringJoin@Riffle[ToUpperCase@StringTake[#, 1] <> 
       ToLowerCase@StringTake[#, {2, -1}] & /@ StringSplit@StringJoin@str, " "];

toTitleCase[str]
toTitleCase[strAcc]
"The Lazy Dog Jumped On The Quick Brown Fox."

"Árv Ízt Űr Őt Ük Örf Úr Óg Ép"

In version 10.1, this is built in as ToTitleCase (if I recall correctly, it was an experimental function; documentation is no longer accessible on the net). This version removes all non-alphanumeric characters from the string; I assume this is a bug.

ToTitleCase[str]
ToTitleCase[strAcc]
"The Lazy Dog Jumped On The Quick Brown Fox"

"Árv Ízt Űr Őt Ük Örf Úr Óg Ép"

ToTitleCase has an eventful development history: in version 11, it is removed from among the built-ins, but is still acccessible from the "GeneralUtilities`" context. It does not remove non-alphanumeric characters anymore from the string.

GeneralUtilities`ToTitleCase[str]
GeneralUtilities`ToTitleCase[strAcc]
"The Lazy Dog Jumped Over the Quick Brown Fox."

"Árv Ízt Űr Őt Ük Örf Úr Óg Ép"
$\endgroup$
  • 2
    $\begingroup$ Jumped on the fox? We learned it as over. :o) $\endgroup$ – Mr.Wizard Sep 10 '13 at 13:39
  • $\begingroup$ @Mr.Wizard Well, obviously there were other messages sent over the Moscow-Budapest hotline... If you examine it more closely, there are further discrepancies :) (simple explanation: my memory failed me) $\endgroup$ – István Zachar Sep 10 '13 at 13:49
  • $\begingroup$ Do you understand why ű is not considered a LetterCharacter? $\endgroup$ – Mr.Wizard Sep 10 '13 at 13:51
  • $\begingroup$ The function ToTitleCase doesn't seem to exist anymore (v11). $\endgroup$ – becko Apr 4 '17 at 20:21
  • 1
    $\begingroup$ @becko Thanks for pointing it out! Actually it does exist, but in GeneralUtilities; please see edit. $\endgroup$ – István Zachar Apr 5 '17 at 7:16
6
$\begingroup$

While I endorse Mr.Wizard's pattern matching solution and I've given Istvàn +1 I would also like to submit this function which is meant to not rely on string patterns and be as readable as possible:

toTitleCase[str_] := StringJoin[
  MapAt[
   ToUpperCase, Characters[str], 
   Position[Characters[" " <> StringTrim@str], " "]
   ]
  ]

toTitleCase["the lazy dog jumped over the quick brown fox"]

"The Lazy Dog Jumped Over The Quick Brown Fox"

$\endgroup$
  • $\begingroup$ I'm sorry, but I can't endorse this. MapAt has poor algorithmic complexity, so you are shifting an operation out of an optimized domain into a known inefficient one. You have also introduced a bug in the case that there is a trailing space on the string. $\endgroup$ – Mr.Wizard Sep 10 '13 at 18:48
  • $\begingroup$ @Mr.Wizard Fair enough, I didn't think about it in terms of speed especially with the sample being so tiny. :/ Fixed the problem with the trailing space though. $\endgroup$ – C. E. Sep 10 '13 at 19:17
2
$\begingroup$

I just came across this problem with strings containing parentheses and acronyms, and the above solutions didn't work correctly for me. This is the solution I came up with:

toTitleCase[str_] := StringReplace[
    ToUpperCase@str, (f_?LetterQ ~~ rest:(LetterCharacter..)) :> 
    f <> ToLowerCase[rest]
];

toTitleCase["the (LAZY) d.O.G."]

"The (Lazy) D.O.G."

$\endgroup$
2
$\begingroup$

Starting in M11.2 or M11.3 you can use Capitalize with "TitleCase" as the second argument:

Capitalize["words are lowercase", "TitleCase"]

"Words Are Lowercase"

$\endgroup$
1
$\begingroup$
rule = RegularExpression["\\b([[:lower:]])"] :> ToUpperCase["$1"];
StringReplace[s, rule]
$\endgroup$
  • $\begingroup$ It is worth to mention that your code works correctly in version 11.3, but not in version 8.0.4. (+1) $\endgroup$ – Alexey Popkov Nov 2 '18 at 7:08
  • $\begingroup$ @AlexeyPopkov Then what would be wrong in Version 8.0.4? $\endgroup$ – Αλέξανδρος Ζεγγ Nov 2 '18 at 7:46
  • $\begingroup$ It doesn't recognize "ű" and "ő" as letters, see the answer by Mr.Wizard: mathematica.stackexchange.com/a/32045/280 $\endgroup$ – Alexey Popkov Nov 2 '18 at 7:50
  • $\begingroup$ @AlexeyPopkov Might "[[:lower:]]" have a narrower scope in that version? $\endgroup$ – Αλέξανδρος Ζεγγ Nov 2 '18 at 7:52
  • $\begingroup$ \\b certainly has narrower scope. $\endgroup$ – Alexey Popkov Nov 2 '18 at 7:55
0
$\begingroup$

All of the answers posted has a wrong result when applied to this string :

"hoSsEiN HAdi" -> "HoSsEiN HAdi"

But you can use the below code to ensure that all of the subsequent letters has been changed to LowerCase:

StringJoin @ StringCases["aA AA aa Aa 1a 2a a2 A2", WordBoundary~~Shortest[x_] ~~ Shortest[y___] ~~ WordBoundary :> ToUpperCase[x] <> ToLowerCase[y]]

This gives the following result :

"Aa Aa Aa Aa 1a 2a A2 A2"
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.