2 ^ 2 ^…^ 2の括弧の可能な数値結果の数


19

演算子を使用した式2^2^...^2を考えます。演算子は、べき乗を意味します(「累乗」)。デフォルトの結合性がないため、式を完全に括弧で囲んで明確にする必要があります。式を括弧で囲む方法の数は、カタロニア語の数値で指定されますn^^ C_n=(2n)!/(n+1)!/n!

たとえば(2^2)^(2^2)=((2^2)^2)^2、異なる括弧で同じ数値結果が得られる場合があります。そのため、特定の数値の異なる数値結果の可能性は、all nよりも少なくなりC_nますn>11, 1, 2, 4, 8, ...カタロニア語番号とは対照的に、シーケンスは開始します1, 2, 5, 14, 42, ...

問題はn、入力として受け入れ2^2^...^2n演算子を使用した式のさまざまな数値結果の数を返す最速のプログラム(または関数)を記述すること^です。パフォーマンスが大きくなってもパフォーマンスが大幅に低下nすることはないため、高出力タワーを直接計算するのはおそらく悪い考えです。


私はここでの考え方を共有していますが、答えは常に形式のものであろうと、排他的に加算と乗算を使用することが可能であるべきと思われ2^n、ゆえ以外のものを追跡する必要だろうn。つまり、べき乗の規則を使用するだけで賢明です。ただし、これを行うには、よりスマートで完全に代数的な方法が確実にあります。
Fors

@Fors nまだ計算するには大きすぎると思います。それでも、よく知られています。「1または2 ^(...)または(...)+(...)」という形式の再帰的な表現かもしれません。しかし、そのような数値の表現を正規化する方法(または値が等しいかどうか2つの表現を比較する方法)の問題はまだあります。
ジョン・ドヴォルザーク

4
@ JanDvorak、A002845(非公開フォームなし)
ピーターテイラー


1
@Vladimir Reshetnikov:あなたの式には1つずれたエラーがあると思います。あなたがn2つありC_n=(2n)!/(n+1)!/n!、括弧の数にする必要がある場合、n = 3の場合、5でなければなりません正しいですか?私が見る(2^2)^22^(2^2)、他の3つの組み合わせは何ですか?C_nはn + 1の2つの括弧の数を与えると思います。
マーティントーマ

回答:


9

Python 2.7

このアプローチでは、次の考慮事項を活用します。

任意の整数は、2の累乗の合計として表すことができます。2のべき乗の指数も2のべき乗として表すことができます。例えば:

8 = 2^3 = 2^(2^1 + 2^0) = 2^(2^(2^0) + 2^0)

最終的にこれらの式は、セットのセットとして表すことができます(Pythonでは、組み込みのを使用しましたfrozenset)。

  • 0空のセットになり{}ます。
  • 2^aを表すセットを含むセットになりaます。例:1 = 2^0 -> {{}}および2 = 2^(2^0) -> {{{}}}
  • a+b代表組の連結となるab。例えば、3 = 2^(2^0) + 2^0 -> {{{}},{}}

2^2^...^2数値が大きすぎて整数として保存できない場合でも、フォームの式を一意のセット表現に簡単に変換できることがわかります。


の場合n=20、これは8.7秒で実行されます私のマシンのCPython 2.7.5でます(Python 3では少し遅く、PyPyでははるかに遅くなります)。

"""Analyze the expressions given by parenthesizations of 2^2^...^2.

Set representation:  s is a set of sets which represents an integer n.  n is
  given by the sum of all 2^m for the numbers m represented by the sets
  contained in s.  The empty set stands for the value 0.  Each number has
  exactly one set representation.

  In Python, frozensets are used for set representation.

  Definition in Python code:
      def numeric_value(s):
          n = sum(2**numeric_value(t) for t in s)
          return n"""

import itertools


def single_arg_memoize(func):
    """Fast memoization decorator for a function taking a single argument.

    The metadata of <func> is *not* preserved."""

    class Cache(dict):
        def __missing__(self, key):
            self[key] = result = func(key)
            return result
    return Cache().__getitem__


def count_results(num_exponentiations):
    """Return the number of results given by parenthesizations of 2^2^...^2."""
    return len(get_results(num_exponentiations))

