7
$\begingroup$

Straight from AnyOrder documentation

StringCases["abc123 456def", 
   AnyOrder[LetterCharacter .., DigitCharacter ..]
]
(* {"abc123", "456def"} *)

But if we name the parameters only the explicitly written ordering matches (e.g letters then digits)!

StringCases["abc123 456def",  
    a : AnyOrder[l : LetterCharacter .., d : DigitCharacter ..] :> {a, {l, d}}
]
(* {{"abc123", {"abc", "123"}}, {"456def", {"", ""}}} *)

Is this the expected behavior? Am I missing something? This was tested on OS X 10.10.5, Mathematica 10.3.0

$\endgroup$
3
  • 3
    $\begingroup$ I can confirm this for 10.3 on Win 7 Pro 64 bit. It seems like a bug. I'll raise the ante by suggesting you try StringCases["abc123 456def 789ghi", a : AnyOrder[d : DigitCharacter .., l : LetterCharacter ..] :> {a, {d, l}}]. You will notice that it only replaces the named patterns in AnyOrder when their order in the string matches the order of the patterns as listed in AnyOrder. $\endgroup$
    – Edmund
    Mar 14, 2016 at 18:58
  • 2
    $\begingroup$ Same for 10.4, Win 10. $\endgroup$ Mar 14, 2016 at 19:23
  • $\begingroup$ @Edmund Yes! That's what I had meant with "only the typed ordering matches!" I'll reword it a bit $\endgroup$
    – Aisamu
    Mar 14, 2016 at 20:07

1 Answer 1

5
$\begingroup$

tl;dr: The behavior of AnyOrder observed by the OP might be related to the way StringExpression handles named patterns. A work around with SequencesCases and OrderlessPatternSequence can be used in principle for simple string patterns.


Analysis

It may be the case that AnyOrder is translated into a form of Alternatives and StringExpression (not necessarily at top-level) where the names of the patterns are lost. This can be seen by noticing that the second output can be obtained with

(* Input 1 *)

StringCases["abc123 456def", 
            a : ( (l : LetterCharacter .. ~~ d : DigitCharacter ..) | 
                  (DigitCharacter .. ~~ LetterCharacter ..)
                ) :> {a, {l, d}}
]

(* {{"abc123", {"abc", "123"}}, {"456def", {"", ""}}} *)

The alternative, where the pattern names are kept in the second argument of Alternatives, makes StringExpression throw warnings for the first evaluation and, more importantly, change the resulting expression:

(* Input 2 *)

StringCases["abc123 456def", 
            a : ( (l : LetterCharacter .. ~~ d : DigitCharacter ..) | 
                  (d : DigitCharacter .. ~~ l : LetterCharacter ..)
                ) :> {a, {l, d}}
]

StringExpression::cond: Warning: restrictions on pattern variable d in d:(DigitCharacter..) 
 are ignored as they are not associated with the first occurrence of d.

StringExpression::cond: Warning: restrictions on pattern variable l in l:(LetterCharacter..) 
 are ignored as they are not associated with the first occurrence of l.

(* {{"abc123", {"abc", "123"}}} *)

So the issue may boil down to the way StringExpression handles pattern, provided of course that the initial assumption about a translation was correct. This would explain why the pattern names are lost for another order of the given string patterns.

StringExpression warnings

The warnings seen above happen with simpler inputs where no Alternatives is involved:

(* Input 3 *)

StringCases["aa", a : LetterCharacter ~~ a : LetterCharacter :> a]

StringExpression::cond: Warning: restrictions on pattern variable a in a:LetterCharacter 
are ignored as they are not associated with the first occurrence of a.

(* {"a"} *)

The second pattern restriction, here LetterCharacter, is ignored in the matching, which means that changing it to different pattern object, such as DigitCharacter, will still work:

(* Input 4 *)

StringCases["aa", a : LetterCharacter ~~ a : DigitCharacter :> a]

StringExpression::cond: Warning: restrictions on pattern variable a in a:DigitCharacter 
are ignored as they are not associated with the first occurrence of a.

(* {"a"} *)

We can therefore assume that Inputs 3 and 4 are viewed for the evaluation process as

StringCases["aa", a : LetterCharacter ~~ a_ :> a]

(* {"a"} *)

Applying this assumption to Input 2, we can understand why we obtained the output {{"abc123", {"abc", "123"}}}

StringCases["abc123 456def", 
            a : ( (l : LetterCharacter .. ~~ d : DigitCharacter ..) | 
                  (d_ ~~ l_)) :> {a, {l, d}}
]

(* {{"abc123", {"abc", "123"}}} *)

Further remarks and possible work around

It has to be noted that when using the following counterpart of Input 2,

SequenceCases[Characters["abc123 456def"], 
               a : ( {l : _?LetterQ .., d : _?DigitQ ..} | 
                     {d : _?DigitQ .., l : _?LetterQ ..}
                   ) :> {StringJoin[a], {StringJoin[l], StringJoin[d]}}
]

(* {{"abc123", {"abc", "123"}}, {"456def", {"def", "456"}}} *)

we obtain the expected output. Re-expressing this input by means of OrderlessPatternSequence, which brings the expression closer to OP's second input, also yields the same output

SequenceCases[Characters["abc123 456def"], 
       a : {OrderlessPatternSequence[l : _?LetterQ .., d : _?DigitQ ..]
           } :> {StringJoin[a], {StringJoin[l], StringJoin[d]}}
]

(* {{"abc123", {"abc", "123"}}, {"456def", {"def", "456"}}} *)

This seems to imply that OrderlessPatternSequence translates to Alternatives (not necessarily at top-level), which makes the initial assumption about AnyOrder plausible. So in the end, the behavior you mentioned might indeed be related to the way StringExpression handles patterns.

As a last remark, using OrderlessPatternSequence in the way above could be a workaround. But this is unfortunately viable to some extent only, since translating other string pattern specifications, such as for instance PunctuationCharacter, WordBoundary and Whitespace, even though perhaps feasible, complicates the picture.

$\endgroup$

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.