9
$\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$
0

3 Answers 3

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$
8
  • $\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, 2014 at 13:46
  • $\begingroup$ Ohh, you are correct! I knew the "just 2 dots" would come back to bite me. $\endgroup$
    – Aisamu
    Nov 20, 2014 at 14:10
  • $\begingroup$ I give up, @WReach! Couldn't do it elegantly... I better learn myself some regex magic. $\endgroup$
    – Aisamu
    Nov 20, 2014 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$ Nov 21, 2014 at 18:12
  • $\begingroup$ There you go, @alancalvitti! $\endgroup$
    – Aisamu
    Nov 21, 2014 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$
5
  • 1
    $\begingroup$ Everyday you teach me something about regexes! $\endgroup$
    – Aisamu
    Nov 20, 2014 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$ Nov 21, 2014 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$ Nov 21, 2014 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, 2014 at 21:07
  • $\begingroup$ How does it do for JacobiCN and NDSolve? $\endgroup$ Jun 3, 2015 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$
6
  • $\begingroup$ Guess I'll ask you too: how does it do for JacobiCN and NDSolve? $\endgroup$ Jun 3, 2015 at 22:26
  • $\begingroup$ {"JacobiCN", "NDSolve"} // Map@camelHumpStringSplit --> {{"Jacobi", "CN"}, {"ND", "Solve"}} $\endgroup$ Jun 3, 2015 at 22:43
  • $\begingroup$ Ah, so it treats the ND as a single unit. Thanks. $\endgroup$ Jun 3, 2015 at 22:48
  • $\begingroup$ Could be modified though. $\endgroup$ Jun 3, 2015 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$ Jun 3, 2015 at 22:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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