# Elegant and fast way to join all string sequences

I want to do what title says:

{"a","b",1,2,3,4,"a",2,2,2,"edg","?"} => {"ab",1,2,3,4,"a",2,2,2,"edg?"}

in the most elegant way. For example one replacement rule for ReplaceAll.

I have manage to do this:

test = {"a", "a", 1, 2, "A", "b", 123, "a", "end"}
SplitBy[test, Head] /. {x__String} :> StringJoin[{x}] // Flatten


{"aa", 1, 2, "Ab", 123, "aend"}

But I find this way inelegant. Could You help me with creating good pattern?

Edit

Bonus request: It should be comparable in duration.

Also there is not need to do this via .

Conclusion:

There are many great answers. Each one is educational and worth an upvote. I should now accept one, the problem is I have not stated this question's goal so clearly, my fault.

• rm -rf's is the fastes and the shortest however,
• I decided to accept Jacob's answer as the fastest one and because it seems it has not focused attention as it deserves.
• Yves Klett answer fulfill my need of a single pattern solution.

As Mr. Wizard pointed out one could think I should accept rm- rf's answer. One could also think Yves Klett should receive it as his answer fits my primordial need. I will abuse a vague form of this question to give them a bounty insted.

-
So I guess procdural stuff would not be to your liking? – Yves Klett Jun 27 '13 at 9:20
@YvesKlett Let say Pattern matching is not a must. I will restate the question in edit but I will leave first version so Your answer will fit it. – Kuba Jun 27 '13 at 9:25
I just posted an answer using SplitBy before noticing that you had mentioned it in the question. Could you explain why you find it inelegant? This really is not a job for ReplaceAll (and cannot be done in one call)... – R. M. Jun 27 '13 at 9:25
But it is indeed quite clean, IMO (my solution used StringQ, which I prefer for clarity of intent, rather than Head) and should be fast... – R. M. Jun 27 '13 at 9:33
@rm-rf Without posting this question I couldn't be sure if it is elegant enough. But now it seems it is. – Kuba Jun 27 '13 at 9:35

R.M's answer below is faster and way shorter than this one

The main idea of this answer is to avoid testing if elements of the list are strings, but rather to infer which elements are the strings, to get a speed increase. Here is the function

joinStringsInList[list_] :=
Module[
{splitList = SplitBy[list, StringQ], strs, others, len, bool},
bool = StringQ[First[test]];
len = Length[splitList];

{strs, others} =
If[
bool,
{StringJoin /@ splitList[[1 ;; len ;; 2]],
splitList[[2 ;; len ;; 2]]},
{StringJoin /@ splitList[[2 ;; len ;; 2]],
splitList[[1 ;; len ;; 2]]}
];
Flatten[Riffle[strs, others], 1]
]


Example:

test = {"a", "a", {1, 1}, bool, testSplit, 2, "A", "b", 123, "a",
"end"}

joinStringsInList[test]


{"aa", {1, 1}, bool, testSplit, 2, "Ab", 123, "aend"}

-
You have not misunderstood my dissatisfaction :) Be patient, I will look at Your answer in the moment. – Kuba Jun 27 '13 at 10:11
+1, I had been discouraged by the many lines... – gpap Jul 2 '13 at 16:28
@Kuba yikes, an accept. I'll have to improve my answer then :P. I'd say the the StringExpression solution of rm-rf is best, especially if he makes an error message around it using FreeQ. I'm glad you like it, anyway :) – Jacob Akkerboom Jul 3 '13 at 8:41
Do not feel sorry. I've done this :) – Kuba Jul 23 '13 at 22:15
@JacobAkkerboom I was referring to this: "P.S.: Sorry for pushing the other answers down". :) – Kuba Jul 23 '13 at 22:26

Ok, since you dismissed SplitBy, which IMO is quite clean, and you wanted other's ideas, here's an unconventional solution that relies on a side-effect of how StringJoin works :)

list = {"a", "b", 1, 2, 3, 4, "a", 2, 2, 2, "edg", "?"};
List @@ Quiet@StringJoin@list
(* {"ab", 1, 2, 3, 4, "a", 2, 2, 2, "edg?"} *)


I wouldn't recommend this for production code. Stick with your SplitBy solution (or a faster alternative answer).

A cleaner version using StringExpression (thanks to Rojo):

List @@ StringExpression @@ list


which probably works very similar to Simon's answer because of the Flat and OneIdentity attribute of StringExpression.

-
Funny...I came up with exactly the same code before seeing yours. You don't need Apply for StringJoin, by the way. +1. – Leonid Shifrin Jun 27 '13 at 10:31
Quick... and dirty. +1 – Yves Klett Jun 27 '13 at 10:48
+1. Really cool! – gpap Jun 27 '13 at 10:54
You could use StringExpression instead of StringJoin and avoid Quiet and make it faster – Rojo Jun 27 '13 at 21:09
@Rojo StringExpression has the advantage of preserving lists e.g. List @@ StringExpression @@ {"a", "b", 1, 2, {3, 4}, "a", 2} which to me makes the solution a lot more usable. Both are beautiful. – Mr.Wizard Jul 2 '13 at 17:10

I think this satisfies all the requirements, except for elegance and speed:

SetAttributes[f, {Flat, OneIdentity}]
f[a_String, b_String] := a <> b

test = {"a", "a", 1, 2, "A", "b", 123, "a", "end"};
List @@ f @@ test

(* {"aa", 1, 2, "Ab", 123, "aend"} *)

