4

I would like to match an integer array against the following pseudo patterns: where a means these numbers are the equal or 0 (i.e. assume any number "equals" 0)

[| a; a; a; a; a; |]          // matches [| 1; 1; 0; 0; 1 |]
[| a; a; a; not a; a; |]      // matches [| 3; 3; 0; 2; 3 |]
[| a; a; not a; a; a; |]      // matches [| 4; 4; 2; 0; 4 |]
[| a; not a; a; a; not a; |]  // matches [| 1; 2; 0; 0; 3 |]
[| a; a; a; not a; a; |]      // matches [| 1; 1; 0; 4; 1 |]
[| not a; a; a; a; a; |]      // matches [| 5; 1; 0; 1; 1 |]

How do I do this?

colinfang
  • 20,909
  • 19
  • 90
  • 173
  • I don't really get what `a` and what `not a` means here. Can you make that more clear? Does `a` mean "0 or 1" and `not a` mean "any other number"? – Chris Dec 14 '12 at 12:38
  • Ultimatemly chained if statements are probably fastest. Is there any real realson not to switch away from match statements? – John Palmer Dec 14 '12 at 20:22

3 Answers3

5

In your examples, the first element of the array is always a you can match on a boolean array:

match Array.map (fun x -> x = Array.get arr 0 || x = 0) arr with
| [| true; true; true; true; true; |] -> ... // matches [| 1; 1; 0; 0; 1 |]
| [| true; true; true; false; true; |] -> ... // matches [| 3; 3; 0; 2; 3 |]
| [| true; true; false; true; true; |] -> ... // matches [| 4; 4; 2; 0; 4 |]
| [| true; false; true; true; false; |] -> ... // matches [| 1; 2; 0; 0; 3 |]
| [| true; true; true; false; true; |] -> ... // matches [| 1; 1; 0; 4; 1 |]
| _ -> ...

This will create a temporary array, but it isn't a big deal with arrays of small sizes.

UPDATE:

If the first a is in another column, you simply project on that column and do another pattern matching (there are at most 5 matches for 5 columns).

match Array.map (fun x -> x = Array.get arr 1 || x = 0) arr with
| [| false; true; true; true; true; |] -> ... // matches [| 5; 1; 0; 1; 1 |]
| ...

That said, when the number and the complexity of pseudo-patterns grow, it's better to use functions instead of pattern matching.

pad
  • 41,040
  • 7
  • 92
  • 166
  • You could group pseudo-patterns by columns and do several `match`. But it seems you expect too much from pattern matching. When pseudo-patterns are too complex, it's time to create functions to recognize them. – pad Dec 14 '12 at 13:04
  • when i had another look, i figured out it was wrong (actually my example was wrong). As [| true; true; true; true; true; |] -> ... // matches [| 0; 1; 2; 3; 4 |] in your method – colinfang Dec 19 '12 at 13:09
2

You can define an active pattern that is parameterized by a pattern that you want to detect. In your example, you write these patterns using a pseudo-syntax with array containing either a or not a, but you could quite easily write them as actual arrays that contain the same value when you want to have matching values in the input. For example, something like:

[| 'a'; 'a'; 'b'; 'a'; 'a'; |]

It could even be a string as in @bytebuster's solution (you can convert string to char[] using the ToCharArray method). Then you can define active pattern:

let inline (|ArrayPattern|_|) pattern input =
  let matches =
    Array.zip pattern input
    |> Seq.groupBy fst 
    |> Seq.forall (fun (_, values) -> 
        let cases = values |> Seq.map snd |> set
        Set.count (cases - Set.singleton 0) <= 1)
  if matches then Some() else None  

I guess this is essentially the same as what @bytebuster implements. Here, I group the elements of the input array by their corresponding key in the pattern and then use set operations to check that there is at most one non-zero value for each group.

To use it, you can now match an int[] against ArrayPattern [| ... |] where the array parameter specifies the specific pattern you're looking for:

match [| 1; 1; 2; 0; 1 |] with 
| ArrayPattern [| 'a'; 'a'; 'b'; 'a'; 'a'; |] -> printfn "match"
| _ -> printfn "not"
Tomas Petricek
  • 240,744
  • 19
  • 378
  • 553
1

I think, it is just a computational task, so it can't look very beautiful. Nevertheless, here's my try. Note I used a string for "a" versus "not a" for better readability.

// Define possible return values
type TriState<'T> = | Yes of 'T | No of 'T | Unknown with 
    override this.ToString() =
        match this with
        | Unknown   -> "Unknown"
        | Yes value -> sprintf "Array matches, common value %A" value
        | No value  -> sprintf "Array mismatches, common value %A" value

// a boilerplate function
let matchA (pat: string) xs =
    let mustBeA, mustNotBeA =
        List.zip
            (xs |> Array.toList)
            (pat.ToCharArray() |> Array.toList)
        |> List.partition (snd >> ( (=) 'A'))
    // these values must be all equal
    let mustBeA' =
        mustBeA |> List.map fst |> List.filter ( (<>) 0)
    // these values must NOT be equal to the value taken from above
    let mustNotBeA' =
        mustNotBeA |> List.map fst
    match mustBeA' with
    | []            -> Unknown   // can't find the "must" value
                                 // due all "must" values are zero
    | mustValue::_  ->           // we have bootstrap value to compare against
        if (List.forall ( (=) mustValue) mustBeA')
            && (List.forall ( (<>) mustValue) mustNotBeA')
        then Yes mustValue
        else No mustValue

Now, testing:

[| 1; 2; 0; 0; 3 |] |> matchA "ABAAB" |> printf "%A\n" // Yes 1
[| 4; 4; 2; 0; 4 |] |> matchA "AABAA" |> printf "%A\n" // Yes 4
[| 5; 1; 0; 1; 1 |] |> matchA "BAAAA" |> printf "%A\n" // Yes 1

Of course, you can wrap it into a parameterized Active Pattern:

let (|MatchesA|_|) pattern arr =
    matchA pattern arr |> function | Yes x -> Some x | _ -> None

match [| 1; 2; 0; 0; 3 |] with
| MatchesA ("AAAAB") x -> sprintf "Pattern1 %d" x
| MatchesA ("ABAAB") x -> sprintf "Pattern2 %d" x
| _                    -> "Unknown"
|> printf "%A\n"
Be Brave Be Like Ukraine
  • 7,596
  • 3
  • 42
  • 66