จำนวนผลลัพธ์ตัวเลขที่เป็นไปได้ของการวงเล็บ 2 ^ 2 ^ … ^ 2


19

พิจารณาการแสดงออก2^2^...^2กับผู้ประกอบการn ^ผู้ประกอบการ^หมายถึงการยกกำลัง ("ถึงพลังของ") สมมติว่ามันไม่ได้มีค่าเริ่มต้นของการมีเพศสัมพันธ์ที่ไม่ดีดังนั้นการแสดงออกต้องได้รับการวงเล็บอย่างเต็มที่เพื่อให้ชัดเจน หลายวิธีที่จะ parenthesize การแสดงออกจะได้รับจากหมายเลขที่คาตาลัน C_n=(2n)!/(n+1)!/n!

บางครั้ง parenthesizations ที่แตกต่างกันให้ผลเป็นตัวเลขเดียวกันตัวอย่างเช่น(2^2)^(2^2)=((2^2)^2)^2ดังนั้นจำนวนของผลเป็นตัวเลขที่เป็นไปได้ที่แตกต่างกันสำหรับการกำหนดnน้อยกว่าสำหรับทุกC_n n>1ลำดับเริ่มต้น1, 1, 2, 4, 8, ...เมื่อเทียบกับหมายเลขคาตาลัน1, 2, 5, 14, 42, ...

ปัญหาที่เกิดขึ้นคือการเขียนโปรแกรมที่เร็วที่สุด (หรือฟังก์ชั่น) ที่ยอมรับnเป็น input และส่งกลับจำนวนของผลเป็นตัวเลขที่แตกต่างกันเป็นไปได้ของการแสดงออก2^2^...^2กับผู้ประกอบการn ^ประสิทธิภาพไม่ควรลดลงอย่างมีนัยสำคัญเมื่อnโตขึ้นดังนั้นการคำนวณโดยตรงของเสาพลังงานสูงอาจเป็นความคิดที่ไม่ดี


ฉันแค่การแบ่งปันความคิดที่นี่ แต่ดูเหมือนว่ามันจะเป็นไปได้ที่จะใช้เฉพาะการบวกและการคูณเป็นคำตอบที่มักจะอยู่ในรูปแบบและมันจึงจะเป็นที่ไม่จำเป็นในการติดตามของอะไรยกเว้น2^n nคือเพียงแค่ใช้กฎของการยกกำลังดูเหมือนฉลาด อย่างไรก็ตามมีวิธีที่ชาญฉลาดและพีชคณิตอย่างสมบูรณ์ในการทำเช่นนี้
Fors

@ สำหรับฉันคิดว่าnมันยังใหญ่เกินไปที่จะคำนวณ ยังคงสังเกตเห็นได้ดี อาจเป็นตัวแทนซ้ำในรูปแบบ "1 หรือ 2 ^ (... ) หรือ (... ) + (... )"; แต่คุณยังคงมีปัญหาเกี่ยวกับวิธีทำให้การแสดงตัวเลขดังกล่าวเป็นมาตรฐาน (หรือเปรียบเทียบการแทนสองแบบเพื่อความเท่าเทียมกันของค่า)
John Dvorak

4
@JanDvorak, A002845 (ไม่มีรูปแบบที่ปิด)
Peter Taylor


1
@Vladimir Reshetnikov: ฉันคิดว่ามีข้อผิดพลาดแบบออฟไลน์ในสูตรของคุณ เมื่อคุณมีnสองครั้งและC_n=(2n)!/(n+1)!/n!ควรเป็นจำนวนวงเล็บแล้วสำหรับ n = 3 ควรเป็น 5 ถูกต้องหรือไม่ ฉันเห็น(2^2)^2และ2^(2^2)แต่อีกสามชุดค่าผสมคืออะไร ฉันคิดว่า C_n ให้จำนวนวงเล็บสำหรับ n + 1 twos
Martin Thoma

คำตอบ:


9

Python 2.7

วิธีนี้ใช้ประโยชน์จากข้อควรพิจารณาต่อไปนี้:

จำนวนเต็มใด ๆ สามารถแทนด้วยผลบวกของกำลังสอง เลขชี้กำลังในอำนาจของทั้งสองยังสามารถแสดงเป็นพลังของทั้งสอง ตัวอย่างเช่น:

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

