3

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

例如一个非常简单的案例

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

4 回答 4

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;
}

我看不出有什么低效的。

于 2012-12-14T02:39:31.387 回答
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

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

于 2012-12-15T19:11:36.657 回答
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. Pattern Match 能够在结论 1 下找到数组分解的优化方法。
  3. 不完整的模式匹配总是抛出异常,因此添加通配符来捕获丢失的模式并显式抛出异常并没有什么坏处。

测试 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

    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 使 Pattern Match 产生奇怪的未优化代码。

测试 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. 多个案例活动模式编译为 FSharpChoice。
  2. 编译器能够检查活动模式的完整性/重复性,但是它不能将它们与正常模式进行比较。
  3. 未达到的模式不会被编译。

测试 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. 编译器有时会自动内联函数。

测试 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. 编译器无法检查部分活动模式的完整性/重复性。

测试 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. 如果 union 中的 case 超过 3 个,Pattern Match 将使用 Tag 属性而不是 is。(它也适用于多案例活动模式。)
  2. 通常,模式匹配会导致多个 is,这会极大地降低性能。
于 2013-01-03T12:59:58.383 回答
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
于 2012-12-15T13:22:02.897 回答