#!/usr/bin/python3 # -*- coding: utf-8 -*- """Balanced ternary. To test: fades -d pytest -x pytest bt.py This module has a command-line interface if run as a script; run ./bt.py -h for details. """ import argparse power_digits = u"⁰¹²³⁴⁵⁶⁷⁸⁹" def test_succ(): ns = [] s = '' for i in range(12): s = succ(s) ns.append(s) assert ns == ['+', '+-', '+0', '++', '+--', '+-0', '+-+', '+0-', '+00', '+0+', '++-', '++0'] def succ(s="0"): "Return the successor of balanced ternary s." if not s: return "+" i = len(s) - 1 while i > 0 and s[i] == '+': i -= 1 # If i is 0 then we had a whole string of +, so we need to add a new digit if i == 0: return '+' + '-' * len(s) # Otherwise s[i] is the digit we need to increment inc = '0' if s[i] == '-' else '+' return s[:i] + inc + '-' * (len(s) - i - 1) def test_to_bt(): assert to_bt(7) == '+-+' assert to_bt(-7) == '-+-' assert to_bt(-9) == '-00' for i in range(-100, 100): assert from_bt(to_bt(i)) == i def from_bt(s): "Convert balanced ternary using -0+ to an integer." return 0 if s == '' else 3 * from_bt(s[:-1]) + '0+'.find(s[-1]) def to_bt(n): "Convert an integer to balanced ternary using -0+." a = natural_to_bt(abs(n)) return a if n >= 0 else negate(a) def negate(s): "Negates balanced ternary s using -0+." return ''.join('-' if d == '+' else '+' if d == '-' else d for d in s) def test_natural_to_bt(): for i in range(100): assert natural_to_bt(i) == natural_to_bt_dumb(i) def natural_to_bt_dumb(n): "Convert a positive integer to balanced ternary using -0+." s = '' for i in range(abs(n)): s = succ(s) return s def natural_to_bt(n): "Convert a positive integer to balanced ternary using -0+." if n == 0: return '' return natural_to_bt((n+1)//3) + '0+-'[n % 3] def dec(n, digits): "Convert a nonnegative integer to base 10 using the supplied digits." return ('' if n < 10 else dec(n // 10, digits)) + digits[n % 10] def test_dec(): assert dec(319, power_digits) == '³¹⁹' assert dec(0, power_digits) == '⁰' def bt_to_alg(bt): "Convert a balanced ternary number ('+0-') to an expression ('3² - 1')." terms = reversed([(digit, ('3%s' % dec(i, power_digits) if i > 1 else '3' if i == 1 else '1')) for i, digit in enumerate(reversed(bt)) if digit in '+-']) return ' '.join(('-' if sign == '-' and i == 0 else '- ' if sign == '-' else '+ ' if i != 0 else '') + term for i, (sign, term) in enumerate(terms)) def test_bt_to_alg(): assert bt_to_alg('+0--') == '3³ - 3 - 1' assert bt_to_alg('-0000+00000') == '-3¹⁰ + 3⁵' def to_alg(n): "Convert an integer like 8 to an expression string like '3² - 1'." return bt_to_alg(to_bt(n)) if __name__ == '__main__': parser = argparse.ArgumentParser( description= "Convert integers to and from balanced ternary, using" + " Unicode algebraic notation (e.g. 3² - 1) by default." ) parser.add_argument('-s', action='store_true', help='Display ternary in -0+ notation.') parser.add_argument('-i', action='store_true', help='Convert FROM -0+ notation, not to ternary.' + ' (Add a leading 0 to negative numbers or precede' + ' the numbers with a -- to keep them from being' + ' parsed as options.)') parser.add_argument('integer', nargs='+', help='Numbers to convert.') args = parser.parse_args() if args.i: for i in args.integer: print(from_bt(i)) else: for i in args.integer: print((to_bt if args.s else to_alg)(int(i)))