26
$\begingroup$

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.

$\endgroup$
11
  • $\begingroup$ So I guess procdural stuff would not be to your liking? $\endgroup$
    – Yves Klett
    Jun 27, 2013 at 9:20
  • $\begingroup$ @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. $\endgroup$
    – Kuba
    Jun 27, 2013 at 9:25
  • 1
    $\begingroup$ 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... $\endgroup$
    – rm -rf
    Jun 27, 2013 at 9:33
  • 2
    $\begingroup$ @rm-rf Without posting this question I couldn't be sure if it is elegant enough. But now it seems it is. $\endgroup$
    – Kuba
    Jun 27, 2013 at 9:35
  • 1
    $\begingroup$ Why didn't you accept R.M's answer? It's so much better! At least on my system List @@ StringExpression @@ list is ten times faster than SplitBy alone. Did you try it? $\endgroup$
    – Mr.Wizard
    Jul 3, 2013 at 8:33

7 Answers 7

4
$\begingroup$

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"}

$\endgroup$
5
  • $\begingroup$ You have not misunderstood my dissatisfaction :) Be patient, I will look at Your answer in the moment. $\endgroup$
    – Kuba
    Jun 27, 2013 at 10:11
  • $\begingroup$ +1, I had been discouraged by the many lines... $\endgroup$
    – gpap
    Jul 2, 2013 at 16:28
  • $\begingroup$ @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 :) $\endgroup$ Jul 3, 2013 at 8:41
  • $\begingroup$ Do not feel sorry. I've done this :) $\endgroup$
    – Kuba
    Jul 23, 2013 at 22:15
  • $\begingroup$ @JacobAkkerboom I was referring to this: "P.S.: Sorry for pushing the other answers down". :) $\endgroup$
    – Kuba
    Jul 23, 2013 at 22:26
23
+100
$\begingroup$

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.

$\endgroup$
6
  • $\begingroup$ Abuse... I like it! :) SplitBy is faster but definitely thanks for that $\endgroup$
    – Kuba
    Jun 27, 2013 at 9:45
  • 4
    $\begingroup$ Funny...I came up with exactly the same code before seeing yours. You don't need Apply for StringJoin, by the way. +1. $\endgroup$ Jun 27, 2013 at 10:31
  • 3
    $\begingroup$ Quick... and dirty. +1 $\endgroup$
    – Yves Klett
    Jun 27, 2013 at 10:48
  • 7
    $\begingroup$ You could use StringExpression instead of StringJoin and avoid Quiet and make it faster $\endgroup$
    – Rojo
    Jun 27, 2013 at 21:09
  • 3
    $\begingroup$ @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. $\endgroup$
    – Mr.Wizard
    Jul 2, 2013 at 17:10
14
$\begingroup$

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"} *)
$\endgroup$
4
  • 1
    $\begingroup$ 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. $\endgroup$ Jun 27, 2013 at 11:48
  • $\begingroup$ @JacobAkkerboom it looks like rm-rf's but is this one so slow for You too? $\endgroup$
    – Kuba
    Jun 27, 2013 at 12:04
  • 2
    $\begingroup$ @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 $\endgroup$ Jun 27, 2013 at 12:31
  • 3
    $\begingroup$ 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 ... $\endgroup$
    – rcollyer
    Jun 27, 2013 at 13:27
7
$\begingroup$

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 *)
$\endgroup$
9
  • $\begingroup$ 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?). $\endgroup$
    – gpap
    Jul 2, 2013 at 16:16
  • 1
    $\begingroup$ :) we are splitting hair here. rm-rf's is, like, faster by a factor of 20 $\endgroup$
    – gpap
    Jul 2, 2013 at 16:26
  • $\begingroup$ 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. $\endgroup$ Jul 2, 2013 at 16:32
  • 1
    $\begingroup$ @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. $\endgroup$
    – rm -rf
    Jul 2, 2013 at 16:49
  • $\begingroup$ @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). $\endgroup$ Jul 2, 2013 at 17:53
6
$\begingroup$

How about this (bad performance duly noted):

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"}
$\endgroup$
5
  • 1
    $\begingroup$ Thank You. It does fit my needs. +1. I would love to see something without ReplaceRepeted. This could be slow sometimes. $\endgroup$
    – Kuba
    Jun 27, 2013 at 9:08
  • $\begingroup$ @Kuba yes, it will not be efficient. Your solution will probably perform much better for larger lists. $\endgroup$
    – Yves Klett
    Jun 27, 2013 at 9:13
  • $\begingroup$ I was searching through all the answers just for this one... $\endgroup$
    – VF1
    Jul 4, 2013 at 16:55
  • $\begingroup$ @VF1 are you pulling my leg? $\endgroup$
    – Yves Klett
    Jul 4, 2013 at 17:03
  • 1
    $\begingroup$ 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 $\endgroup$
    – VF1
    Jul 4, 2013 at 17:07
2
$\begingroup$

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", "?"};
Apply[Join,SplitBy[list, Head]/.{x__String}:>{StringJoin[x]}]
{ab,1,2,{3},4,a,2,2,2,edg?}

{3} is still a List.

$\endgroup$
-1
$\begingroup$

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"} *)
$\endgroup$
2
  • $\begingroup$ This is incorrect. You've now converted everything to a String! $\endgroup$
    – rm -rf
    Jul 5, 2013 at 1:38
  • 1
    $\begingroup$ The edit still won't work for, say, {"a", "b", 1, "1.23"} $\endgroup$
    – rm -rf
    Jul 5, 2013 at 5:25

Your Answer

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

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