นิพจน์เหล่านี้ที่เราลงท้ายด้วยสามารถแสดงเป็นชุดชุด (ใน Python ฉันใช้บิวด์อินfrozenset):

  • 0{}กลายเป็นเซตว่าง
  • 2^aaกลายเป็นชุดที่มีชุดที่เป็นตัวแทนของ เช่น: และ1 = 2^0 -> {{}}2 = 2^(2^0) -> {{{}}}
  • a+bกลายเป็น concatenation ของชุดตัวแทนและa bเช่น,3 = 2^(2^0) + 2^0 -> {{{}},{}}

ปรากฎว่าการแสดงออกของแบบฟอร์ม2^2^...^2สามารถเปลี่ยนเป็นชุดตัวแทนที่ไม่ซ้ำกันได้อย่างง่ายดายแม้ว่าค่าตัวเลขจะมีขนาดใหญ่เกินไปที่จะเก็บเป็นจำนวนเต็ม


สำหรับn=20สิ่งนี้ทำงานใน8.7sบน 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

n21 ข้อใด ๆข้างต้นส่งผลให้เกิดข้อผิดพลาดของหน่วยความจำในเครื่องของฉัน

ฉันจะสนใจถ้าใครสามารถทำให้เร็วขึ้นโดยการแปลเป็นภาษาอื่น

แก้ไข:ปรับget_resultsฟังก์ชั่นให้เหมาะสมที่สุด นอกจากนี้การใช้ Python 2.7.5 แทนที่จะเป็น 2.7.2 ทำให้การรันเร็วขึ้นเล็กน้อย


ฉันแปล C # แต่ใช้อาร์เรย์ที่เรียงลำดับและทำการเพิ่มตามลำดับแทนที่จะเป็นชุดมีการตรวจสอบ มันช้ากว่ามากและฉันยังไม่ได้ทำโปรไฟล์เพื่อดูว่านั่นเป็นเพราะการไม่บันทึกฟังก์ชันการทำต่อหรือเนื่องจากค่าใช้จ่ายในการเปรียบเทียบ
Peter Taylor

1
ฉันยังไม่ได้ทำโปรไฟล์ (ยอดเยี่ยม) ของ @ flornquake รหัส แต่ฉันคิดว่าเวลา CPU ส่วนใหญ่ใช้ในการทำชุดการทดสอบการเป็นสมาชิกและตั้งค่าการดำเนินการจัดการซึ่งทั้งสองได้รับการปรับให้เหมาะสมใน Python โดยใช้ตารางแฮช ubiquitous กิจวัตร การบันทึกเป็นเรื่องใหญ่แน่นอนด้วยอัลกอริธึมอธิบายเช่นนี้ หากคุณไม่ได้ใช้สิ่งนั้นคุณสามารถคาดหวังประสิทธิภาพที่ช้าลงอย่างมาก
เบีย

@Tobia ที่จริงฉันพบว่าใน C # memoising ฟังก์ชั่นการสืบทำให้มันช้าลง ฉันยังพบว่าการแปลตามตัวอักษรมากขึ้น (โดยใช้การตั้งค่า) นั้นช้ากว่าการเพิ่มระดับล่างอย่างมีนัยสำคัญ การปรับปรุงที่แท้จริงเพียงอย่างเดียวที่ฉันพบในรหัสต้นฉบับของฉันคือการคำนึงถึง(a^b)^c = (a^c)^bและมันก็ยังช้ากว่าการนำ Python นี้ไปใช้
Peter Taylor

@PeterTaylor: แก้ไข: เท่าที่ฉันเห็นอัลกอรึทึมของ flornquake อาศัยการสร้างชุดของต้นไม้ที่ต้นไม้เป็นชุดของต้นไม้และอื่น ๆ ต้นไม้ทุกต้นจากชุดที่เล็กที่สุดไปจนถึงชุดที่ใหญ่ที่สุดจะถูกบันทึกไว้ ซึ่งหมายความว่าต้นไม้เหล่านี้มี "โครงสร้างซ้ำ" ที่คำนวณเพียงครั้งเดียว (โดย CPU) และเก็บไว้ครั้งเดียว (ใน RAM) คุณแน่ใจหรือไม่ว่าอัลกอริทึม "การเพิ่มตามลำดับ" ของคุณกำลังระบุโครงสร้างซ้ำทั้งหมดและคำนวณเพียงครั้งเดียว (สิ่งที่ฉันเรียกว่าความซับซ้อนแบบเอกซ์โปเนนเชียลด้านบน) ดูen.wikipedia.org/wiki/Dynamic_programming
Tobia

@Tobia เราเหลื่อมกัน ฉันโพสต์รหัสแล้ว
Peter Taylor

5

ค#

นี่คือการแปลรหัสไพ ธ อนของ flornquake เป็น 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.