【问题标题】:How would you translate this from Perl to Python?你会如何把它从 Perl 翻译成 Python?
【发布时间】:2010-10-10 22:08:05
【问题描述】:

我有一个 Perl 函数,它接受一个时间戳并返回未更改的时间戳(如果它以前从未见过),否则它会附加一些字母以使其唯一:

sub uniqify($) {
  my $timestamp = shift;

  state $last_ts = -1;
  state $next_letter = 'A';

  if ($timestamp == $last_ts) {
    $timestamp .= $next_letter++;
  } else {
    $last_ts = $timestamp;
    $next_letter = 'A';
  }

  return $timestamp;
}

因此,如果您调用它四次,使用值 1、1、1 和 2,它将返回 1,然后是 1A,然后是 1B,然后是 2。

注意:它只会以不断增加的时间戳被调用,因此它不需要调用它见过的每一个,只需调用最后一个。

现在我需要把这个函数翻译成 Python。我了解到我可以用全局变量(糟糕!)替换“状态”变量,或者将它们作为属性附加到函数中,但这些都不是特别优雅。

另外,Python 没有类似 Perl 的神奇自动增量,如果你“++”一个值为“A”的变量,它变成“B”——或者如果它是“Z”,它变成“AA” ”。所以这也是一个曲线球。

我正在拼凑一个解决方案,但它真的很丑陋且难以阅读。从 Perl 翻译到 Python 应该会产生相反的效果,对吧? :) 所以我将此作为对 SO 用户的挑战。你能把它变成一个优雅的 Python 函数吗?

【问题讨论】:

  • 因为没有 perl 的字符串增量特性而抨击 Python 是不公平的。但令人失望的是,还没有人敢于以蟒蛇的方式来应对挑战。如果 Chris J 使用的是函数属性,我觉得函数属性并不那么令人讨厌。
  • 在 perl 的自动增量中,'AZ' 之后是什么? 'AAA'??
  • 我不知道这个问题的目的是什么,除了诱使 Python 人抨击 Perl,反之亦然。除此以外,这里还有实际要求吗?
  • 我不是在抨击。我是在陈述事实。我有一个真实的、真实的 Perl 脚本,恰好使用了这个 Perl 功能。现在我将它移植到 Python。 Python 没有这个特性。就是那样子。如果我不喜欢 Python,我一开始就不会移植脚本。

标签: python perl


【解决方案1】:

this answer for a robust method to convert a number to an alphanumeric id

我提供的代码不是从“Z”转到“AA”,而是转到“BA”,但我想这没关系,它仍然会产生一个唯一的 id

from string import uppercase as up
import itertools

def to_base(q, alphabet):
    if q < 0: raise ValueError( "must supply a positive integer" )
    l = len(alphabet)
    converted = []
    while q != 0:
        q, r = divmod(q, l)
        converted.insert(0, alphabet[r])
    return "".join(converted) or alphabet[0]

class TimestampUniqifier( object ):
    def __init__(self):
        self.last = ''
        self.counter = itertools.count()
    def __call__( self, str ):
        if str == self.last:
            suf = self.counter.next()
            return str + to_base( suf, up )
        else:
            self.last = str
            self.counter = itertools.count()
            return str            

timestamp_uniqify = TimestampUniqifier()

用法:

timestamp_uniqify('1')
'1'
timestamp_uniqify('1')
'1A'
timestamp_uniqify('1')
'1B'
timestamp_uniqify('1')
'1C'
timestamp_uniqify('2')
'2'
timestamp_uniqify('3')
'3'
timestamp_uniqify('3')
'3A'
timestamp_uniqify('3')
'3B'

您可以随时调用它,它仍然会产生良好的效果:

for i in range(100): print timestamp_uniqify('4')

4
4A
4B
4C
4D
4E
4F
4G
4H
4I
4J
4K
4L
4M
4N
4O
4P
4Q
4R
4S
4T
4U
4V
4W
4X
4Y
4Z
4BA
4BB
4BC
4BD
4BE
4BF
4BG
4BH
4BI
4BJ
4BK
4BL
4BM
4BN
4BO
4BP
4BQ
4BR
4BS
4BT
4BU
4BV
4BW
4BX
4BY
4BZ
4CA
4CB
4CC
4CD
4CE
4CF
4CG
4CH
4CI
4CJ
4CK
4CL
4CM
4CN
4CO
4CP
4CQ
4CR
4CS
4CT
4CU
4CV
4CW
4CX
4CY
4CZ
4DA
4DB
4DC
4DD
4DE
4DF
4DG
4DH
4DI
4DJ
4DK
4DL
4DM
4DN
4DO
4DP
4DQ
4DR
4DS
4DT
4DU

【讨论】:

  • 我认为您需要在获得新时间戳以匹配原始 perl 版本时重置计数器。
  • 非常好的解决方案。可悲的是,您对迈克的“小”后缀的不正确和不屑一顾的评论破坏了它。 Perl 的字符串自动增量使 'Z'++ 变为 'AA'——Mike 在他的帖子中提出的观点。请参阅perldoc.perl.org/perlop.html#Auto-increment-and-Auto-decrement 了解更多信息。
  • 是的,后来我发现了我的错误..呵呵
