13
$\begingroup$

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?

$\endgroup$
1
  • $\begingroup$ Related: (58376) $\endgroup$
    – Mr.Wizard
    Sep 15, 2014 at 0:37

7 Answers 7

15
$\begingroup$

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]
$\endgroup$
7
$\begingroup$
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}} *)
$\endgroup$
1
  • 2
    $\begingroup$ Somehow amazing that your short poem works. But inspecting it with Trace it became clearer to me :) $\endgroup$
    – eldo
    Sep 14, 2014 at 20:05
6
$\begingroup$

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}} *)
$\endgroup$
6
$\begingroup$

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]

enter image description here
(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.

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

Without Reg. Exp.:

string = "ABC123DEFG456HI89UZXX1";
StringSplit[string, PatternSequence[x : LetterCharacter .., y : DigitCharacter ..] :>
                                                   {x, ToExpression@y}][[;; ;; 2]]
(* {{"ABC", "123"}, {"DEFG", "456"}, {"HI", "89"}, {"UZXX", "1"}} *)
$\endgroup$
3
  • 1
    $\begingroup$ @RunnyKine Yup. Thanks $\endgroup$ Sep 14, 2014 at 19:19
  • 1
    $\begingroup$ You come to StringCases with these revisions :) $\endgroup$
    – ybeltukov
    Sep 14, 2014 at 19:21
  • $\begingroup$ 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. $\endgroup$
    – Mr.Wizard
    Sep 15, 2014 at 7:09
5
$\begingroup$
StringCases[string, 
            x : LetterCharacter .. ~~ y : NumberString :> {x, ToExpression@y}]
$\endgroup$
4
$\begingroup$
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}]
$\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.