Как бы вы перевели это с Perl на Python? - PullRequest
11 голосов
/ 03 марта 2009

У меня есть функция 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?

Ответы [ 8 ]

7 голосов
/ 03 марта 2009

Посмотрите на этот ответ для надежного метода преобразования числа в буквенно-цифровой идентификатор

Код, который я представляю, не переходит от 'Z' к 'AA', вместо этого он переходит к 'BA', но я полагаю, что это не имеет значения, он все равно создает уникальный идентификатор

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 times, и это все равно даст хорошие результаты:

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
5 голосов
/ 03 марта 2009

Ну, извините, но вы не можете просто сделать прямой перевод с 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())

И просто будь счастлив.

3 голосов
/ 03 марта 2009

Я только что проверил это до 1000 с оригинальной реализацией perl, и 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)
2 голосов
/ 03 марта 2009

Класс общий и скучный, , но это мой самый первый рекурсивный генератор. <3 </p>

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']
1 голос
/ 03 марта 2009

Должен ли суффикс быть такими буквами?

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)).
1 голос
/ 03 марта 2009

Очень похоже на Али А, но я все равно выложу:

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'
0 голосов
/ 10 марта 2009

Глядя на проблему, кажется, что она подходит для сопрограммы (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'
0 голосов
/ 05 марта 2009

Я отвечаю впервые, и я использовал глобалы, но мне это показалось самым простым.

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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...