【解决方案2】:

好吧,很抱歉,但您不能直接从 Perl 翻译到 Python(包括逐位 Perlisms)并期望结果会更漂亮。不会,会丑很多。

如果你想要 Python 的漂亮,你需要使用 Python 习惯用法。

现在是手头的问题:

from string import uppercase

class Uniquifier(object):

    def __init__(self):
        self.last_timestamp = None
        self.last_suffix = 0

    def uniquify(self, timestamp):
        if timestamp == self.last_timestamp:
            timestamp = '%s%s' % (timestamp,
                                  uppercase[self.last_suffix])
            self.last_suffix += 1
        else:
            self.last_suffix = 0
            self.timestamp = timestamp
        return timestamp

uniquifier = Uniquifier()
uniquifier.uniquify(a_timestamp)

更漂亮?可能是。更具可读性?大概吧。

编辑(re cmets):是的,这在 Z 之后失败了,我对这个解决方案完全不满意。所以我不会修复它,但可能会提供更好的东西,比如使用数字代替:

timestamp = '%s%s' % (timestamp,
                      self.last_suffix)

如果是我,我会这样做:

import uuid

def uniquify(timestamp):
    return '%s-%s' % (timestamp, uuid.uuid4())

只要开心就好。

【讨论】:

  • 我不懂python;那 string.uppercase[] 会产生“A”、“B”等吗?如果是这样,“Z”之后会发生什么?
  • 根据我对它的测试来看,它因字符串索引超出范围而崩溃。另外,请注意from string import uppercase,因为import string.uppercase 不起作用。在 else 块中,self.last_timestamp 而不是 self.timestamp。我很遗憾如此匆忙发表第一篇文章。自己成为受害者:-)
  • 一个简短的谷歌搜索表明 ascii_uppercase 可能是一个更好的选择。
  • 我知道 Python 激进分子抨击 Perl 的可读性是传统,但说真的,你不能说你的 Python 比他的 Perl 更具可读性!它们在语法上都相当简单,而且都很容易阅读。拜托了,不要再传福音了。
  • 你不能让 Python 中的类可以调用吗?这会稍微清理一下界面。
【解决方案3】:

我刚刚针对原始 perl 实现测试了最多 1000 次,并且 diff 为两者返回了相同的结果。后缀代码很棘手——这不是一个以 36 为基数的计数器。 Hasen J 的解决方案 - 尽管它产生一个唯一的时间戳 - 并不完全相同,因为它从“Z”转到“BA”,而应该转到“AA”以匹配 perl ++ 运算符。

#!/usr/bin/python

class uniqify:
    def __init__(self):
        self.last_timestamp = -1
        self.next_suffix = 'A'
        return

    def suffix(self):
        s = self.next_suffix
        letters = [l for l in self.next_suffix]
        if letters[-1] == 'Z':
            letters.reverse()
            nonz = None
            for i in range(len(letters)):
                if letters[i] != 'Z':
                    nonz = i
                    break
            if nonz is not None:
                letters[nonz] = chr(ord(letters[nonz]) + 1)
                for i in range(0, nonz):
                    letters[i] = 'A'
            else:
                letters = ['A'] * (len(letters) + 1)
            letters.reverse()
        else:
            letters[-1] = chr(ord(letters[-1]) + 1)

        self.next_suffix = ''.join(letters)
        return s

    def reset(self):
        self.next_suffix = 'A'
        return

    def __call__(self, timestamp):
        if timestamp == self.last_timestamp:
            timestamp_str = '%s%s' % (timestamp, self.suffix())
        else:
            self.last_timestamp = timestamp
            self.reset()
            timestamp_str = '%s' % timestamp

        return timestamp_str

uniqify = uniqify()

if __name__ == '__main__':
    for n in range(1000):
        print uniqify(1)
    for n in range(1000):
        print uniqify(2)

【讨论】:

  • 作为记录,我没有想出那个方法 :) 它在生成“唯一”时间戳方面做得很好,事实上它不是 完全 perl 没关系!虽然您的答案产生与 perl 相同的输出,但它并不短.. 它实际上非常复杂
【解决方案4】:

这个类是通用且无聊的,但这是我的第一个递归生成器。

def stamptag():
    yield ''
    prefixtag = stamptag()
    prefix = prefixtag.next()
    while True:
        for i in range(ord('A'),ord('Z')+1):
            yield prefix+chr(i)
        prefix = prefixtag.next()

tagger = stamptag()
for i in range(3000):
    tagger.next()
print tagger.next()

class uniquestamp:
    def __init__(self):
        self.timestamp = -1
        self.tagger = stamptag()

    def format(self,newstamp):
        if self.timestamp < newstamp:
            self.tagger = stamptag()
            self.timestamp = newstamp
        return str(newstamp)+self.tagger.next()

stamper = uniquestamp()
print map(stamper.format, [1,1,1,2,2,3,4,4])

