# Match sequence of zero or more characters except a given character

This looks like it should be simple: I want to match an "i" followed by an "o" with an arbitrary (possibly zero-length) sequence of characters in between, unless that sequence also contains an "i". Without this constraint it's easy: ___~~"i"~~___~~"o"~~___. This will match "histology", but it will also match "adventitious". I've tried using a conditional or a predicate test in the pattern but I can't get the syntax right.

-
If there is "i" between them, then this second "i" and "o" will match Your pattern. You have to tell more precisely what do You want. Only one "i" in a string or maybe You do not want to happen "i"~~___~~"i". –  Kuba Jun 4 at 11:22
The pattern should be something like ___ ~~ "i" ~~ Longest[Except["i"] ...] ~~ "o" ~~ ___, but this doesn't work. Someone may correct me, but this may not be expressible as a regular expression (which is a requirement for string patterns). I spent some time a while ago looking for a way to match all except a certain string using regular expressions, eventually finding that it isn't possible. –  Oleksandr R. Jun 4 at 11:44
@OleksandrR. "i" ~~ ___?(! MemberQ[Characters[#], "r"] &) ~~ "o" may work sometimes. I could try to write more general pattern but there is no precise question here. By definition David's problem can not be solved in this form. –  Kuba Jun 4 at 12:00
@OleksandrR. I think the documentation said Longest is the default strategy, so Longest[Except["i"] ...] will not override the Longest on the leading ___, which will be matched first, so will eat any char including "i" until the last one before some "o". –  Silvia Jun 4 at 12:32
Are you looking to extract the text between "i" and "o", where there are no other "i"s present, or are looking to match an entire word? If the latter, should "adventitious" match, or not? Currently, it meets the conditions implied by your pattern. –  rcollyer Jun 4 at 14:12

As commented by Kuba, I'm not sure what is exactly you want, but you might want to try this way:

StringMatchQ[#,
Except["i"] ... ~~ "i" ~~ Except["i"] ... ~~ "o" ~~ ___


{True, False}

Or the regular expression version:

StringMatchQ[#,
RegularExpression["[^i]*i[^i]*o.*"]


{True, False}

Note:

Despite Longest is the default strategy for StringExpression, it seems working differently on List and on String.

• On List:

{a, b, c, d, e, f, g} /. {x__, Longest[y__], z__} :> {{x}, {y}, {z}}

{{a}, {b, c, d, e, f}, {g}}

{a, b, c, d, e, f, g} /. {x__, y__, z__} :> {{x}, {y}, {z}}

{{a}, {b}, {c, d, e, f, g}}

• On String:

StringCases["abcdefg",
x : __ ~~ Longest[y : __] ~~ z : __ :> {x, y, z}
]

{{"abcde", "f", "g"}}

StringCases["abcdefg",
x : __ ~~ y : __ ~~ z : __ :> {x, y, z}
]

{{"abcde", "f", "g"}}


So you'll have to indicate explicitly that the leading part should not contain "i" (so Except["i"] ... instead of ___) to enforce the middle part "i" ~~ Except["i"] ... ~~ "o" actually matching the longest pattern. I personally guess that is because string match is greedy and the leading part is matched before the middle part.

Supplement:

There is a great free tool for regex debugging, which can show the matching step by step. Also a useful online tester. (I learnt them from this post.)

-
It should be noted that RegularExpression["[^i]*i[^i]*o.*"] does match part of "adventitious" and using it with StringCases will return "tious". Of course, I interpret the OPs original pattern to match "i[^i]*o". –  rcollyer Jun 4 at 12:44
@rcollyer Yes it is. I think it because StringMatchQ compares the whole string with the pattern, while StringCases will compare all sub-string. I interpret what OP wants based on the statement: an "i" followed by an "o" with an arbitrary (possibly zero-length) sequence of characters in between, unless that sequence also contains an "i". –  Silvia Jun 4 at 12:50
@OleksandrR. Thanks. I think it is because the a~~b~~c pattern divides a whole string, e.g. "adventitious", into 3 parts, in which part b must NOT have "i", so even part a is shortest, it has to extend to include all "i"s before the part c. Adding a ___ between a and b will solve the problem. In regex form, it's "^(.*?i).*([^i]*)(o.*?)$" instead of "^(.*?i)([^i]*)(o.*?)$". –  Silvia Jun 4 at 17:53
@OleksandrR. There is a great free tool for regex debugging, which can show the matching step by step. Also a useful online tester. Learnt them from this post. –  Silvia Jun 4 at 17:57
@OleksandrR. A StringExpression version: StringCases[{"histology","adventitious","pious","bilious"},a:Shortest[StartOfSt‌​ring~~___~~"i"]~~b:Shortest[___]~~c:Except["i"]...~~d:Shortest["o"~~___~~EndOfString]:>‌​Sequence@@{a,b,c,d}]//Grid[#,Frame->All]&. –  Silvia Jun 4 at 18:03