8
$\begingroup$

When I name a pattern I'm working with, that name stands for the same particular expression wherever it shows up. That's a basic precept of how the Wolfram Language's pattern-matching works. But if I want to keep the generality of some pattern, how can I avoid naming it when applying a condition? That's a little hard to articulate, so let me give a specific example.

endsWithSpaceQ[str_] := StringEndsQ[str, " "];
str1 = "abc efg 17";
str2 = "abc abc 17";

I want to test str1 and str2 to see if they match the pattern

Repeat[substring-that-satisfies-endsWithSpaceQ, {2}] ~~ "17"

It's natural to start with:

StringMatchQ[#, 
   Repeated[p__ /; endsWithSpaceQ[p], {2}] ~~ "17"] & /@ {str1, str2}

However, this returns {False, True}, because since I named the pattern p__, Mathematica looks for two occurrences of a particular string that satisfy my condition. Instead, I want two substrings, not necessarily identical, that satisfy the condition. ? allows me to use pure functions so I can avoid the necessity for /; f[] to take an argument (i.e. the name of my pattern), but ? does not test an entire substring [sequence for non-string patterns] but rather its individual characters [elements of the sequence].

Is there any way I can use Condition without forcing myself to name the pattern? If not, I'm looking specifically for a way to require the anonymous pattern inside Repeated[__] to satisfy a condition.

(Help with the title is encouraged -- I can't find anything to sum this question up well)

Edit: clarification Of course, my real-life task is a little more complicated than this example. I'm hoping for a general solution or solution-strategy, because I think it'd be good to have on record here. If one isn't out there, I might post another question with my specific problem (assuming I can't hack it together myself).

$\endgroup$
5
  • 1
    $\begingroup$ Related: (18337), (55274), (86056) $\endgroup$
    – Mr.Wizard
    Commented Jul 26, 2015 at 15:04
  • 1
    $\begingroup$ Thanks for the crossrefs. Two of them I didn't find when searching, which probably means I should brush up on my searching here. $\endgroup$ Commented Jul 26, 2015 at 15:33
  • 6
    $\begingroup$ Searching is difficult which is why as a community service I make these Related: comments as often as possible. $\endgroup$
    – Mr.Wizard
    Commented Jul 26, 2015 at 15:34
  • $\begingroup$ @Mr.Wizard: Thanks for the great job you are doing on this forum! (sorry for ignoring the no thank you rule ;)) $\endgroup$
    – Wizard
    Commented Jul 26, 2015 at 20:31
  • $\begingroup$ I have yet to see or conceive a universally applicable solution to this issue. Nevertheless I hope you will post your specific problem so that it can at least serve as an example. $\endgroup$
    – Mr.Wizard
    Commented Jul 26, 2015 at 23:59

2 Answers 2

4
$\begingroup$

I don't know of any direct solution to this problem. I have always had to work around it, but usually I find that isn't too hard. In this case, as I imagine you know, you could just write:

StringMatchQ[#, Repeated[__ ~~ " ", {2}] ~~ "17"] & /@ {str1, str2}
{True, True}

Further this is far more efficient as it is run in an optimized string library, rather than being done by brute force.

You could of course use multiple patterns, generated programmatically if need be:

StringMatchQ[#, (p1__ /; endsWithSpaceQ[p1]) ~~ (p2__ /; endsWithSpaceQ[p2]) ~~ 
    "17"] & /@ {str1, str2}
$\endgroup$
3
  • $\begingroup$ Unlike in my toy example, the real pattern I'm trying to match repeats an arbitrary number of times, making the prospect of generating patterns programmatically a little scarier. I'll play with it a bit, but seeing that a Wizard doesn't know a general solution to what seems to be a pretty elementary problem suggests that one might not exist and I should start thinking about hacking around the specific case. $\endgroup$ Commented Jul 26, 2015 at 15:41
  • 1
    $\begingroup$ @hYPotenuser Believe me I understand your frustration; this (to the best of my knowledge) is one of those peculiar missing pieces you think should be there but isn't. If you give a more representative example I might be able to give another recommendation. Also consider the number of forms that have to be tried; it may be better to abandon pattern matching and parse the string yourself, e.g. StringSplit or whatever, then work from there. $\endgroup$
    – Mr.Wizard
    Commented Jul 26, 2015 at 15:47
  • 2
    $\begingroup$ Programmatically you could substitute in Unique: StringMatchQ[#, StringExpression @@ With[{each = p__ /; endsWithSpaceQ@p}, Table[each /. p -> Unique[], {2}]] ~~ "17"] & /@ {str1, str2} $\endgroup$ Commented Jul 26, 2015 at 18:51
4
$\begingroup$

Okay, I think here's a way you can do virtually everything you want.

First, my original answer, which I had then moved to a comment to Mr.Wizard's answer.

You can substitute in Unique:

StringMatchQ[#, StringExpression @@ With[{each = p__ /; endsWithSpaceQ@p}, Table[each /. p -> Unique[], {2}]] ~~ "17"] & /@ {str1, str2}

(* {True, True} *)

This gives you what you want for your specific case.

But, you may want to have an arbitrarily long repeated sequence. Meaning, you don't know if it will repeat exactly 2 times or 3 times. The above doesn't do that.

Well, now, I don't know how to do it completely arbitrarily, but if you're willing to choose some maximum, then the following seems to work:

With[{each = p__ /; endsWithSpaceQ[p]}, StringMatchQ[#, Table[StringExpression @@ Table[each /. p -> Unique[], {i}], {i, 10}] ~~ "17"] & /@ {str1, str2}]

(replace the "10" in the last iterator with whatever your maximum is)

The way it works is to first create the sequence of patterns programmatically, as Mr.Wizard suggested. For that, it replicates the "p" pattern a certain i number of times and replaces it with a unique symbol each time it appears. Then, the outer table iterates i up through the maximum. A list of StringExpression's evaluates as an alternative, so as long as one of those patterns matches, it goes on.

Here's a way to see that it is really working. Let's change your function a little bit:

endsWithSpaceQn[str_, lenprespace_: 3] := StringEndsQ[str, " "] && StringLength@str == lenprespace + 1

now we are looking for strings that end with a space and are of length 3 (not counting the space)

Running this:

With[{each = p__ /; endsWithSpaceQn[p, 3]}, StringMatchQ[#, Table[StringExpression @@ Table[each /. p -> Unique[], {i}], {i, 10}] ~~ "17"] & /@ {str1, str2}]

with any maximum greater than 1 in the final iterator (where the 10 is), results in the correct answer:

(* {True, True} *)

But if you replace the 10 with a 1 then you'll see it comes back as:

(* {False, False} *)

(if you had used the original endsWithSpaceQ function here, it would still come back true, because it would match eg "abc abc ")

$\endgroup$
2
  • $\begingroup$ I've been busy with other things and so haven't had a chance to dig in here, but rest assured your answer hasn't been forgotten! $\endgroup$ Commented Jul 29, 2015 at 1:14
  • $\begingroup$ be sure to consider Mr.Wizard's important point about (in)efficiency and the number of forms this kind of approach implicitly checks: mathematica.stackexchange.com/questions/89160/… $\endgroup$ Commented Jul 29, 2015 at 4:35

Your Answer

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

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