输出:

DKJ
['1', '1A', '1B', '2', '2A', '3', '4', '4A']

【讨论】:

    【解决方案5】:

    后缀必须是这样的字母吗?

    from itertools import count
    def unique(timestamp): 
      if timestamp in unique.ts.keys():
        return timestamp + '.' + str(unique.ts[timestamp].next())
      else:
        unique.ts[timestamp] = count()
        return timestamp
    unique.ts = {}
    

    如果你想找回字母,你可以定义一个不同的计数。

    不过,这与您的 perl 代码不同。

    • 它保留了一个字典,因此如果您有很多独特的时间戳,那么您将使用大量内存。
    • 它处理乱序调用,而原始调用不处理(即 u(1)、u(2)、u(1))。

    【讨论】:

      【解决方案6】:

      与 Ali A 非常相似,但我还是会发布我的:

      class unique_timestamp:
          suffixes = " ABCDEFGHIJKLMNOPQRSTUVWXYZ"
          def __init__(self):
              self.previous_timestamps = {}
              pass
          def uniquify(self, timestamp):
              times_seen_before = self.previous_timestamps.get(timestamp, 0)
              self.previous_timestamps[timestamp] = times_seen_before + 1
              if times_seen_before > 0:
                  return str(timestamp) + self.suffixes[times_seen_before]
              else:
                  return str(timestamp)
      

      用法:

      >>> u = unique_timestamp()
      >>> u.uniquify(1)
      '1'
      >>> u.uniquify(1)
      '1A'
      >>> u.uniquify(1)
      '1B'
      >>> u.uniquify(2)
      '2'
      

      【讨论】:

        【解决方案7】:

        这是我第一次回答,我使用了全局变量,但对我来说这似乎是最简单的方法。

        from string import uppercase
        
        last_ts = None
        letters = None
        
        def increment(letters):
            if not letters:
                return "A"
            last_letter = letters[-1]
            if last_letter == "Z":
                return increment(letters[:-1])  + "A" 
            return letters[:-1] + uppercase[uppercase.index(last_letter) + 1]
        
        def uniquify(timestamp):
            global last_ts, letters
            if timestamp == last_ts:
                letters = increment(letters)
                return timestamp + letters
            last_ts = timestamp
            letters = None
            return timestamp
        
        print uniquify("1")
        print uniquify('1')
        print uniquify("1")
        print uniquify("2")
        for each in range(100): print uniquify("2")
        
        
        1
        1A
        1B
        2
        2A
        2B
        2C
        2D
        2E
        2F
        2G
        2H
        2I
        2J
        2K
        2L
        2M
        2N
        2O
        2P
        2Q
        2R
        2S
        2T
        2U
        2V
        2W
        2X
        2Y
        2Z
        2AA
        2AB
        2AC
        2AD
        2AE
        2AF
        2AG
        2AH
        2AI
        2AJ
        2AK
        2AL
        2AM
        2AN
        2AO
        2AP
        2AQ
        2AR
        2AS
        2AT
        2AU
        2AV
        2AW
        2AX
        2AY
        2AZ
        2BA
        2BB
        2BC
        2BD
        2BE
        2BF
        2BG
        2BH
        2BI
        2BJ
        2BK
        2BL
        2BM
        2BN
        2BO
        2BP
        2BQ
        2BR
        2BS
        2BT
        2BU
        2BV
        2BW
        2BX
        2BY
        2BZ
        2CA
        2CB
        2CC
        2CD
        2CE
        2CF
        2CG
        2CH
        2CI
        2CJ
        2CK
        2CL
        2CM
        2CN
        2CO
        2CP
        2CQ
        2CR
        2CS
        2CT
        2CU
        2CV
        

        【讨论】:

          【解决方案8】:

          从这个问题来看,它似乎很适合协程(Python 2.5 或更高版本)。下面是一些大致会产生相同结果的代码:

          def uniqify():
              seen = {}
              val = (yield None)
              while True:
                  if val in seen:
                      idxa, idxb = seen[val]
                      idxb += 1
                  else:
                      idxa, idxb = (len(seen)+1, ord('a'))
                  seen[val] = (idxa, idxb)
                  uniq = "%s%s" % (idxa, chr(idxb))
                  val = (yield uniq)
          

          这是你如何使用它的:

          >>> u = send.uniqify()
          >>> u.next() #need this to start the generator
          >>> u.send(1)
          '1a'
          >>> u.send(1)
          '1b'
          >>> u.send(1)
          '1c'
          >>> u.send(2)
          '2a'
          >>> u.send(2)
          '2b'
          >>> u.send(1) #you can go back to previous values
          '1d'
          >>> u.send('stringy') #you can send it anything that can be used as a dict key
          '3a'
          

          【讨论】:

            猜你喜欢
            • 2020-09-19
            • 1970-01-01
            • 2011-07-27
            • 2010-11-07
            • 1970-01-01
            • 1970-01-01
            • 2018-03-26
            • 1970-01-01
            • 2010-09-12
            相关资源
            最近更新 更多