4
$\begingroup$

I have a list of strings of the form

list = {str1, str2, strx, str3, .., str10}

each string contains both letters and numbers, except for strx which does not contain numbers. What I want to do is attach strx to the element that immediately precedes it, producing

{str1, {str2, strx}, ..., str10}

I am using

 list /. 
   {bef___, PatternSequence[x_, m_] /; StringFreeQ[m, DigitCharacter], aft___} :> 
     {bef, {x, m}, aft}

However this seems to match only the first occurrence of the pattern I'm looking for. How can I make it match several cases? I also want to be able to process a list that does not contain any of the strx.

$\endgroup$
  • 2
    $\begingroup$ Can there be strxs in sequence, e.g. {...,strn,strx,strx,strn...}? Can there be a strx at the start of the list, and if so, what is to be done? You need to specify your problem much more precisely. $\endgroup$ – ciao Jul 27 '15 at 5:25
  • $\begingroup$ Good catch, no, the strx do not follow each other. $\endgroup$ – amrods Jul 27 '15 at 7:47
  • $\begingroup$ Well, actually i found out that there are some cases in which up to 3 strx follow a single stri. In that case, I would like to attach each of those strx to the stri that immediately precedes it: {str1, strx1, srtx2, strx3, str2} should result in {{str1, strx1, strx2, strx3}, str2} $\endgroup$ – amrods Jul 27 '15 at 20:25
  • $\begingroup$ Oh, and no, an strx will never be at the beginning. $\endgroup$ – amrods Jul 27 '15 at 20:27
  • 3
    $\begingroup$ It is seriously uncool to fundamentally change the objective, particularly after already answering a question about the change oppositely. $\endgroup$ – ciao Jul 27 '15 at 23:24
7
$\begingroup$

Here is one way:

lines={"123","312","anb","452","Xys"}

ReleaseHold[
 lines //. {bef___, 
    PatternSequence[x_String, 
     m_String?(StringFreeQ[#, DigitCharacter] &)], 
    aft___} :> {bef, {Hold[x], m}, aft}]

(* ==> {"123", {"312", "anb"}, {"452", "Xys"}} *)

I changed the pattern so that it tests the Head of x and m as well, and wrap x in Hold in the replacement. That way, when I then do ReplaceRepeated, the Hold will prevent an infinite recursion.

Edit: simplicity and speed

If you don't insist on using PatternSequence, I would strongly recommend doing it this way:

lines = {"123", "127", "312", "anb", "45h2", "Xys"}

Split[lines, StringFreeQ[#2, DigitCharacter] &] /. {x_} :> x

(* ==> {"123", "127", {"312", "anb"}, {"45h2", "Xys"}} *)

This uses the second argument of Split to test the subsequent element for every list member and checks that it satisfies the condition. Then it groups the two elements surrounding the position where this result changes into a list.

$\endgroup$
  • $\begingroup$ Interesting idea, but performance is.... interesting. Lists with no elements with no digits are done reasonably fast. Adding just one element with that condition clobbers performance, adding many....things get ugly... $\endgroup$ – ciao Jul 27 '15 at 6:23
  • 1
    $\begingroup$ @ciao Well, the question was about PatternSequence... $\endgroup$ – Jens Jul 27 '15 at 17:43
  • $\begingroup$ @ciao I added an alternative solution that is also "interesting". $\endgroup$ – Jens Jul 27 '15 at 18:30
  • $\begingroup$ Second is pretty sweet, appears to be second fastest so far. +1 on that. $\endgroup$ – ciao Jul 27 '15 at 23:19
  • $\begingroup$ @ciao Are you sure you want to delete your A? It can still teach readers a lot. Anyway, luckily my second approach still seems to work for the modified Q, too. $\endgroup$ – Jens Jul 28 '15 at 0:02
4
$\begingroup$

An approach using ReplaceRepeated and a temporary tag.

data = 
  {"DD1", "B12", "CCC", "3AD", "C2A", "3D1", "1A1", "C11", "BBA", 
   "322", "1D2", "B3C", "1BD", "CC1", "AC"};

list //. 
  {bef___, 
   PatternSequence[x_, m_] /; Head[m] =!= tag && StringFreeQ[m, DigitCharacter], 
   aft___} :> {bef, tag[x, m], aft} /. 
     tag -> List
{"DD1", {"B12", "CCC"}, "3AD", "C2A", "3D1", "1A1", {"C11", "BBA"}, 
 "322", "1D2", "B3C", "1BD", {"CC1", "AC"}}
$\endgroup$
  • $\begingroup$ You must have posted this after I left - it's similar to what I did, but nonetheless an independent solution (+1). $\endgroup$ – Jens Jul 27 '15 at 18:04
2
$\begingroup$

One option is to apply the rule over and over again until it doesn't have any effect:

list = {"DD1", "B12", "CCC", "3AD", "C2A", "3D1", "1A1", "C11", "BBA",
    "322", "1D2", "B3C", "1BD", "CC1", "AC"};

FixedPoint[Replace[#, {
     bef___,
     PatternSequence[x_, m_] /; StringFreeQ[m, DigitCharacter],
     aft___
     } :> {bef, {x, m}, aft}] &,
 list
 ]

(* Out: {"DD1", {"B12", "CCC"}, "3AD", "C2A", "3D1", "1A1", {"C11", 
  "BBA"}, "322", "1D2", "B3C", "1BD", {"CC1", "AC"}} *)

You may also consider this approach to solving your problem:

replacePair[{str1_, str2_}] := Module[{freeQ1, freeQ2},
  freeQ1 = StringFreeQ[str1, DigitCharacter];
  freeQ2 = StringFreeQ[str2, DigitCharacter];
  If[freeQ1, ## &[], If[freeQ2, {str1, str2}, str1]]
  ]
replacePair[{str_}] := If[StringFreeQ[str, DigitCharacter], ## &[], str]

Developer`PartitionMap[replacePair, list, 2, 1, {1, 1}, {}]

(* Out: {"DD1", {"B12", "CCC"}, "3AD", "C2A", "3D1", "1A1", {"C11", 
  "BBA"}, "322", "1D2", "B3C", "1BD", {"CC1", "AC"}} *)
$\endgroup$
  • $\begingroup$ Wouldn't //. be appropriate here? $\endgroup$ – J. M. will be back soon Jul 27 '15 at 5:52
  • $\begingroup$ @Guesswhoitis. Jens showed how to do that, the reason I used Replace instead is because it only works on the top level so I don't have to do the Hold/ReleaseHold trick. $\endgroup$ – C. E. Jul 27 '15 at 5:57

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.