16

I have the following list, each element of which is of type string:

{"abc", "def","2","ghi","7"}

Is there an efficient way to arrive at:

{"abcdef", 2,"ghi",7}

where adjacent alphabetic elements are combined and string representations of integers are converted to Integer type?

ToExpression obtains the integers all right, but the alphabetic strings are converted to Symbols and the string manipulation functions can't be used.

I would be grateful for any suggestions.

Suite401
  • 4,793
  • 8
  • 18

6 Answers6

17

Since StringReplace works on lists, I would use:

List @@ StringExpression @@ StringReplace[
    {"abc","def","2","ghi","7"},
    n:NumberString :> ToExpression[n]
] //InputForm

{"abcdef", 2, "ghi", 7}

Carl Woll
  • 130,679
  • 6
  • 243
  • 355
13

Here is a benchmark of the existing answers.

make = RandomChoice[{"abc", "def", "2", "ghi", "7"}, #] &;

bob[lis_] := (lis /. n_?(NumericQ[ToExpression[#]] &) :> ToExpression[n]) //. {s___,
     str1_String, str2_String, f___} :> {s, str1 <> str2, f}

halirutan[lis_] :=
 Module[{isNumber},
  isNumber[s_String] := StringMatchQ[s, NumberString];
  If[isNumber[#], ToExpression[#], #] & /@ StringJoin @@@ SplitBy[lis, isNumber]
  ]

hubble[list_] :=
 Module[{res = StringJoin /@ Split[list, (LetterQ[#1] && LetterQ[#2]) &]},
  Table[Which[DigitQ[res[[i]]], ToExpression[res[[i]]], True, res[[i]]], {i, 1, 
    Length[res]}]
  ]

carl[lis_] := 
 List @@ StringExpression @@ StringReplace[lis, n : NumberString :> ToExpression[n]]

mrwiz2[lis_] := 
 List @@ StringReplace[
   StringRiffle[lis, "!"], {"!" -> "", d : DigitCharacter .. :> FromDigits[d]}]

Needs["GeneralUtilities`"]
BenchmarkPlot[{bob, halirutan, hubble, carl, mrwiz2}, make, 5]

enter image description here

Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371
11

If a string is a number can be tested by

isNumber[s_String] := StringMatchQ[s, NumberString]

and using a custom version of join that takes care if it is a list of numbers

join[s : {_?isNumber, ___}] := Sequence @@ ToExpression[s];
join[s_] := StringJoin[s];

you can apply SplitBy to collect all non-numbers in your list and convert the rest to integers:

join /@ SplitBy[{"abc", "def", "2", "ghi", "7", "8", "jkl"}, isNumber]
(* {"abcdef", 2, "ghi", 7, 8, "jkl"} *)
halirutan
  • 112,764
  • 7
  • 263
  • 474
  • 1
    Fails (?) when integer strings are adjacent to each other. Don't know, if {..., "2", "7", ...} -> {..., 27, ...} is a desirable outcome. – LLlAMnYP Jan 30 '17 at 10:07
  • @LLlAMnYP It wasn't in the customer's requirements :), but you know how well I can handle critique. I edit my post. – halirutan Jan 31 '17 at 02:40
8
lis = {"abc", "def", "2", "ghi", "7"};

(lis /. n_?(NumericQ[ToExpression[#]] &) :> 
    ToExpression[n]) //. {s___, str1_String, str2_String, f___} :> {s,
    str1 <> str2, f}

(*  {"abcdef", 2, "ghi", 7}  *)
Bob Hanlon
  • 157,611
  • 7
  • 77
  • 198
  • This is a very slow method on longer lists. – Mr.Wizard Jan 29 '17 at 07:44
  • 3
    It also fails if any of your strings happen to coincide with a variable name which has stored a numeric value. And it's generally dangerous if any of the strings represent an expression which causes some evaluation with side effects to happen (which is possible even if you know that all strings are only alphanumeric characters). – Martin Ender Jan 30 '17 at 08:46
8
list = {"abc", "def", "2", "ghi", "7"};

Select adjacent alphabetic elements and join them.

res = StringJoin /@ Split[list, (LetterQ[#1] && LetterQ[#2]) &]

(*{"abcdef", "2", "ghi", "7"}*)

Now apply ToExpression to digits only.

Table[Which[DigitQ[res[[i]]], ToExpression[res[[i]]], True,res[[i]]], {i, 1, Length[res]}]

(*{"abcdef", 2, "ghi", 7}*)
Hubble07
  • 3,614
  • 13
  • 23
5

The answer I posted before was broken; it did not merge strings "abc" and "def", etc.
Here is another attempt to get this right and offer some modicum of an advantage.

This is based on Carl Woll's StringReplace method. "!" is an arbitrary character that must not appear in any of the strings.

mrwiz2[lis_] := 
 List @@ StringReplace[
   StringRiffle[lis, "!"],
   {"!" -> "", d : DigitCharacter .. :> FromDigits[d]}
 ]

mrwiz2[{"abc", "def", "2", "ghi", "7"}]
{"abcdef", 2, "ghi", 7}     (* joined at last! *)

Benchmark to be updated momentarily.

Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371