# Splitting strings into letter keys and integers

I have strings of the following form:

string = "ABC123DEFG456HI89UZXX1";


Letter keys of variable lengths are followed by (positive) integers.

I want to get this transformation:

{{"ABC", 123}, {"DEFG", 456}, {"HI", 89}, {"UZXX", 1}}

I have written:

chars = Characters @ string;

runs = Length /@ Split[IntegerQ @ ToExpression @ # & /@ chars]


{3, 3, 4, 3, 2, 2, 4, 1}

takes = Transpose[{# - runs + 1, #}] & [Accumulate @ runs]


{{1, 3}, {4, 6}, {7, 10}, {11, 13}, {14, 15}, {16, 17}, {18, 21}, {22, 22}}

 result =
Partition[StringJoin /@ Map[Take[chars, #] &, takes], 2] /.
{a_String, b_String} :> {a, ToExpression @ b}


{{"ABC", 123}, {"DEFG", 456}, {"HI", 89}, {"UZXX", 1}}

I have two questions:

(1) How could the above coding be shortened / improved? (it seems to be too long for such a trivial problem)

(2) How would a "direct" method (StringCases , StringSplit ...) look like?

• Related: (58376) Sep 15, 2014 at 0:37

You asked for shortened, improved, so here it is using RegularExpressions:

StringCases[string, RegularExpression["(\\D+)(\\d+)"] :> {"$1", ToExpression["$2"]}]

{{"ABC", 123}, {"DEFG", 456}, {"HI", 89}, {"UZXX", 1}}


Here's a version using StringSplit:

Partition[StringSplit[string, RegularExpression["(\\d+)"] :> FromDigits @ "$1"], 2]  string = "ABC123DEFG456HI89UZXX1"; Partition[StringSplit[string, x : NumberString :> ToExpression@x], 2]  or Partition[StringSplit[string, x : NumberString :> FromDigits@x], 2]  or Split[StringSplit[string, x : NumberString :> FromDigits@x], Head@#2 === Integer &] (* {{"ABC",123},{"DEFG",456},{"HI",89},{"UZXX",1}} *)  • Somehow amazing that your short poem works. But inspecting it with Trace it became clearer to me :) – eldo Sep 14, 2014 at 20:05 With all the answers we need a timing comparison. Functions: bel[string_] := StringSplit[string, PatternSequence[x : Longest[LetterCharacter ..], y : Longest[DigitCharacter ..]] :> {x, ToExpression@y}][[;; ;; 2]]; RK1[string_] := StringCases[string, RegularExpression["(\\D+)(\\d+)"] :> {"$1", ToExpression["$2"]}]; RK2[string_] := Partition[StringSplit[string, RegularExpression["(\\d+)"] :> FromDigits@"$1"], 2];
ybk[string_] :=
Partition[#, 2] &@StringSplit[string, x : DigitCharacter .. :> ToExpression@x];
kg1[string_] := Partition[StringSplit[string, x : NumberString :> ToExpression@x], 2];
kg2[string_] := Partition[StringSplit[string, x : NumberString :> FromDigits@x], 2];
kuba[string_] :=
StringCases[string, x : LetterCharacter .. ~~ y : NumberString :> {x, ToExpression@y}];
al1[string_] := Module[{nu, le},
nu = ToExpression[StringCases[string, DigitCharacter ..]];
le = StringCases[string, LetterCharacter ..];
Transpose[{le, nu}]
];
al2[string_] := Module[{nu, le},
nu = ToExpression[StringSplit[string, __?LetterQ]];
le = StringSplit[string, __?DigitQ];
Transpose[{le, nu}]
];


Generator:

g = "a" <> RandomChoice[
Join @@ ConstantArray @@@ {{2, 10}, {1, 26}} ->
CharacterRange["0", "Z"]~Drop~{11, 17}, #] <> "1" &;


Benchmark Plot:

Needs["GeneralUtilities"]

BenchmarkPlot[{bel, RK1, RK2, ybk, kg1, kg2, kuba, al1, al2}, g, "IncludeFits" -> True]


(click for larger)

All methods have similar complexity except Algohi's second method which incurs a heavy penalty.

The winning method is kguler's second function, followed closely by RunnyKine's second function which is nearly the same thing except for the use of regular expressions.

• Could you please test my edited answer? (I've replaced RuleDelayed by Rule) Sep 15, 2014 at 6:53
• @belisarius Why did you revert my correction of your broken code? Sep 15, 2014 at 7:06
• Did I? I wasn't aware of it. Please forgive me Sep 15, 2014 at 10:33

StringSplit with Partition works fine for this particular case

Partition[#, 2] &@StringSplit[string, x : DigitCharacter .. :> ToExpression@x]
(* {{"ABC", 123}, {"DEFG", 456}, {"HI", 89}, {"UZXX", 1}} *)

StringCases[string,
x : LetterCharacter .. ~~ y : NumberString :> {x, ToExpression@y}]


Without Reg. Exp.:

string = "ABC123DEFG456HI89UZXX1";
StringSplit[string, PatternSequence[x : LetterCharacter .., y : DigitCharacter ..] :>
{x, ToExpression@y}][[;; ;; 2]]
(* {{"ABC", "123"}, {"DEFG", "456"}, {"HI", "89"}, {"UZXX", "1"}} *)

• @RunnyKine Yup. Thanks Sep 14, 2014 at 19:19
• You come to StringCases with these revisions :) Sep 14, 2014 at 19:21
• This code is currently broken due to the use of -> rather than :> in the rule. This means that ToExpression@y directly evaluates to y, therefore its existence is pointless and the output is in the wrong format. (Also x and y are not localized.) I corrected this but the edit was reverted. Sep 15, 2014 at 7:09
nu = ToExpression[StringCases[string, DigitCharacter ..]];
le = StringCases[string, LetterCharacter ..];
Transpose[{le, nu}]


using StringSplit:

nu = ToExpression[StringSplit[string, __?LetterQ]];
le = StringSplit[string, __?DigitQ];
Transpose[{le, nu}]
`