@single_arg_memoize
def get_results(num_exponentiations):
    """Return a set of all results given by parenthesizations of 2^2^...^2.

    <num_exponentiations> is the number of exponentiation operators in the
    parenthesized expressions.

    The result of each parenthesized expression is given as a set.  The
    expression evaluates to 2^(2^n), where n is the number represented by the
    given set in set representation."""

    # The result of the expression "2" (0 exponentiations) is represented by
    # the empty set, since 2 = 2^(2^0).
    if num_exponentiations == 0:
        return {frozenset()}

    # Split the expression 2^2^...^2 at each of the first half of
    # exponentiation operators and parenthesize each side of the expession.
    split_points = xrange(num_exponentiations)
    splits = itertools.izip(split_points, reversed(split_points))
    splits_half = ((left_part, right_part) for left_part, right_part in splits
                                           if left_part <= right_part)

    results = set()
    results_add = results.add
    for left_part, right_part in splits_half:
        for left in get_results(left_part):
            for right in get_results(right_part):
                results_add(exponentiate(left, right))
                results_add(exponentiate(right, left))
    return results


def exponentiate(base, exponent):
    """Return the result of the exponentiation of <operands>.

    <operands> is a tuple of <base> and <exponent>.  The operators are each
    given as the set representation of n, where 2^(2^n) is the value the
    operator stands for.

    The return value is the set representation of r, where 2^(2^r) is the
    result of the exponentiation."""

    # Where b is the number represented by <base>, e is the number represented
    # by <exponent> and r is the number represented by the return value:
    #   2^(2^r) = (2^(2^b)) ^ (2^(2^e))
    #   2^(2^r) = 2^(2^b * 2^(2^e))
    #   2^(2^r) = 2^(2^(b + 2^e))
    #   r = b + 2^e

    # If <exponent> is not in <base>, insert it to arrive at the set with the
    # value: b + 2^e.  If <exponent> is already in <base>, take it out,
    # increment e by 1 and repeat from the start to eventually arrive at:
    #   b - 2^e + 2^(e+1) =
    #   b + 2^e
    while exponent in base:
        base -= {exponent}
        exponent = successor(exponent)
    return base | {exponent}

@single_arg_memoize
def successor(value):
    """Return the successor of <value> in set representation."""
    # Call exponentiate() with <value> as base and the empty set as exponent to
    # get the set representing (n being the number represented by <value>):
    #   n + 2^0
    #   n + 1
    return exponentiate(value, frozenset())


def main():
    import timeit
    print timeit.timeit(lambda: count_results(20), number=1)
    for i in xrange(21):
        print '{:.<2}..{:.>9}'.format(i, count_results(i))

if __name__ == '__main__':
    main()

(メモ化デコレータのコンセプトは、 http://code.activestate.com/recipes/578231-probably-the-fastest-memoization-decorator-in-the-/ます。)

出力:

8.667753234
0...........1
1...........1
2...........1
3...........2
4...........4
5...........8
6..........17
[...]
19.....688366
20....1619087

異なるタイミングn

 n    time
16    0.240
17    0.592
18    1.426
19    3.559
20    8.668
21   21.402

どれか n私のマシン上のメモリエラーに上記の21件の結果。

誰かがそれを別の言語に翻訳することでこれをより速くできるかどうかに興味があります。

編集:get_results機能を最適化しました。また、2.7.2ではなくPython 2.7.5を使用すると、実行速度が少し速くなりました。


C#変換を行いましたが、並べ替えられた配列を使用し、セットにはチェックが含まれているのではなく、順番に追加を行っています。それはずっと遅いです、そして、それが後継者機能を覚えていないことに起因するのか、比較のコストに起因するのかを見るために私はまだプロファイルしていません。
ピーターテイラー

1
私は@flornquakeの(素晴らしい)コードのプロファイルを作成していませんが、ユビキタスハッシュテーブルとハッシュキーを使用して、Pythonでかなり適切に最適化されたセットメンバーシップテストとセット操作操作の実行にCPU時間の多くが費やされていると思いますルーチン。メモ化は確かに大きなことであり、このような指数アルゴリズムを使用します。省略した場合、パフォーマンスが指数関数的に低下することが予想されます。
トビア

@Tobia、実際には、C#で後継関数をメモすると速度が遅くなることがわかりました。また、(集合演算を使用した)よりリテラルな翻訳は、低レベルの追加よりも大幅に遅いこともわかりました。元のコードに対して私が見つけた唯一の本当の改善は(a^b)^c = (a^c)^b、考慮に入れることでしたが、このPython実装よりもずっと遅いです。
ピーターテイラー

@PeterTaylor:編集:私の知る限り、flornquakeのアルゴリズムは、ツリーがツリー自体のセットであるなど、ツリーのセットの構築に依存しています。最小の空のセットから最大のセットまで、これらのツリーのすべての部分がメモされます。これは、これらすべてのツリーに、「CPUによって」1回だけ計算され、(RAMに)1回保存される「繰り返し構造」が含まれることを意味します。「順序の追加」アルゴリズムは、この繰り返された構造のすべてを識別し、それを1回計算しますか?(上記の指数関数的複雑度と呼ぶもの)en.wikipedia.org/wiki/Dynamic_programming
Tobia

@Tobia、重複しました。コードを投稿しました。
ピーターテイラー

5

C#

これは、flornquakeのPythonコードを低レベルの追加ルーチンを使用してC#に変換したもので、直接変換よりも中程度の高速化を実現します。これは私が持っている最も最適化されたバージョンではありませんが、値だけでなくツリー構造も保存する必要があるため、かなり長くなります。

using System;
using System.Collections.Generic;
using System.Linq;

namespace Sandbox {
    class PowerTowers {
        public static void Main() {
            DateTime start = DateTime.UtcNow;
            for (int i = 0; i < 17; i++)
                Console.WriteLine("{2}: {0} (in {1})", Results(i).Count, DateTime.UtcNow - start, i);
        }

        private static IList<HashSet<Number>> _MemoisedResults;

        static HashSet<Number> Results(int numExponentations) {
            if (_MemoisedResults == null) {
                _MemoisedResults = new List<HashSet<Number>>();
                _MemoisedResults.Add(new HashSet<Number>(new Number[] { Number.Zero }));
            }

            if (numExponentations < _MemoisedResults.Count) return _MemoisedResults[numExponentations];

            HashSet<Number> rv = new HashSet<Number>();
            for (int i = 0; i < numExponentations; i++) {
                IEnumerable<Number> rhs = Results(numExponentations - 1 - i);
                foreach (var b in Results(i))
                    foreach (var e in rhs) {
                        if (!e.Equals(Number.One)) rv.Add(b.Add(e.Exp2()));
                    }
            }
            _MemoisedResults.Add(rv);
            return rv;
        }
    }

    // Immutable
    struct Number : IComparable<Number> {
        public static Number Zero = new Number(new Number[0]);
        public static Number One = new Number(Zero);

        // Ascending order
        private readonly Number[] _Children;
        private readonly int _Depth;
        private readonly int _HashCode;

        private Number(params Number[] children) {
            _Children = children;
            _Depth = children.Length == 0 ? 0 : 1 + children[children.Length - 1]._Depth;

            int hashCode = 0;
            foreach (var n in _Children) hashCode = hashCode * 37 + n.GetHashCode() + 1;
            _HashCode = hashCode;
        }

        public Number Add(Number n) {
            // "Standard" bitwise adder built from full adder.
            // Work forwards because children are in ascending order.
            int off1 = 0, off2 = 0;
            IList<Number> result = new List<Number>();
            Number? carry = default(Number?);

            while (true) {
                if (!carry.HasValue) {
                    // Simple case
                    if (off1 < _Children.Length) {
                        if (off2 < n._Children.Length) {
                            int cmp = _Children[off1].CompareTo(n._Children[off2]);
                            if (cmp < 0) result.Add(_Children[off1++]);
                            else if (cmp == 0) {
                                carry = _Children[off1++].Add(One);
                                off2++;
                            }
                            else result.Add(n._Children[off2++]);
                        }
                        else result.Add(_Children[off1++]);
                    }
                    else if (off2 < n._Children.Length) result.Add(n._Children[off2++]);
                    else return new Number(result.ToArray()); // nothing left to add
                }
                else {
                    // carry is the (possibly joint) smallest value
                    int matches = 0;
                    if (off1 < _Children.Length && carry.Value.Equals(_Children[off1])) {
                        matches++;
                        off1++;
                    }
                    if (off2 < n._Children.Length && carry.Value.Equals(n._Children[off2])) {
                        matches++;
                        off2++;
                    }

                    if ((matches & 1) == 0) result.Add(carry.Value);
                    carry = matches == 0 ? default(Number?) : carry.Value.Add(One);
                }
            }
        }

        public Number Exp2() {
            return new Number(this);
        }

        public int CompareTo(Number other) {
            if (_Depth != other._Depth) return _Depth.CompareTo(other._Depth);

            // Work backwards because children are in ascending order
            int off1 = _Children.Length - 1, off2 = other._Children.Length - 1;
            while (off1 >= 0 && off2 >= 0) {
                int cmp = _Children[off1--].CompareTo(other._Children[off2--]);
                if (cmp != 0) return cmp;
            }

            return off1.CompareTo(off2);
        }

        public override bool Equals(object obj) {
            if (!(obj is Number)) return false;

            Number n = (Number)obj;
            if (n._HashCode != _HashCode || n._Depth != _Depth || n._Children.Length != _Children.Length) return false;
            for (int i = 0; i < _Children.Length; i++) {
                if (!_Children[i].Equals(n._Children[i])) return false;
            }

            return true;
        }

        public override int GetHashCode() {
            return _HashCode;
        }
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.