Parantezlerin olası sayısal sonuçlarının sayısı 2 ^ 2 ^… ^ 2


19

Bir ifade düşünün 2^2^...^2ile noperatörler ^. Operatör ^üs anlamına gelir ("gücüne"). Varsayılan bir varsayımının olmadığını varsayalım, bu nedenle ifadenin açık olması için ifadenin tamamen parantez içinde olması gerekir. İfadeyi parantez içine almanın yol sayısı Katalan sayıları ile verilir C_n=(2n)!/(n+1)!/n!.

Bazen farklı parenthesizations örneğin, aynı sayısal sonucunu vermek (2^2)^(2^2)=((2^2)^2)^2Verilen için farklı olası sayısal sonuçların sayısı böylece, ndaha az olan C_nherkes için n>1. Dizi 1, 1, 2, 4, 8, ..., Katalan sayılarının aksine başlar1, 2, 5, 14, 42, ...

Sorun kabul hızlı programı (veya fonksiyonu) yazmak için nbir girdi olarak ve farklı ifade olası sayısal sonuçları sayısını verir 2^2^...^2ile noperatör ^. Performans nbüyüdükçe önemli ölçüde bozulmamalıdır , bu nedenle yüksek güç kulelerinin doğrudan hesaplanması muhtemelen kötü bir fikirdir.


Ben sadece burada bir fikir paylaşıyorum, ancak cevap her zaman formda olacağından, toplama ve çarpma işlemlerini kullanmak mümkün olmalı 2^nve bu nedenle dışında herhangi bir şeyi takip etmek gereksiz olacaktır n. Yani, sadece üs alma kurallarını kullanmak akıllıca görünüyor. Ancak, bunu yapmanın kesinlikle daha akıllı ve tamamen cebirsel bir yolu vardır.
Fors

Sanırım @Fors nolduğunu hala bilgi işlem için çok büyüktü. Yine de dikkat çekti. Belki de "1 veya 2 ^ (...) veya (...) + (...)" biçiminde özyinelemeli gösterim; ancak yine de bir sayının bu şekilde gösterimini nasıl normalleştireceğiniz (veya değer eşitliği için iki gösterimi nasıl karşılaştıracağınız) sorununuz var.
John Dvorak

4
@JanDvorak, A002845 (kapalı form verilmedi)
Peter Taylor


1
@Vladimir Reshetnikov: Formülünüzde bire bir hata olduğunu düşünüyorum. Eğer varsa nikişer ikişer ve C_n=(2n)!/(n+1)!/n!ardından parenthesizations sayısı olmalıdır n = 3 için doğru 5 olmalıdır? Anlıyorum (2^2)^2ve 2^(2^2)ancak diğer üç kombinasyonlar nelerdir? Bence C_n n + 1 ikişer ay için parantezlerin sayısını veriyor
Martin Thoma

Yanıtlar:


9

Python 2.7

Bu yaklaşım aşağıdaki hususlardan yararlanır:

Herhangi bir tam sayı, ikisinin güçlerinin toplamı olarak temsil edilebilir. İkisinin gücündeki üsler aynı zamanda ikisinin gücü olarak da temsil edilebilir. Örneğin:

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

Sonunda bu ifadeler kümeler olarak temsil edilebilir (Python'da yerleşik olarak kullandım frozenset):

  • 0boş küme olur {}.
  • 2^atemsil eden seti içeren set haline gelir a. Örneğin: 1 = 2^0 -> {{}}ve 2 = 2^(2^0) -> {{{}}}.
  • a+btemsil eden setlerinin birleştirme olur ave b. Örneğin,3 = 2^(2^0) + 2^0 -> {{{}},{}}

2^2^...^2Sayısal değer bir tamsayı olarak depolanamayacak kadar büyük olsa bile , form ifadelerinin kolayca benzersiz küme gösterimlerine dönüştürülebileceği ortaya çıkmaktadır .


Bu n=20, makinemdeki CPython 2.7.5 üzerinde 8.7s'de çalışıyor (Python 3'te biraz daha yavaş ve PyPy'de çok daha yavaş):

"""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()

(Not dekoratörünün konsepti http://code.activestate.com/recipes/578231-plasılıkla-the-fastest-memoization-decorator-in-the-/ adresinden kopyalanır .)

Çıktı:

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

Farklı zamanlamalar n:

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

nYukarıdakilerden herhangi biri makinemde bellek hatasına neden olur.

Başka bir dile çevirerek bunu daha hızlı yapıp yapamayacağımı merak ediyorum.

Düzenle:get_results İşlev optimize edildi . Ayrıca, 2.7.2 yerine Python 2.7.5 kullanmak biraz daha hızlı çalışmasını sağladı.


Bir C # çeviri yaptım ama sıralı diziler kullanarak ve ek olarak set yerine çekleri içerir. Çok daha yavaş ve henüz halef işlevini hatırlamamaktan mı yoksa karşılaştırmaların maliyetinden mi kaynaklandığını görmek için henüz profilli değilim.
Peter Taylor

1
@ Flornquake'in (parlak) kodunu profilli etmedim, ancak CPU zamanının çoğunun, her zamanki hash tablosu ve karma anahtarını kullanarak Python'da oldukça iyi optimize edilmiş set üyelik testleri ve set manipülasyon işlemleri yaparak harcandığını varsayabilirim. rutinleri. Memoization kesinlikle büyük bir şey, bunun gibi üstel bir algoritma ile. Bunu dışarıda bırakırsanız, katlanarak daha yavaş bir performans bekleyebilirsiniz.
Tobia

@Tobia, aslında ben C # halefi işlevini memoising yavaşladı buldum. Ayrıca, daha basit bir çevirinin (set işlemlerini kullanarak) daha düşük düzeyli eklediğimden önemli ölçüde daha yavaş olduğunu buldum. Orijinal kodumda bulduğum tek gerçek gelişme dikkate (a^b)^c = (a^c)^balmaktı ve bu Python uygulamasından hala çok daha yavaş.
Peter Taylor

@PeterTaylor: Edit: Görebildiğim kadarıyla, flornquake algoritması bir ağaç ağaçlarının kendisi olduğu bir ağaç kümesi oluşturmak dayanmaktadır, vb. En küçük boş kümeden en büyük kümeye kadar bu ağaçların tüm parçaları not edilir. Bu, tüm bu ağaçların yalnızca bir kez (CPU tarafından) hesaplanan ve bir kez (RAM'de) depolanan "tekrarlanan yapı" içerdiği anlamına gelir. "Sırayla ekleme" algoritmanızın bu yinelenen yapının tümünü tanımladığından ve bir kez hesapladığından emin misiniz? (yukarıda üstel karmaşıklık dediğim şey) Ayrıca bkz. en.wikipedia.org/wiki/Dynamic_programming
Tobia

@Tobia, üst üste geldik. Kodu gönderdim.
Peter Taylor

5

C #

Bu, flornquake'in Python kodunun C # 'a, doğrudan çeviri üzerinde ılımlı bir hızlanma sağlayan daha düşük seviye ekleme rutini kullanılarak yapılan çeviridir. Sahip olduğum en optimize edilmiş sürüm değil, ama bu biraz daha uzun çünkü ağaç yapısını ve değerleri saklamak zorunda.

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;
        }
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.