12
$\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$
14
$\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$
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$
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$
  • 2
    $\begingroup$ Somehow amazing that your short poem works. But inspecting it with Trace it became clearer to me :) $\endgroup$ – eldo Sep 14 '14 at 20:05
5
$\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$
  • $\begingroup$ Could you please test my edited answer? (I've replaced RuleDelayed by Rule) $\endgroup$ – Dr. belisarius Sep 15 '14 at 6:53
  • $\begingroup$ @belisarius Why did you revert my correction of your broken code? $\endgroup$ – Mr.Wizard Sep 15 '14 at 7:06
  • $\begingroup$ Did I? I wasn't aware of it. Please forgive me $\endgroup$ – Dr. belisarius Sep 15 '14 at 10:33
5
$\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$
  • 1
    $\begingroup$ @RunnyKine Yup. Thanks $\endgroup$ – Dr. belisarius Sep 14 '14 at 19:19
  • 1
    $\begingroup$ You come to StringCases with these revisions :) $\endgroup$ – ybeltukov Sep 14 '14 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 '14 at 7:09
4
$\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.