8
$\begingroup$

Given System names of 2 or more characters:

systemNames = Names["System`*"] // Select[StringLength[#] > 1 &] ;

Is there a more compact way to split at upper case letters that avoids the Partition and subsequent StringJoin?

EDIT: match to digits as well as upper case, but still w/ limitations outlined below:

systemNames // 
  Map[StringSplit[#, 
      x : c_ /; (UpperCaseQ[c] || DigitQ[c]) :> x] & /* (Partition[#, 2] & )] // 
 Map[Map[StringJoin]] 

enter image description here

Also, what's the easiest way to also split at $ and also the final upper case char, eg N? I coudn't find an easy way with Alternatives and longer ___.

$\endgroup$
9
$\begingroup$

You can do all of it directly with StringCases!

StringCases[systemNames, hump : (CharacterRange["A", "Z"] | DigitCharacter) | "$" ~~ restOfCamel : CharacterRange["a", "z"] ...]

(*
{{A,A,S,Triangle},{Abelian,Group},{Abort},{Abort,Kernels},{Abort,Protect},
{Above},{Abs}, ...5182... ,{$,User,Base,Directory},
{$,User,Documents,Directory},{$,User,Name},{$,Version},{$,Version,Number},
{$,Wolfram,I,D},{$,Wolfram,U,U,I,D}}
*)

And, out of pure envy, a version that wrongly groups abbreviations as suggested by @WReach!

StringCases[systemNames, hump : CharacterRange["A", "Z"] .. | "$" ~~ restOfCamel : CharacterRange["a", "z"] ...]

(*
{{AASTriangle},{Abelian,Group},{Abort},{Abort,Kernels},{Abort,Protect},{Above},
{Abs},{Absolute}, ...5185... ,{$,User,Base,Directory},
{$,User,Documents,Directory},{$,User,Name},{$,Version},{$,Version,Number},
{$,Wolfram,ID},{$,Wolfram,UUID}}
*)

Just 2 dots of difference! Finally something that was easier to do with ordinary patterns instead of regexes! Noooot.

Finally

A version with ordinary patterns that does the marvels that @WReach`s look-ahead regexes can do. Two separate pattern "levels" must be used, though.

StringCases[systemNames, hump : (CharacterRange["A", "Z"] | DigitCharacter) | "$" ~~ restOfCamel : CharacterRange["a", "z"] ...]
Replace[%, {pre___, Longest[d1__?UpperCaseQ], post___} :> {pre, StringJoin@d1, post}, {1}]

(*
{{"AAS", "Triangle"}, {"Abelian", "Group"}, {"Abort"}, {"Abort", "Kernels"}, {"Abort", "Protect"},
{"Above"}, {"Abs"}, {"Absolute"}, ...5184... , {"$", "User", "Base", "Directory"},
{"$", "User", "Documents","Directory"}, {"$", "User", "Name"}, {"$", "Version"}, {"$", "Version", "Number"},
{"$", "Wolfram", "ID"}, {"$", "Wolfram", "UUID"}}
*)

And a quick fix for the {Te,X} issue

Replace[%, {pre___, "Te", "X", post___} :> {pre, "TeX", post}, {1}]
$\endgroup$
  • $\begingroup$ Alas, the two dots are not quite enough for the abbreviations. Notice how "AASTriangle" is not split up. It doesn't detract from a good answer, though. $\endgroup$ – WReach Nov 20 '14 at 13:46
  • $\begingroup$ Ohh, you are correct! I knew the "just 2 dots" would come back to bite me. $\endgroup$ – Aisamu Nov 20 '14 at 14:10
  • $\begingroup$ I give up, @WReach! Couldn't do it elegantly... I better learn myself some regex magic. $\endgroup$ – Aisamu Nov 20 '14 at 16:08
  • $\begingroup$ +1, I like the approach, but your method eats digits, "BarChart3D" -> {"Bar", "Chart", "D"}. My original method also requires matching to DigitQ. Can you modify yours? $\endgroup$ – alancalvitti Nov 21 '14 at 18:12
  • $\begingroup$ There you go, @alancalvitti! $\endgroup$ – Aisamu Nov 21 '14 at 19:38
12
$\begingroup$

Here is a method using StringSplit and RegularExpression:

StringSplit[systemNames, RegularExpression["(?=[$[:upper:]])"]]

(*
   {{A,A,S,Triangle},{Abelian,Group},{Abort},{Abort,Kernels},{Abort,Protect},
    {Above},{Abs},<<5192>>,{$,User,Documents,Directory},{$,User,Name},{$,Version},
    {$,Version,Number},{$,Wolfram,I,D},{$,Wolfram,U,U,I,D}}
*)

It works by splitting each string wherever it finds a zero-length substring which is followed by a dollar sign or an upper case character. (?=...) is the zero-length look-ahead syntax. [$[:upper:]] matches any character in the set comprised of the literal dollar sign along with all members of the upper case character class.

It might be interesting to keep groups of otherwise loose upper case letters together and treat them as abbreviations. If so, then:

StringSplit[
  systemNames
, RegularExpression[
    "(?=\\$)|(?<![[:upper:]])(?=[[:upper:]])|(?=[[:upper:]][^[:upper:]])"
  ]
]

(*
   {{AAS,Triangle},{Abelian,Group},{Abort},{Abort,Kernels},{Abort,Protect},
    {Above},<<5191>>,{$,User,Name},{$,Version},{$,Version,Number},
        {$,Wolfram,ID},{$,Wolfram,UUID}}
*)

Note how "AAS" in the first entry and "UUID" in the last are kept together as abbreviations. This pattern works by matching a dollar sign, or an upper case letter that is not preceded by another upper case letter, or an upper case letter followed by a non-uppercase letter.

Edit: Including Digits as "Hump" Characters

A comment requests that digits be treated as "hump" characters. This is accomplished by changing each occurrence of the regular expression pattern [[:upper:]] to [\\d[:upper:]]. \\d matches any digit character.

namesWithDigits = Select[systemNames, !StringFreeQ[#, DigitCharacter]&];

StringSplit[namesWithDigits, RegularExpression[#]]& @
    "(?=\\$)|(?<![\\d[:upper:]])(?=[\\d[:upper:]])|(?=[\\d[:upper:]][^\\d[:upper:]])"

(*
  {{Appell,F1},{Arrow,3D,Box},{Bar,Chart,3D},{Bezier,Curve,3D,Box},<<85>>,
   {Text,3D,Box},{Text,3D,Box,Options},{V2,Get},{Vector,Plot,3D}}
*)
$\endgroup$
  • 1
    $\begingroup$ Everyday you teach me something about regexes! $\endgroup$ – Aisamu Nov 20 '14 at 2:31
  • $\begingroup$ +1, Great, particularly the complication to handle AAS, UUID, and many more, which was the evental goal. Can you include matches to DigitQ? Currently: "BarChart3D" -> {"Bar", "Chart3", "D"}. $\endgroup$ – alancalvitti Nov 21 '14 at 18:09
  • $\begingroup$ @WReach, Also, is RegularExpression necessary here? The syntax is cryptic. We need a more transparent and scalable approach going forward for advanced analytics, whether text or any other character encoded signal). $\endgroup$ – alancalvitti Nov 21 '14 at 18:10
  • $\begingroup$ @alancalvitti See the new section concerning digit characters. I'm afraid that the Mathematica symbolic string pattern syntax does not yet fully support all of the functionality in PCRE regular expressions. I agree that they are so cryptic as to be "write only". $\endgroup$ – WReach Nov 21 '14 at 21:07
  • $\begingroup$ How does it do for JacobiCN and NDSolve? $\endgroup$ – J. M. will be back soon Jun 3 '15 at 22:24
4
$\begingroup$

Using insights from WReach and Aisamu here's a version with good properties on system names:

humpCharacters = 
  Flatten@{CharacterRange["A", "Z"], CharacterRange["0", "9"]};

.

StringSplit[systemNames, 
  c : humpCharacters ~~ 
    rest : Except[Append[humpCharacters, "$"]] .. :> c <> rest] // 
  Map[Select[# != "" &]]

From visual inspection of output (~5000 names) only the following issues are apparant:

(* "TeX" --> {"Te", "X", "Form"}, {"Te", "X", "Save"} 
   "$HTML", "$HTTP" --> {"$HTML", "Export", "Rules"}, {"$HTTP", "Cookies"}  *)
$\endgroup$
  • $\begingroup$ Guess I'll ask you too: how does it do for JacobiCN and NDSolve? $\endgroup$ – J. M. will be back soon Jun 3 '15 at 22:26
  • $\begingroup$ {"JacobiCN", "NDSolve"} // Map@camelHumpStringSplit --> {{"Jacobi", "CN"}, {"ND", "Solve"}} $\endgroup$ – alancalvitti Jun 3 '15 at 22:43
  • $\begingroup$ Ah, so it treats the ND as a single unit. Thanks. $\endgroup$ – J. M. will be back soon Jun 3 '15 at 22:48
  • $\begingroup$ Could be modified though. $\endgroup$ – alancalvitti Jun 3 '15 at 22:50
  • $\begingroup$ I suppose, but then it might screw up on AASTriangle; I guess the point I was trying to make is that with so many symbols these days, you're bound to have some inconsistencies. That reminds me: MittagLefflerE will be split in the expected way, even though the mathematician's surname is "Mittag-Leffler". :) $\endgroup$ – J. M. will be back soon Jun 3 '15 at 22:52

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.