模式匹配有多聪明?

时间:2012-12-14 01:47:50

标签: f# pattern-matching

我的程序大部分时间花在数组模式匹配上,我想知道是否应该重写该函数并丢弃自动模式匹配。

E.g。一个非常简单的案例

let categorize array =
    match array with
    | [|(1|2);(1|2);(1|2)|] -> 3
    | [|(1|2);(1|2);_|] -> 2
    | [|(1|2);_;_|] -> 1
    | _ -> 0

categorize [|2;1;3|]

在这种情况下,编译器是否应用最少量的比较,通过识别,例如第一种情况与第二种情况相同,但第三种情况除外。

实际上模式更复杂,预优化模式匹配可能比完全优化模式匹配花费更多时间。

4 个答案:

答案 0 :(得分:6)

直接来自反射器:

public static int categorize(int[] array)
{
    if ((array > null) && (array.Length == 3))
    {
        switch (array[0])
        {
            case 1:
                switch (array[1])
                {
                    case 1:
                        switch (array[2])
                        {
                            case 1:
                            case 2:
                                goto Label_005C;
                        }
                        goto Label_005A;

                    case 2:
                        switch (array[2])
                        {
                            case 1:
                            case 2:
                                goto Label_005C;
                        }
                        goto Label_005A;
                }
                goto Label_0042;

            case 2:
                switch (array[1])
                {
                    case 1:
                        switch (array[2])
                        {
                            case 1:
                            case 2:
                                goto Label_005C;
                        }
                        goto Label_005A;

                    case 2:
                        switch (array[2])
                        {
                            case 1:
                            case 2:
                                goto Label_005C;
                        }
                        goto Label_005A;
                }
                goto Label_0042;
        }
    }
    return 0;
Label_0042:
    return 1;
Label_005A:
    return 2;
Label_005C:
    return 3;
}

我认为没有任何低效率。

答案 1 :(得分:1)

您的问题中真正缺少的是实际的主题领域。换句话说,你的问题是非常通用的(通常,对于SO来说是好的),而对你的实际问题进行编码可能会以优雅的方式解决整个问题。

如果我按照目前的推断你的问题,你只需要第一个元素的索引既不是1也不是2,而且实现很简单:

let categorize arr =
    try
        Array.findIndex (fun x -> not(x = 1 || x = 2)) arr
    with
        | :? System.Collections.Generic.KeyNotFoundException -> Array.length arr

// Usage
let x1 = categorize [|2;1;3|]    // returns 2
let x2 = categorize [|4;2;1;3|]  // returns 0
let x3 = categorize [|1;2;1|]    // returns 3

作为一些免费的好处,您可以获得与数组长度无关并且绝对可读的代码 这是你需要的吗?

答案 2 :(得分:0)

你可以写:

let f (xs: _ []) =
  if xs.Length=3 then
    let p n = n=1 || n=2
    if p xs.[0] then
      if p xs.[1] then
        if p xs.[2] then 3
      else 2
    else 1
  else 0

答案 3 :(得分:0)

测试1

    F#
        let test1 x =
            match x with
            | [| 1; 2; 3 |] -> A
            | [| 1; 2; _ |] -> A
            | [| 1; _; _ |] -> A
    Decompiled C#
        if (x != null && x.Length == 3)
        {
            switch (x[0])
            {
            case 1:
                switch (x[1])
                {
                case 2:
                    switch (x[2])
                    {
                    case 3:
                        return Program.MyType.A;
                    default:
                        return Program.MyType.A;
                    }
                    break;
                default:
                    return Program.MyType.A;
                }
                break;
            }
        }
        throw new MatchFailureException(...);
    Decompiled IL
        Code size 107