-
Ahh, I was just typing this but I was missing OneIdentity. Rojo showed me something similar once. I suppose this is how rm-rfs answer works. – Jacob Akkerboom Jun 27 '13 at 11:48
@JacobAkkerboom it looks like rm-rf's but is this one so slow for You too? – Kuba Jun 27 '13 at 12:04
@Kuba This must be very slow. There may be other examples where something like this can be efficient, but if you do not know the implementation details of Flat and OneIdentity, then you have to get very lucky to get an efficient algorithm. To see a little bit what happens, try: SetAttributes[g, {Flat, OneIdentity}]; g[3] := f[3]; g[1, 2] := f[1, 2] g[2, 3] := f[2, 3]; Trace[g[1, 2, 3]] // Column – Jacob Akkerboom Jun 27 '13 at 12:31
I've been looking at the Trace of this, and that is the most bizarre thing I've seen in a while. I learned something, +1. Oh, and I think it meets "elegant"; speed, however ... – rcollyer Jun 27 '13 at 13:27

If we are going for least efficient here, I think I may have a winner (using Fold):

ffs[any___, a_String, b_String] := Sequence[any, StringJoin[a, b]];
ffs[any___] := Sequence[any];


with

list = {"a", "b", 1, 2, 3, 4, "a", 2, 2, 2, "edg", "?"};

Rest@{Fold[ffs, 1, list]}
(* {"ab", 1, 2, 3, 4, "a", 2, 2, 2, "edg?"} *)


The "1" being auxiliary hence dropped.

--EDIT--

Actually amending a little bit makes this quite efficient:

 ffs2[any___] := {any}
ffs2 /: ffs2[{any___, a_String}, b_String] := ffs2[any, StringJoin[a, b]];


and

Rest@Flatten@Fold[ffs2, 1, list]
(* {"ab", 1, 2, 3, 4, "a", 2, 2, 2, "edg?"}*)


and it can deal with longer lists a little better than the one originally proposed by Kuba:

longlist = NestList[PadLeft, list, 100000] // Flatten;
AbsoluteTiming[mine = Rest@Flatten@Fold[ffs2, 1, longlist];]
AbsoluteTiming[original = SplitBy[longlist, Head] /. {x__String} :> StringJoin[{x}] //Flatten;]
mine == original

(* {1.370464, Null} *)
(* {1.556161, Null} *)
(* True *)

-
yes, you're right, I had written this before rm-rf replaced ~~ instead of <> (which made it really fast - why do you say it's slow?). – gpap Jul 2 '13 at 16:16
:) we are splitting hair here. rm-rf's is, like, faster by a factor of 20 – gpap Jul 2 '13 at 16:26
wow, I did not realize that one was so fast. Well, I guess our answers can still be useful because of the remarks on unstability on rm-rfs answer. +1 for you. – Jacob Akkerboom Jul 2 '13 at 16:32
@JacobAkkerboom I didn't say it was unstable... just that I probably wouldn't use it in production code, because it relied on a side effect and I personally like to write clean code where the intent is clear. The second one, using StringExpression seems to be even faster and also the expected (albeit uncommon) behaviour, although I probably wouldn't be able to guess what it's doing from a quick glance. – R. M. Jul 2 '13 at 16:49
@rm-rf sorry if that was a poor choice of words. I guess I was thinking also about the case where a StringExpression occurs in the list, as Rojo remarked. I guess that can be tested with FreeQ very quickly (I think Mathematica keeps track of all symbols that occur in an expression anyway). – Jacob Akkerboom Jul 2 '13 at 17:53

test = {"a", "a", 1, 2, "A", "b", 123, "a", "end"};

test //. {a___, b_String, c_String, d___} :> {a, b <> c, d}


or

test //. {a___, b : Longest[_String ..], c___} :> {a, StringJoin@b, c}

{"aa", 1, 2, "Ab", 123, "aend"}

-
Thank You. It does fit my needs. +1. I would love to see something without ReplaceRepeted. This could be slow sometimes. – Kuba Jun 27 '13 at 9:08
@Kuba yes, it will not be efficient. Your solution will probably perform much better for larger lists. – Yves Klett Jun 27 '13 at 9:13
I was searching through all the answers just for this one... – VF1 Jul 4 '13 at 16:55
@VF1 are you pulling my leg? – Yves Klett Jul 4 '13 at 17:03
Haha, no. I just developed an affinity for ReplaceRepeated as a terribly inefficient but very elegant solution whenever it's used since I read Jon McLoone's 10 tips – VF1 Jul 4 '13 at 17:07

A semi-pattern approach...

list = {"a","b",1,2,3,4,"a",2,2,2,"edg","?"};
Apply[Join, SplitBy[list, Head] /. {x__String} :> {StringJoin[x]}]

{ab,1,2,3,4,a,2,2,2,edg?}


The StringJoin[x] is in a list so the Apply[Join... will only strip the top-level lists SplitBy introduces. This allows elements of list to be Lists as well.

list = {"a", "b", 1, 2, {3}, 4, "a", 2, 2, 2, "edg", "?"};

{ab,1,2,{3},4,a,2,2,2,edg?}


{3} is still a List.

-

Just for fun, a different way:

test={"a","b",1,2,3,4,"a",2,2,2,"edg","x"};
d=StringSplit[FixedPoint[StringReplace[#,RegularExpression["(\\D+),(\\D+)"]:>"$1"<>"$2" ]&,ToString@Row[test,","]],","];
d/.x_String /; StringMatchQ[x, NumberString] :> ToExpression[x]

(* {"ab", 1, 2, 3, 4, "a", 2, 2, 2, "edgx"} *)

-
This is incorrect. You've now converted everything to a String! – R. M. Jul 5 '13 at 1:38
The edit still won't work for, say, {"a", "b", 1, "1.23"} – R. M. Jul 5 '13 at 5:25