2013-09-22 2 views
0

Я только начинаю изучать питон, и у меня есть это упражнение, которое меня озадачивает: Создайте функцию, которая может упаковать или распаковать строку букв. Итак, aaabb будет упакован a3b2 и наоборот.Python - упаковка/распаковка буквами

Для упаковки части функции, я написал следующее

def packer(s): 
    if s.isalpha():  # Defines if unpacked 

    stack = [] 

     for i in s: 
      if s.count(i) > 1: 
       if (i + str(s.count(i))) not in stack: 
        stack.append(i + str(s.count(i))) 
      else: 
       stack.append(i) 

     print "".join(stack) 

    else: 
     print "Something's not quite right.." 
     return False 
packer("aaaaaaaaaaaabbbccccd") 

Это, кажется, работает все собственно. Но в задании указывается, что , если на входе есть (например) буква a после b или c, тогда он должен быть впоследствии распакован в первоначальную форму. Итак, «ааббкка» должна стать a3b2k2a, а не a4b2k2. Я поэтому полагал, что я не могу использовать команду count(), так как , которая подсчитывает все вхождения элемента во всей строке, правильно? Какие у меня были бы варианты?

Переходим к распаковке - Я думал об основах, что мой код нужно сделать -

  1. между «если s.isalpha():» и еще, я должен добавить, что Элиф проверяет, имеет ли строка в нем цифры. (Я полагал, что это будет достаточно, чтобы определить, упакована ли она или распакована).
  2. Создать цикл и внутри него если приговор, который затем проверяет каждый элемент:

    2.1. Если у него есть число за ним> Возврат (или добавление в пустой стек) число раз после цифры
    2.2. Если у него нет номера, следующего за ним> Верните только элемент.

Большой вопрос номер 2 - как я могу проверить, является ли это число или просто еще один алфавитный элемент после элемента в списке? Я предполагаю, что это должно быть сделано с помощью slicing, но это только целые числа. Может ли это быть достигнуто с помощью команды индекса?

Кроме того - если это имеет какое-либо значение - до сих пор я в основном охватывал списки, строки, если и для , и мне сказали, что это упражнение выполнимо только с такими (... так что если вы не хотите, t mind, сохраняя это на самом деле базовым)

Вся помощь приветствуется энтузиастом новичка!


РЕШИТЬ:

def packer(s): 
    if s.isalpha():  # Defines if unpacked 
     groups= [] 
     last_char = None 

     for c in s: 
      if c == last_char: 
       groups[-1].append(c) 
      else: 
       groups.append([c]) 
      last_char = c 

     return ''.join('%s%s' % (g[0], len(g)>1 and len(g) or '') for g in groups) 

    else:     # Seems to be packed 

     stack = "" 

     for i in range(len(s)): 
      if s[i].isalpha(): 
       if i+1 < len(s) and s[i+1].isdigit(): 
        digit = s[i+1] 
        char = s[i] 
        i += 2 

        while i < len(s) and s[i].isdigit(): 
         digit +=s[i] 
         i+=1 
        stack += char * int(digit) 

       else: 
        stack+= s[i] 
      else: 
       "" 
     return "".join(stack) 
print (packer("aaaaaaaaaaaabbbccccd")) 
print (packer("a4b19am4nmba22")) 

Так что это мой окончательный код. Почти удалось вытащить все с помощью только циклов и утверждений. В конце концов, хотя мне пришлось ввести цикл while, чтобы решить проблему с несколькими цифрами. Я думаю, мне все же удалось сохранить его достаточно простым. Спасибо тонну миллимозам и всем остальным за щебень!

+0

Для упаковки мой алгоритм будет: иметь индекс «start» и «end». Установите 'start' в 0, затем увеличивайте' end' до тех пор, пока символ в индексе 'end' будет таким же, как и индекс' start'. (Или до тех пор, пока вы не дойдете до конца строки.) Когда символ меняется, выведите начальный символ и «конец запуска» (длина «запуска») символов. Установите 'start'' end', повторите, пока 'start' больше длины строки. – millimoose

+0

(Здесь используется очень распространенное соглашение, в котором, если указывать какой-то интервал с использованием индекса, индекс 'start' является * включенным *, а индекс' end' является * exclusive *. Таким образом, оба они указывают «до» символа.) – millimoose

+0

Я бы обработал распаковку, проверив, есть ли в какой-то момент цифра. Если вы это сделаете, просто прекратите то, что вы делаете, и вызовите функцию распаковки для всей строки. Это использует тот факт, что для неоднозначных строк (например, 'abc') не имеет значения, что вы делаете - ни упаковка, ни распаковка не изменят их - это означает, что безопасно считать, что строка распакована, пока вы не узнаете иначе. – millimoose

ответ

1

Самое простое решение: Если символ отличается, сделать новую группу , В противном случае добавьте его в последнюю группу. Наконец, подсчитайте все группы и присоедините их.

def packer(s): 
    groups = [] 
    last_char = None 
    for c in s: 
     if c == last_char: 
      groups[-1].append(c) 
     else: 
      groups.append([c]) 
     last_char = c 
    return ''.join('%s%s'%(g[0], len(g)) for g in groups) 

