首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >将正数转换为其他基(2 <= Base <= 35)的基对象。

将正数转换为其他基(2 <= Base <= 35)的基对象。
EN

Code Review用户
提问于 2017-03-22 00:17:03
回答 1查看 68关注 0票数 4

我对Python相当陌生(这是我做的第一件真正利用类的事情)。这是可行的,我对此很满意,只是寻求一般性的建议。特别好的是输入我的评论和如何‘仿生’的代码是。

代码语言:javascript
复制
#import fractions #unused, on todo

class Base:

  # Dicts for getting a digit from a value and a value from a digit
  nums    = {0: '0', 1: '1', 2: '2', 3: '3', 4: '4', 5: '5', 6: '6', 7: '7', 8: '8', 9: '9', 10: 'A', 11: 'B', 12: 'C', 13: 'D', 14: 'E', 15: 'F', 16: 'G', 17: 'H', 18: 'I', 19: 'J', 20: 'K', 21: 'L', 22: 'M', 23: 'N', 24: 'O', 25: 'P', 26: 'Q', 27: 'R', 28: 'S', 29: 'T', 30: 'U', 31: 'V', 32: 'W', 33: 'X', 34: 'Y', 35: 'Z'}
  rnums   = {'6': 6, 'Y': 34, '3': 3, 'M': 22, 'C': 12, '0': 0, '7': 7, 'X': 33, '1': 1, 'D': 13, 'W': 32, '9': 9, 'V': 31, 'U': 30, 'A': 10, 'L': 21, 'F': 15, 'O': 24, '4': 4, 'J': 19, 'Z': 35, 'I': 18, '5': 5, 'T': 29, 'P': 25, '2': 2, 'E': 14, 'R': 27, 'H': 17, 'S': 28, 'N': 23, '8': 8, 'B': 11, 'Q': 26, 'K': 20, 'G': 16}

  # The special bases (binary, octal, hex) have different identifiers from the pattern followed by most
  sbases  = {2: 'b', 8: 'o', 16: 'x'}
  rsbases = {'b': 2, 'o': 8, 'x': 16}

  def __init__(self, cols, base = 8, bigval = None): #cols is just a holdover; not sure what the best name for this general value would be
    self.vals = {} # {column: value} dictionary
    self.base = base

    #can be created from an int & base, a str, a dict, or another Base object
    if isinstance(cols, int) and bigval is None:
      self.int_init(cols)
    elif isinstance(cols, str):
      if cols[0] == '0' and cols[2] == 'b': #valid string
        self.baseNum_init(cols)
      else:
        raise ValueError('str init can only be used with a string of the form `0<base_char>b<basenum>`')
        #self.vals = convert(int(cols)).vals 
        # ^Thought about this before but it shouldn't be allowed
    elif isinstance(cols, dict):
      self.dict_init(cols)
    elif isinstance(cols, Base):
      self.int_init(cols.base_ten())
    else: #for using Base in a function like convert()
      self.cols = cols
      self.vals = {cols: bigval}

  def int_init(self, num):

    # The powers of the base, in a 1000-tuple of 2-tuples. i+1 b/c base**0 is the 1st column in a number.
    self.powers = tuple( (i + 1, self.base**i) for i in range(1000 + 1) )

    if self.base > len(nums) - 1 or self.base < 2: #unacceptable bases: <2 or more than are in the dict
      raise ValueError('base used in int_init either too low or too high')
    elif num > sum( set( power[1] for power in self.powers) ): #if num > sum of every power up to base**1000, can't represent it
      raise ValueError('num is too large; must be <= sum_i=0^1000(base^i)') #sigma notation
    elif num < 0: #no negatives
      raise ValueError('num is too small; must be >= 0')

    #special case for zero
    if num == 0:
      self.cols = 1
      self.vals = {1: 0}


    elif num % 1 != 0:
      pass
      #frac = fractions.Fraction(num)
      #to-do: fractions
    else:
      for k in range(len(self.powers) - 1, 0 - 1, -1):  #counting down through self.powers
        power = self.powers[k]                 
        if not self.vals: #not {} is True
          if num >= power[1]: #go down till a power is smaller than the number
            self.cols = power[0] #how many columns there are total
            self.assign(power[0], num // power[1]) #how many of that power are in num
            num %= power[1]
        else:
          self.assign(power[0], num // power[1])
          num %= power[1]

  def baseNum_init(self, _str):
    #base included in the string in index 1 takes precedence over base argument of __init__
    #if-statement b/c of special bases
    self.base = self.rsbases[_str[1]] if _str[1] in self.rsbases else Base.val_from_char(_str[1])

    baseNum   = _str[3:] #the important bit
    self.cols = len(baseNum)
    _col     = self.cols
    #baseNum[::-1].index(digit) instead of a decrementing _col almost works, but repeating digits foil it

    for digit in baseNum:
      if Base.val_from_char(digit) >= self.base: #'8' is never allowed in a base-8 number
        raise ValueError('Value in column ' + str(_col) + ' greater than allowed.')
      else:
        self.assign(_col, Base.val_from_char(digit))
        _col -= 1

  def dict_init(self, _dict):
    if 'base' in _dict: #base can be supplied as argument to __init__ or as a dict key, if both dict takes precedence
      self.base = _dict['base']
      del _dict['base']

    self.cols = len(_dict)

    for col, val in _dict.items():
      if val >= self.base:
        raise ValueError('Value in column ' + str(col) + 'greater than allowed.')
      else:
        self.assign(col, val)

    #ASSIGN
  def assign(self, col, val):
    self.vals[col] = val

  #VAL DICTS
  def val_from_char(char): #No self argument b/c they logically belong to the class, but they don't require an instance to work
    return Base.rnums[char]

  def char_from_val(val):
    return Base.nums[val]

  #TO AND FROM BASE-10
  def base_ten(self):
    result = 0 
    for col, val in self.vals.items():
      result += self.base**(col-1) * val
    return result

  #basically undos str_init()
  def __str__(self):
    baseNum = ''
    for m in range(len(self.vals), 0, -1):
      baseNum += Base.char_from_val(self.vals[m])
    baseId = Base.sbases[self.base] if self.base in Base.sbases else Base.char_from_val(self.base)
    return '0' + baseId + 'b' + baseNum

  def __format__(self):
    pass
    #on the to-do list

  def __repr__(self):
    return "Base('" + self.__str__() + "')"
    # b/c of str_init, eval(repr(Base)) == Base


#-------------

#These are used in convert()
nums = {0: '0', 1: '1', 2: '2', 3: '3', 4: '4', 5: '5', 6: '6', 7: '7', 8: '8', 9: '9', 10: 'A', 11: 'B', 12: 'C', 13: 'D', 14: 'E', 15: 'F', 16: 'G', 17: 'H', 18: 'I', 19: 'J', 20: 'K', 21: 'L', 22: 'M', 23: 'N', 24: 'O', 25: 'P', 26: 'Q', 27: 'R', 28: 'S', 29: 'T', 30: 'U', 31: 'V', 32: 'W', 33: 'X', 34: 'Y', 35: 'Z'}
rnums = {'6': 6, 'Y': 34, '3': 3, 'M': 22, 'C': 12, '0': 0, '7': 7, 'X': 33, '1': 1, 'D': 13, 'W': 32, '9': 9, 'V': 31, 'U': 30, 'A': 10, 'L': 21, 'F': 15, 'O': 24, '4': 4, 'J': 19, 'Z': 35, 'I': 18, '5': 5, 'T': 29, 'P': 25, '2': 2, 'E': 14, 'R': 27, 'H': 17, 'S': 28, 'N': 23, '8': 8, 'B': 11, 'Q': 26, 'K': 20, 'G': 16}

#Has all the same logic as Base.int_init()
def convert(num, base=8):
  if base > len(nums) or base < 2:
    raise Exception
  powers = tuple( (i + 1, base**i) for i in range(1000 + 1) )
  if num == 0:
    return Base(1, bigval = 0)
  if num % 1 != 0:
    pass
    #frac = fractions.Fraction(num)
  else:
    result = None
    for k in range(1, len(powers) + 1):
      power = powers[-k]
      if not result:
        if num >= power[1]:
          biggest = power
          result = Base(biggest[0], base, nums[ num // biggest[1] ])
          num %= biggest[1]

      elif isinstance(result, Base):
        result.assign(power[0], num // power[1])
        num %= power[1]

  return result

#This used to be a placeholder Base to use where needed but it's not needed anymore
#place = Base(1, base = 2)

#TESTING

#if __name__ == '__main__':
baseToTheWhat = 5
bases = ((2, 8, 16), range(2, 35 + 1), sorted( (2, 8, 6, 4, 35, 30, 16, 20, 15, 9, 29) ) ) #bases I want to try
baseIndex = 0 #which of those to use?
for i in bases[baseIndex]:
  for l in sorted(set([0, 1, 2, 5, 8, 9, 10, 11, 20, 40, 32, 63, 64, 65, 8**3 - 1, 8**3, 8**3 + 1, 100, 1000, 8**5 ]
                         + [1,2,3,4,5,6,7,8,9,10] 
                         + [8**q for q in range(baseToTheWhat + 1)] + [8**q - 1 for q in range(baseToTheWhat + 1)] + [8**q + 1 for q in range(baseToTheWhat)]
                         + [r**t + v for v in (-1, 0, 1) for t in range(baseToTheWhat) for r in bases[baseIndex]]
                         + list(range(100))  )):
    print("{:10d} {:2d} {:20}".format(l, i, str(Base(l, base=i))))
EN

回答 1

Code Review用户

发布于 2017-03-22 04:40:11

一般来说,您的代码看起来不错。我有几条裤子。

Pep8:

您应该考虑按照pep8对代码进行格式化。在共享代码时,这一点很重要,因为一致的样式使其他程序员更容易阅读您的代码。有各种工具可以帮助使代码pep8兼容。我使用的是PyCharm IDE,它将在编辑器中显示pep8违规情况。

在可能时构造数据结构:

如果您改变了这一点:

代码语言:javascript
复制
nums = {0: '0', 1: '1', 2: '2', 3: '3', 4: '4', 5: '5', 6: '6', 7: '7',
        8: '8', 9: '9', 10: 'A', 11: 'B', 12: 'C', 13: 'D', 14: 'E',
        15: 'F', 16: 'G', 17: 'H', 18: 'I', 19: 'J', 20: 'K', 21: 'L',
        22: 'M', 23: 'N', 24: 'O', 25: 'P', 26: 'Q', 27: 'R', 28: 'S',
        29: 'T', 30: 'U', 31: 'V', 32: 'W', 33: 'X', 34: 'Y', 35: 'Z'}

rnums = {'6': 6, 'Y': 34, '3': 3, 'M': 22, 'C': 12, '0': 0, '7': 7, 'X': 33,
         '1': 1, 'D': 13, 'W': 32, '9': 9, 'V': 31, 'U': 30, 'A': 10,
         'L': 21, 'F': 15, 'O': 24, '4': 4, 'J': 19, 'Z': 35, 'I': 18,
         '5': 5, 'T': 29, 'P': 25, '2': 2, 'E': 14, 'R': 27, 'H': 17,
         'S': 28, 'N': 23, '8': 8, 'B': 11, 'Q': 26, 'K': 20, 'G': 16}

至:

代码语言:javascript
复制
nums = {i: chr(ord('0') + i) for i in range(10)}
nums.update({i+10: chr(ord('A') + i) for i in range(26)})
rnums = {v: k for k, v in nums.items()}

我相信结果更容易检查正确性。坦率地说,我发现从一开始就容易得多。

Python有一个巧妙的双重不等式:

你可以改变:

代码语言:javascript
复制
if self.base > len(nums) - 1 or self.base < 2:

至:

代码语言:javascript
复制
if not 2 <= self.base < len(nums):

我觉得更容易读懂。

使用类数据元素:

您的convert()代码重新定义了numsrnums。可以以Base.numsBase.rnums的形式直接从类中访问这些数据,这将允许更好的干的

票数 2
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/158444

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档