16
$\begingroup$

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.

$\endgroup$
2
  • $\begingroup$ Related: (7900), (14030), (59690) $\endgroup$
    – Mr.Wizard
    Commented Jan 29, 2017 at 7:30
  • 1
    $\begingroup$ Sorry for the useless answer and false benchmark I posted earlier. For some reason I cannot seem to get it through my thick head that strings "abc" and "def" are supposed to be merged. Twice I tried to offer solutions that did not do this critical step. $\endgroup$
    – Mr.Wizard
    Commented Jan 31, 2017 at 7:53

6 Answers 6

17
$\begingroup$

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}

$\endgroup$
0
13
$\begingroup$

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

$\endgroup$
11
$\begingroup$

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"} *)
$\endgroup$
2
  • 1
    $\begingroup$ Fails (?) when integer strings are adjacent to each other. Don't know, if {..., "2", "7", ...} -> {..., 27, ...} is a desirable outcome. $\endgroup$
    – LLlAMnYP
    Commented Jan 30, 2017 at 10:07
  • $\begingroup$ @LLlAMnYP It wasn't in the customer's requirements :), but you know how well I can handle critique. I edit my post. $\endgroup$
    – halirutan
    Commented Jan 31, 2017 at 2:40
8
$\begingroup$
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}  *)
$\endgroup$
2
  • $\begingroup$ This is a very slow method on longer lists. $\endgroup$
    – Mr.Wizard
    Commented Jan 29, 2017 at 7:44
  • 3
    $\begingroup$ 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). $\endgroup$ Commented Jan 30, 2017 at 8:46
8
$\begingroup$
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}*)
$\endgroup$
5
$\begingroup$

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.

$\endgroup$
3
  • 2
    $\begingroup$ Try the input in the OP, your code doesn't seem to do what was requested. $\endgroup$
    – Carl Woll
    Commented Jan 30, 2017 at 16:46
  • $\begingroup$ @CarlWoll jiminy christmas! Why do I keep messing up this problem!? Sorry for the waste of everyone's time. $\endgroup$
    – Mr.Wizard
    Commented Jan 30, 2017 at 17:42
  • $\begingroup$ @CarlWoll I think I got it right this time but if not let me know, yet again. $\endgroup$
    – Mr.Wizard
    Commented Jan 31, 2017 at 8:24

Your Answer

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

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