@WebDeveloper2016

Как сделать равномерное распределение фамилий по группам?

Есть очень необычная и как оказалось довольно сложная задачка. Дано: список фамилий. Требуется распределить их по буквенным группам аля А-В с таким расчетом например что если много фамилий начинающихся на А, В, Д, Е, Ц, и мало на остальные буквы, то группы должны сформироваться А-Б, В-Д, Е-Ж, З-Ц, Ч-Я. Мучаюсь с этим уже долго. Никак не могу придумать нормальный алгоритм... Единственное что я пока смог сделать это просто раскидать их на группы типа [ ( 'А' , [ 'Аааа', 'Аббб', ] ), ]. Также уточню что неважны буквы идущие после первой. Главное распределить по первой букве. Ну и я это делаю на питоне, но буду рад решению на любом языке (если конечно смогу его понять :D).

П.С. Вот, если поможет что я смог накидать. Это конечно не то что нужно, но кажется я на правильном пути...
def distribute(employees):
    letters = []

    for i in range(ord('А'), ord('Я') + 1):
        c = chr(i)
        c_employees = [e for e in employees if e[0].upper() == c]
        letter = (c, c_employees)
        letters.append(letter)

    return letters


upd. Кажется решил.

def flatten(sequence):
    for item in sequence:
        if isinstance(item, collections.Iterable) and not isinstance(item, (str, bytes)):
            yield from flatten(item)
        else:
            yield item

def distribute_by_letters(employees):
    letters = []
    count = 0

    for i in range(ord('А'), ord('Я') + 1):
        c = chr(i)
        c_employees = [e for e in employees if e[0].upper() == c]
        letter = (c, c_employees)
        letters.append(letter)

        if len(c_employees) != 0:
            count += 1

    avg = len(employees) / count
    return (letters, round(avg))

def distribute_by_groups(letters_info):
    letters, avg = letters_info
    groups = []
    i = 0

    while i < len(letters):
        group_employees = []
        j = i
        count = 0

        while count < avg and j < len(letters):
            group_employees.append(letters[j])
            count += len(letters[j][1])
            j += 1

        empty_letters = itertools.takewhile(lambda l: len(l[1]) == 0, letters[j:])
        group_employees.extend(list(empty_letters))

        begin_letter = letters[i][0]
        end_letter = group_employees[-1][0]
        group_name = '%s-%s' % (begin_letter, end_letter)

        i += len(group_employees)
        group_employees = [l[1] for l in group_employees]
        group_employees = list(flatten(group_employees))
        group = (group_name, group_employees)
        groups.append(group)

    return groups
  • Вопрос задан
  • 993 просмотра
Решения вопроса 1
SilenceOfWinter
@SilenceOfWinter
та еще зажигалка...
Допустим есть 100 человек, их фамилии начинаются на 5 букв т.е. в среднем на букву должно быть 20 чел. Делаем цикл с проверкой если в следующей букве меньше 20 чел, значит можно объединять.
Ответ написан
Пригласить эксперта
Ответы на вопрос 2
sergey-gornostaev
@sergey-gornostaev Куратор тега Python
Седой и строгий
Выкатил для теста из доменного каталога список фамилий всех сотрудников (550 человек) в файл users.txt.

def chunks(l, n):
    for i in range(0, len(l), n):
        yield l[i:i + n]

#Считываем, убираем дубликаты, сортируем
names = sorted({line.strip() for line in open('users.txt', encoding='utf-8')})
#Разбиваем на примерно равные части и генерируем словарь,
#ключом которого служат первая буква первой и последней фамилии в списке.
catalog = {'{}-{}'.format(item[0][0], item[-1][0]):item for item in chunks(names, 100)}

4c0a4e2e5d604df1adf25ef243f2dec6.png 
Обновление: Предыдущий вариант допускает пересечение групп. Поэтому я накидал другой.
from itertools import groupby
from operator import itemgetter

# Разбиваем на группы по первой букве
def chunks(items):
    for letter, names in groupby(sorted(items), key=itemgetter(0)):
        yield list(names)


# Сливаем вместе группы меньше min_len в группы не больше max_len
def reshape(items, min_len, max_len):
    buffer = []
    for item in items:
        if len(buffer) >= max_len:
            yield sorted(buffer)
            buffer = []
        if len(item) <= min_len:
            buffer += item
        else:
            yield item
    yield sorted(buffer)

            
#Считываем и сортируем
names = sorted(line.strip() for line in open('users.txt', encoding='utf-8'))
#Разбиваем на примерно равные части
groups_list = reshape(chunks(names), 50, 100)
#генерируем словарь ключом которого служат первая буква первой и последней фамилии в списке
catalog = {'{}-{}'.format(item[0][0], item[-1][0]):item for item in groups_list}
Ответ написан
sgjurano
@sgjurano
Разработчик
Посчитайте сколько фамилий начинается с каждой из букв. Затем, начиная с буквы А, объединяйте буквы пока добавление следующей буквы не вызовет переход за границу выбранного вами размера группы. Начиная с этой буквы формируйте следующую группу, так вплоть до конца алфавита. Число групп можно определить как общее число фамилий, поделенное на целевой размер группы.
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы