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|]

この場合、コンパイラは、たとえば最初のケースが3番目の要素を除いて2番目のケースと同じであることを認識することにより、最小量の比較を適用しますか。

実際には、パターンはより複雑であり、事前に最適化されたパターンマッチングは、完全に最適化されたパターンマッチングよりもはるかに多くの時間を費やす可能性があります。

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

あなたは書くことができます:

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 に答える
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. パターンマッチは、結論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は、PatternMatchに最適化されていない奇妙なコードを生成させます。

テスト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. ユニオンに3つ以上のケースがある場合、パターンマッチはisの代わりにTagプロパティを使用します。(複数のケースのアクティブパターンにも適用されます。)
  2. 多くの場合、パターンマッチは複数の結果になり、パフォーマンスが大幅に低下します。
于 2013-01-03T12:59:58.383 に答える