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$
  • $\begingroup$ Related: (7900), (14030), (59690) $\endgroup$ – Mr.Wizard Jan 29 '17 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 Jan 31 '17 at 7:53
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$
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$
  • 1
    $\begingroup$ Fails (?) when integer strings are adjacent to each other. Don't know, if {..., "2", "7", ...} -> {..., 27, ...} is a desirable outcome. $\endgroup$ – LLlAMnYP Jan 30 '17 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 Jan 31 '17 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$
  • $\begingroup$ This is a very slow method on longer lists. $\endgroup$ – Mr.Wizard Jan 29 '17 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$ – Martin Ender Jan 30 '17 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$
  • 2
    $\begingroup$ Try the input in the OP, your code doesn't seem to do what was requested. $\endgroup$ – Carl Woll Jan 30 '17 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 Jan 30 '17 at 17:42
  • $\begingroup$ @CarlWoll I think I got it right this time but if not let me know, yet again. $\endgroup$ – Mr.Wizard Jan 31 '17 at 8:24

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.