结论

  1. 模式匹配不会根据 - >。
  2. 之后的值进行优化
  3. 模式匹配能够在结论1下找到优化的数组分解方法。
  4. 不完整模式匹配总是抛出异常,因此添加通配符来捕获缺失的模式并明确抛出异常是没有害处的。
  5. 测试2

        F#
            let test2 x =
                match x with
                | [| 1; 2; 3 |] -> A
                | [| _; 2; 3 |] -> B
                | [| _; _; 3 |] -> C
        Decompiled C#
            if (x != null && x.Length == 3)
            {
                switch (x[0])
                {
                case 1:
                    switch (x[1])
                    {
                    case 2:
                        switch (x[2])
                        {
                        case 3:
                            return Program.MyType.A;
                        default:
                            goto IL_49;
                        }
                        break;
                    default:
                        switch (x[2])
                        {
                        case 3:
                            break;
                        default:
                            goto IL_49;
                        }
                        break;
                    }
                    break;
                default:
                    switch (x[1])
                    {
                    case 2:
                        switch (x[2])
                        {
                        case 3:
                            return Program.MyType.B;
                        default:
                            goto IL_49;
                        }
                        break;
                    default:
                        switch (x[2])
                        {
                        case 3:
                            goto IL_58;
                        }
                        goto IL_49;
                    }
                    break;
                }
                IL_58:
                return Program.MyType.C;
            }
            IL_49:
            throw new MatchFailureException(...);
        Decompiled IL
            Code size 185
    

    结论

    1. 模式匹配检查从数组开头到结尾的值。因此无法找到优化的方法。
    2. 代码大小是最佳代码的2倍。
    3. 测试3

          F#
              let test3 x =
                  match x with
                  | [| 1; 2; 3 |] -> A
                  | [| 1; 2; a |] when a <> 3 -> B
                  | [| 1; 2; _ |] -> C
          Decompiled C#
              if (x != null && x.Length == 3)
              {
                  switch (x[0])
                  {
                  case 1:
                      switch (x[1])
                      {
                      case 2:
                          switch (x[2])
                          {
                          case 3:
                              return Program.MyType.A;
                          default:
                              if (x[2] != 3)
                              {
                                  int a = x[2];
                                  return Program.MyType.B;
                              }
                              break;
                          }
                          break;
                      }
                      break;
                  }
              }
              if (x != null && x.Length == 3)
              {
                  switch (x[0])
                  {
                  case 1:
                      switch (x[1])
                      {
                      case 2:
                          return Program.MyType.C;
                      }
                      break;
                  }
              }
              throw new MatchFailureException(...);
      

      结论

      1. 编译器不够智能,无法通过Guard查看完整性/重复性。
      2. Guard使模式匹配产生奇怪的未经优化的代码。
      3. 测试4

            F#
                let (| Is3 | IsNot3 |) x =
                   if x = 3 then Is3 else IsNot3
                let test4 x =
                    match x with
                    | [| 1; 2; 3      |] -> A
                    | [| 1; 2; Is3    |] -> B
                    | [| 1; 2; IsNot3 |] -> C
                    | [| 1; 2; _      |] -> D // This rule will never be matched.
            Decompiled C#
                if (x != null && x.Length == 3)
                {
                    switch (x[0])
                    {
                    case 1:
                        switch (x[1])
                        {
                        case 2:
                            switch (x[2])
                            {
                            case 3:
                                return Program.MyType.A;
                            default:
                            {
                                FSharpChoice<Unit, Unit> fSharpChoice = Program.|Is3|IsNot3|(x[2]);
                                if (fSharpChoice is FSharpChoice<Unit, Unit>.Choice2Of2)
                                {
                                    return Program.MyType.C;
                                }
                                return Program.MyType.B;
                            }
                            }
                            break;
                        }
                        break;
                    }
                }
                throw new MatchFailureException(...);
        

        结论

        1. 多个案例Active Patterns编译为FSharpChoice。
        2. 编译器能够检查活动模式的完整性/重复性,但无法将它们与正常模式进行比较。
        3. 未编译未到达的模式。
        4. 测试5

              F#
                  let (| Equal3 |) x =
                     if x = 3 then Equal3 1 else Equal3 0 // Equivalent to "then 1 else 0"
                  let test5 x =
                      match x with
                      | [| 1; 2; 3        |] -> A
                      | [| 1; 2; Equal3 0 |] -> B
                      | [| 1; 2; Equal3 1 |] -> C
                      | [| 1; 2; _        |] -> D
              Decompiled C#
                  if (x != null && x.Length == 3)
                  {
                      switch (x[0])
                      {
                      case 1:
                          switch (x[1])
                          {
                          case 2:
                              switch (x[2])
                              {
                              case 3:
                                  return Program.MyType.A;
                              default:
                              {
                                  int num = x[2];
                                  switch ((num != 3) ? 0 : 1)
                                  {
                                  case 0:
                                      return Program.MyType.B;
                                  case 1:
                                      return Program.MyType.C;
                                  default:
                                      return Program.MyType.D;
                                  }
                                  break;
                              }
                              }
                              break;
                          }
                          break;
                      }
                  }
                  throw new MatchFailureException(...);
          

          结论

          1. 单例活动模式编译为返回类型。
          2. 编译器有时会自动内联函数。
          3. 测试6

                F#
                    let (| Partial3 | _ |) x =
                       if x = 3 then Some (Partial3 true) else None // Equivalent to "then Some true"
                    let test6 x =
                        match x with
                        | [| 1; 2; 3 |] -> A
                        | [| 1; 2; Partial3 true |] -> B
                        | [| 1; 2; Partial3 true |] -> C
                Decompiled C#
                    if (x != null && x.Length == 3)
                    {
                        switch (x[0])
                        {
                        case 1:
                            switch (x[1])
                            {
                            case 2:
                                switch (x[2])
                                {
                                case 3:
                                    return Program.MyType.A;
                                default:
                                {
                                    FSharpOption<bool> fSharpOption = Program.|Partial3|_|(x[2]);
                                    if (fSharpOption != null && fSharpOption.Value)
                                    {
                                        return Program.MyType.B;
                                    }
                                    break;
                                }
                                }
                                break;
                            }
                            break;
                        }
                    }
                    if (x != null && x.Length == 3)
                    {
                        switch (x[0])
                        {
                        case 1:
                            switch (x[1])
                            {
                            case 2:
                            {
                                FSharpOption<bool> fSharpOption = Program.|Partial3|_|(x[2]);
                                if (fSharpOption != null && fSharpOption.Value)
                                {
                                    return Program.MyType.C;
                                }
                                break;
                            }
                            }
                            break;
                        }
                    }
                    throw new MatchFailureException(...);
            

            结论

            1. 部分活动模式编译为FSharpOption。
            2. 编译器无法检查部分活动模式的完整性/重复性。
            3. 测试7

                  F#
                      type MyOne =
                          | AA
                          | BB of int
                          | CC
                      type MyAnother =
                          | AAA
                          | BBB of int
                          | CCC
                          | DDD
                      let test7a x =
                          match x with
                          | AA -> 2
                      let test7b x =
                          match x with
                          | AAA -> 2
                  Decompiled C#
                      public static int test7a(Program.MyOne x)
                      {
                          if (x is Program.MyOne._AA)
                          {
                              return 2;
                          }
                          throw new MatchFailureException(...);
                      }
                      public static int test7b(Program.MyAnother x)
                      {
                          if (x.Tag == 0)
                          {
                              return 2;
                          }
                          throw new MatchFailureException(...);
                      }
              

              结论

              1. 如果联合中有超过3个案例,模式匹配将使用Tag属性而不是is。 (它也适用于多种情况下的活动模式。)
              2. 模式匹配通常会导致多重性能大大降低性能。