How does negative character class subtraction work in .Net?

Viewed 79

According to the .Net documentation on Regex, one can use Character Class Subtractions, in the form of:

[base_group - [excluded_group]]

Both base_group and excluded_group can be positive and negative character groups.

However, see the following #fsharp code:

let pattern = "([^A-F-[C-D]])"

"ABCDEFG".ToCharArray()
|>  Array.iter(fun s -> printfn "%c - %b" s ((Regex.Match (string(s), pattern)).Success))

With the following output:

A - false
B - false
C - false
D - false
E - false
F - false
G - true

I was expecting true on "C" and "D".

The following pattern gives the same result: "([^A-F-[CD]])" (note I changed character range [C-D] into characterset [CD])

Am I doing this the right way?

I did a few extra tests, and this code:

let pattern = "([^A-F-[^KL]])"
"ABCDEFGHIJKLMNOP".ToCharArray()
|>  Array.iter(fun s -> printfn "%c - %b" s ((Regex.Match (string(s), pattern)).Success))

Gives this result:

A - false
B - false
C - false
D - false
E - false
F - false
G - false
H - false
I - false
J - false
K - true
L - true
M - false
N - false
O - false
P - false

Which suggests that I might gotten the interpretation incorrect. The spec says:

Character class subtraction yields a set of characters that is the result of excluding the characters in one character class from another character class.

So the root of confusion might be that I expected the subtraction operation to work during the construction of the character class, while it works on character class constructs.

With this interpretation, if I have a pattern like this: [^A-F-[C-D]] This does not mean that "C-D" should be subtracted from "A-F". It means that we match true on any character except "A-F" and we subtract a match on "C-D" - for which it already did not match. That's why it appears ineffective.

So [^A-F] is character class in its own right, it's atomic and a subtraction operation works on the resulting set of characters.

The second pattern demonstrates this interpretation even better: [^A-F-[^KL]] Read like this. We match true for anything except "A-F" and we subtract matches on anything except "KL". Simple set-subtraction and this result in "KL".

1 Answers

I was expecting true on "C" and "D".

Why? You defined a set of [CD] to be subtracted out or ignored.^1

I get the confusion in the wordy document referenced, frankly, it instead be called this:

Set Subtraction Negation Pattern

Which may better imparts that one is negating an existing pattern with a subset pattern to achieve a finer result.


A better example is this

var test = "1234567890";
var pattern = @"[\d-[57]]";

Which would match any number except 5 or 7. In the [ ] set, it contains a \d any digit, then the substraction set, says skip these items in this sub set of 5 and 7 Not six, we did not combine them with a - in our set.

The pattern I offered above is a character subtraction pattern [\d-[57]] which works on all digits except for 5 and 7. This comes in handy when one has a character class that nearly meets the needs; except that it contains one or a few characters that one wishes to exclude.


^1 Attaching the - as C-D (C through D) does nothing because there are no characters between the C and D; hence it is a set of two characters CD and not a series.

Related