你会如何将这个从Perl翻译成Python?

时间:2009-03-03 00:59:21

标签: python perl

我有一个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。我已经知道我可以用globals替换“state”变量(yuck!)或者可能将它们作为属性附加到函数中,但这些都不是特别优雅。

此外,Python没有像Perl的魔法自动增量那样的东西,如果你“++”是一个值为“A”的变量,它变成“B” - 或者如果它是“Z”,它变成“AA” ”。所以这也是一个曲线球。

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

8 个答案:

答案 0 :(得分:7)

查看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'

你可以称之为maaaany次,它仍然会产生良好的效果:

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

答案 1 :(得分:5)

嗯,很遗憾地说,但你不能只是直接从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)

更漂亮?也许。更具可读性?可能。

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

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

如果是我,我会这样做:

import uuid

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

只是快乐。

答案 2 :(得分: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)

答案 3 :(得分:2)

这个类是通用的,很无聊,但这是我的第一个递归生成器。&lt; 3

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']

答案 4 :(得分:1)

与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'

答案 5 :(得分:1)

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

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 :(得分:0)

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

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

答案 7 :(得分:0)

看看问题,它似乎非常适合协程(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'