Другой подход использует re.

Regex r'(.)\1+' может соответствовать последовательных символов длиннее 1. И с re.sub вы можете легко кодировать:

regex = re.compile(r'(.)\1+') 

def replacer(match): 
    return match.group(1) + str(len(match.group(0))) 

regex.sub(replacer, 'aaabbkka') 
#=> 'a3b2k2a' 
+0

Первый код работает почти идеально. Но в случае «a» - он возвращает упакованную a1, что избыточно, чтобы упакованная форма была длиннее оригинала. Я попытался реализовать оператор if, чтобы проверить длину встречающегося символа, но я не знаю ни одного способа, кроме count, и это действительно не работает. Мысли? – Kano

+0

@Kano: вы можете заменить len (g) 'на len (g)> 1 и len (g) или ''' – Kabie

1

Я думаю, вы можете использовать `itertools.Функция grouby»

, например

import itertools 
data = 'aaassaaasssddee' 
groupped_data = ((c, len(list(g))) for c, g in itertools.groupby(data)) 
result = ''.join(c + (str(n) if n > 1 else '') for c, n in groupped_data) 

конечно можно сделать этот код более читаемым, используя генератор вместо заявления генератора

+0

'len (list ((i [1]))))' - это много парнеров. – millimoose

+0

Да, есть, но этот код работает – oleg

+0

Я упоминал удвоенный парн вокруг 'i [1]', который является избыточным. Вы также можете разрушить 'i' с' для ключа, group в itertools.groupby (...) ' – millimoose

1

Это реализация алгоритма я изложил в комментариях:

from itertools import takewhile, count, islice, izip 

def consume(items): 
    from collections import deque 
    deque(items, maxlen=0) 

def ilen(items): 
    result = count() 
    consume(izip(items, result)) 
    return next(result) 

def pack_or_unpack(data): 
    start = 0 
    result = [] 

    while start < len(data): 
     if data[start].isdigit(): 
      # `data` is packed, bail 
      return unpack(data) 
     run = run_len(data, start) 

     # append the character that might repeat 
     result.append(data[start]) 

     if run > 1: 
      # append the length of the run of characters 
      result.append(str(run)) 

     start += run 

    return ''.join(result) 


def run_len(data, start): 
    """Return the end index of the run of identical characters starting at 
    `start`""" 
    return start + ilen(takewhile(lambda c: c == data[start], 
            islice(data, start, None))) 

def unpack(data): 
    result = [] 
    for i in range(len(data)): 
     if data[i].isdigit(): 
      # skip digits, we'll look for them below 
      continue 

     # packed character 
     c = data[i] 
     # number of repetitions 
     n = 1 
     if (i+1) < len(data) and data[i+1].isdigit(): 
      # if the next character is a digit, grab all the digits in the 
      # substring starting at i+1 
      n = int(''.join(takewhile(str.isdigit, data[i+1:]))) 

     # append the repeated character 
     result.append(c*n) # multiplying a string with a number repeats it 
    return ''.join(result) 

print pack_or_unpack('aaabbc') 
print pack_or_unpack('a3b2c') 
print pack_or_unpack('a10') 
print pack_or_unpack('b5c5') 
print pack_or_unpack('abc') 

Вариант с добавлением регулярных выражений будет:

import re 
UNPACK_RE = re.compile(r'(?P<char> [a-zA-Z]) (?P<count> \d+)?', re.VERBOSE) 
def unpack_re(data): 
    matches = UNPACK_RE.finditer(data) 
    pairs = ((m.group('char'), m.group('count')) for m in matches) 
    return ''.join(char * (int(count) if count else 1) 
        for char, count in pairs) 

Этот код демонстрирует наиболее простой (или «базовый») подход к реализации этого алгоритма. Это не особенно элегантно или идиоматично или обязательно эффективно. (Это было бы, если бы было написано на C, но у Python есть такие предостережения, как: индексирование строки копирует символ в новую строку, а алгоритмы, которые, похоже, скопируют данные чрезмерно, могут быть быстрее, чем пытаться избежать этого, если копирование выполняется в C и обходной путь был реализован с помощью цикла Python.)

+0

При втором вводе («a3b2c») смены a3 на a31 - он дает только aaa ,Это новый уровень, чтобы заставить его работать с 2-значным номером? – Kano

+0

@ Kano Nope. Просто включает замену 'int (data [i + 1])' на то, что принимает все последовательные цифры в строке, начинающейся с индекса 'i + 1'. Это можно сделать с помощью регулярных выражений, но поскольку вы их не покрыли, я получаю версию с помощью itertools. (Целый ряд моих петель может быть.) – millimoose

+0

@Kano Код теперь больше 'itertools'y. Кроме того, больше Schlemiel-the-paintery на счетах пропуская мимо начала строки часто. (Или подставляя его много.) Я бы сказал, что эффективным способом распаковки строки будет использование регулярного выражения. – millimoose

Смежные вопросы