Python: преобразование ("понедельник", "вторник", "среда") в "понедельник-среду"

Я получаю последовательность дней недели. Код Python, что я хочу сделать:

def week_days_to_string(week_days):
    """
    >>> week_days_to_string(('Sunday', 'Monday', 'Tuesday'))
    'Sunday to Tuesday'
    >>> week_days_to_string(('Monday', 'Wednesday'))
    'Monday and Wednesday'
    >>> week_days_to_string(('Sunday', 'Wednesday', 'Thursday'))
    'Sunday, Wednesday, Thursday'
    """
    if len(week_days) == 2:
       return '%s and %s' % weekdays
    elif week_days_consecutive(week_days):
       return '%s to %s' % (week_days[0], week_days[-1])
    return ', '.join(week_days)

мне просто нужен week_days_consecutive функция (трудная часть Хе).

любые идеи, как я могу это сделать?

пояснение:

мои формулировки и примеры вызвали некоторую путаницу. Я не только хочу ограничить эту функцию рабочей неделей. Я хочу рассмотреть все дни недели (S, M, T, W, T, F). Мои извинения за неясность. об этом прошлой ночью. Отредактировал текст вопроса, чтобы сделать его более ясным.

Edit: бросая некоторые ключи в него

запахом последовательности:

>>> week_days_to_string(('Sunday', 'Monday', 'Tuesday', 'Saturday'))
'Saturday to Tuesday'

и, в @user470379 и дополнительно:

>>> week_days_to_string(('Monday, 'Wednesday', 'Thursday', 'Friday'))
'Monday, Wednesday to Friday'

7 ответов


Я бы подошел к этой проблеме:

  • создание dict отображения имен дней в их последовательный индекс
  • преобразование моих входных имен дней в их последовательные индексы
  • глядя на результирующие входные индексы и спрашивая, являются ли они последовательными

вот как вы можете сделать это, используя calendar.day_name, range и некоторые для понимания:

day_indexes = {name:i for i, name in enumerate(calendar.day_name)}
def weekdays_consecutive(days):
    indexes = [day_indexes[d] for d in days]
    expected = range(indexes[0], indexes[-1] + 1)
    return indexes == expected

несколько других вариантов, в зависимости от того, что вы нужно:

  • Если вам нужен Python

    day_indexes = dict((name, i) for i, name in enumerate(calendar.day_name))
    
  • если вы не хотите, чтобы субботу и воскресенье, просто обрезать последние два дня:

    day_indexes = ... calendar.day_name[:-2] ...
    
  • Если вам нужно обернуться после воскресенья, вероятно, проще всего просто проверить, что каждый элемент на один больше, чем предыдущий, но работает по модулю 7:

    def weekdays_consecutive(days):
        indexes = [day_indexes[d] for d in days]
        return all(indexes[i + 1] % 7 == (indexes[i] + 1) % 7
                   for i in range(len(indexes) - 1))
    

обновление: для расширенной проблемы я бы все равно придерживался их ежедневного индекса, но вместо этого я бы:

  • найти все индексы, где запуск последовательных дней останавливается
  • оберните дней, если нужно сделать самую длинную последовательность дней
  • группируйте дни в их последовательные промежутки
вот делать это:
def weekdays_to_string(days):
    # convert days to indexes
    day_indexes = {name:i for i, name in enumerate(calendar.day_name)}
    indexes = [day_indexes[d] for d in days]

    # find the places where sequential days end
    ends = [i + 1
            for i in range(len(indexes))
            if (indexes[(i + 1) % len(indexes)]) % 7 !=
               (indexes[(i) % len(indexes)] + 1) % 7]

    # wrap the days if necessary to get longest possible sequences
    split = ends[-1]
    if split != len(days):
        days = days[split:] + days[:split]
        ends = [len(days) - split + end for end in ends]

    # group the days in sequential spans
    spans = [days[begin:end] for begin, end in zip([0] + ends, ends)]

    # format as requested, with "to", "and", commas, etc.
    words = []
    for span in spans:
        if len(span) < 3:
            words.extend(span)
        else:
            words.append("%s to %s" % (span[0], span[-1]))
    if len(days) == 1:
        return words[0]
    elif len(days) == 2:
        return "%s and %s" % tuple(words)
    else:
        return ", ".join(words)

вы также можете попробовать следующее вместо этого последнего if/elif/else блок, чтобы получить "и" между последними двумя элементами и запятыми между всем остальным:

    if len(words) == 1:
        return words[0]
    else:
        return "%s and %s" % (", ".join(words[:-1]), words[-1])

это немного отличается от спецификации, но красивее в моих глазах.


def weekdays_consecutive(inp):
    days = { 'Monday': 0,
             'Tuesday': 1,
             'Wednesday': 2,
             'Thursday': 3,
             'Friday': 4 }

    return [days[x] for x in inp] == range(days[inp[0]], days[inp[-1]] + 1)

Как вы уже проверили для других случаев, я думаю, что это будет достаточно хорошо.


вот мое полное решение, вы можете использовать его, как хотите; (код помещается в общественное достояние, но я не буду нести никакой ответственности, если что-то случится с вами или вашим компьютером в результате его использования, и нет никакой гарантии yadda yadda ya).

week_days = {
    'monday':0,
    'tuesday':1,
    'wednesday':2,
    'thursday':3,
    'friday':4,
    'saturday':5,
    'sunday':6
}
week_days_reverse = dict(zip(week_days.values(), week_days.keys()))

def days_str_to_int(days):
    '''
    Converts a list of days into a list of day numbers.
    It is case ignorant.
    ['Monday', 'tuesday'] -> [0, 1]
    '''
    return map(lambda day: week_days[day.lower()], days)

def day_int_to_str(day):
    '''
    Converts a day number into a string.
    0 -> 'Monday' etc
    '''
    return week_days_reverse[day].capitalize()

def consecutive(days):
    '''
    Returns the number of consecutive days after the first given a sequence of 
    day numbers.
    [0, 1, 2, 5] -> 2
    [6, 0, 1] -> 2
    '''
    j = days[0]
    n = 0
    for i in days[1:]:
        j = (j + 1) % 7
        if j != i:
            break
        n += 1
    return n

def days_to_ranges(days):
    '''
    Turns a sequence of day numbers into a list of ranges.
    The days can be in any order
    (n, m) means n to m
    (n,) means just n
    [0, 1, 2] -> [(0, 2)]
    [0, 1, 2, 4, 6] -> [(0, 2), (4,), (6,)] 
    '''
    days = sorted(set(days))
    while days:
        n = consecutive(days)
        if n == 0:
            yield (days[0],)
        else:
            assert n < 7
            yield days[0], days[n]
        days = days[n+1:]

def wrap_ranges(ranges):
    '''
    Given a list of ranges in sequential order, this function will modify it in 
    place if the first and last range can be put together.
    [(0, 3), (4,), (6,)] -> [(6, 3), (4,)]
    '''
    if len(ranges) > 1:
        if ranges[0][0] == 0 and ranges[-1][-1] == 6:
            ranges[0] = ranges[-1][0], ranges[0][-1]
            del ranges[-1]

def range_to_str(r):
    '''
    Converts a single range into a string.
    (0, 2) -> "Monday to Wednesday"
    '''
    if len(r) == 1:
        return day_int_to_str(r[0])
    if r[1] == (r[0] + 1) % 7:
        return day_int_to_str(r[0]) + ', ' + day_int_to_str(r[1])
    return day_int_to_str(r[0]) + ' to ' + day_int_to_str(r[1])

def ranges_to_str(ranges):
    '''
    Converts a list of ranges into a string.
    [(0, 2), (4, 5)] -> "Monday to Wednesday, Friday, Saturday"
    '''
    if len(ranges) == 1 and ranges[0] == (0, 6):
        return 'all week'
    return ', '.join(map(range_to_str, ranges))

def week_days_to_string(days):
    '''
    Converts a list of days in string form to a stringed list of ranges.
    ['saturday', 'monday', 'wednesday', 'friday', 'sunday'] -> 
        'Friday to Monday, Wednesday'
    '''
    ranges = list(days_to_ranges(days_str_to_int(days)))
    wrap_ranges(ranges)
    return ranges_to_str(ranges)

характеристики:

  • он поддерживает более одного диапазона,
  • вы можете ввести в днях в любом порядке,
  • он обернет вокруг,

добавлять комментарии, если вы обнаружите какие-либо проблемы, и я сделаю все возможное, чтобы исправить их.


вам нужно будет проверить первую дату, затем иметь список со всеми будними днями в нем, проверить, находится ли следующий день в следующем индексе в списке, и повторить.

Это можно легко сделать с помощью нескольких циклов, предполагая, что данные дни в порядке.


Я не проверял, должен сказать.

def test(days):
  days = list(days)
  if len(days) == 1:
    return days[0]
  elif len(days) == 2:
    return ' to '.join(days)
  else:
    return ''.join(days[:1] + [' to ' + days[-1]])

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

def weekdays_consecutive(x):
    allDays = { 'Monday':1, 'Tuesday':2, 'Wednesday':3, 'Thursday':4, 'Friday':5, 'Saturday' : 6, 'Sunday' : 7}
    mostRecent = x[0]
    for i in x[1:]:
        if allDays[i] % 7 != allDays[mostRecent] % 7 + 1: return False
        mostRecent = i
    return True

и это может сортировать входные данные:x.sort(lambda x, y: allDays[x] - allDays[y]). Я не знаю, какую функцию вы предпочитаете использовать в

>>>x = ['Tuesday', 'Thursday', 'Monday', 'Friday']
>>>x.sort(lambda x, y: allDays[x] - allDays[y]) 
>>>x
['Monday', 'Tuesday', 'Thursday', 'Friday']

Это зависит от отсутствия не-дней. Я предполагаю, что вы хотели бы иметь дело с этим в функции weekdays_to_string, а не здесь в weekdays_consecutive.

Я тоже думаю, что вы хотите изменить первый случай вашей другой функции К "и" вместо " К " и добавьте случай для однодневных входных данных.

EDIT: была довольно глупая ошибка, которую я только что исправил, должна работать сейчас!


import itertools

#probably a better way to obtain this like with the datetime library
WEEKDAYS = (('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'))

def weekdays_consecutive(days):
    #assumes that days only contains valid weekdays

    if len(days) == 0:
        return True #or False?
    iter = itertools.cycle(WEEKDAYS)
    while iter.next() != days[0]: pass
    for day in days[1:]:
        if day != iter.next(): return False
    return True

#...

>>> weekdays_consecutive(('Friday', 'Monday'))
True
>>> weekdays_consecutive(('Friday', 'Monday', 'Tuesday'))
True
>>> weekdays_consecutive(('Friday', 'Monday', 'Tuesday', 'Thursday'))
False