Ограничение вхождений строк в списке на основе префикса - PullRequest
0 голосов
/ 16 декабря 2018

Итак, код, над которым я работаю, предназначен для бота IRC, и я хочу реализовать способ ограничения каналов на основе опции сервера CHANLIMIT.

Параметр CHANLIMIT представляет собой список ограничений с префиксом и пределом, разделенных :, но если после : ничего нет, то ограничения нет.

Решение ниже работает, но я ищу какие-либо улучшения в нем.

result = ['#+:2', '&:']
channels = ['#test1', '#test2', '+test3', '&test4']

prefix_groups = [(prefix, []) for prefix in result]
channel_groups = {k: v for (k, v) in prefix_groups}
for channel in channels:
    for group in prefix_groups:
        if channel[0] in group[0]:
            channel_groups[group[0]].append(channel)
            break

for prefix, channels in channel_groups.items():
    limit = prefix.split(':')[1]
    if limit:
        if len(channels) > int(limit):
            channel_groups[prefix] = channels[:int(limit)]

channels = [
    channel for chanlist in channel_groups.values() for channel in chanlist]

print(channels)

Ответы [ 5 ]

0 голосов
/ 22 декабря 2018

Но давайте сделаем небольшое резервное копирование.Если я правильно понимаю, в конце вы хотите список элементов channels_to_test, который соответствует префиксам и не превышает предела префикса, если он есть.Это поведение фильтрации можно реализовать в генераторе :

Решение 4

import re

results = ['#+:2', '&:']
channels_to_test = ['#test1', '#test2', '+test3', '&test4',
                    '#test5', '!test5', '&test6', '&test7',
                    '+test8', '#test9']

def filter_channel_list(prefixes_to_match, input_channel_list):
    prefix_pattern = re.compile(r'^(.*):(\d+)?$')
    prefix_matches = (prefix_pattern.match(x) for x in prefixes_to_match)
    prefix_split = (x.groups() for x in prefix_matches)
    prefixes_remaining = {x: (int(y) if y is not None else None)
                          for (x, y) in prefix_split}

    for current_channel in input_channel_list:
        for (prefix, nb_left) in prefixes_remaining.items():
            if current_channel[0] in prefix:
                if nb_left is None:
                    yield current_channel
                    break
                else:
                    if nb_left > 0:
                        prefixes_remaining[prefix] -= 1
                        yield current_channel
                        break
                    else:
                        continue

result_channels = list(filter_channel_list(results, channels_to_test))

print(result_channels)

Вот некоторые комментарии:

  • В этом решении я отменил требование, чтобы элемент channels_to_test соответствовал только одному элементу results.Это из-за операторов break, помещенных в генератор.
  • Мы имеем словарь с начальными пределами для каждого results и уменьшаем его каждый раз, когда встречаем совпадение с элементом channels_to_test,Если это значение станет 0, генератор перейдет к следующему значению.Это то, что делает (необязательно в данном случае) оператор continue.
0 голосов
/ 22 декабря 2018

Когда мне нужно извлечь некоторую информацию из строк, я обычно использую регулярные выражения .Таким образом, расширяя Решение 2 мы можем получить:

Решение 3

import re
import itertools

results = ['#+:2', '&:']
channels_to_test = ['#test1', '#test2', '+test3', '&test4',
                    '#test5', '!test5', '&test6', '&test7',
                    '+test8', '#test9']

prefix_pattern = re.compile(r'^(.*):(\d+)?$')
prefix_matches = (prefix_pattern.match(x) for x in results)
prefix_split = (x.groups() for x in prefix_matches)
channel_groups = {group: [channel for channel in channels_to_test
                                  if channel[0] in group[0]]
                  for group in prefix_split}

prefix_existing_limit = ((x, int(x[1])) for x in channel_groups
                         if x[1] is not None)
modified_channel_groups = {prefix_group: channel_groups[prefix_group][:limit]
                           for (prefix_group, limit) in prefix_existing_limit}

channel_groups.update(modified_channel_groups)

result_channels = list(itertools.chain.from_iterable(channel_groups.values()))

print(result_channels)
0 голосов
/ 22 декабря 2018

Мы можем пойти дальше:

Решение 2

import itertools

results = ['#+:2', '&:']
channels_to_test = ['#test1', '#test2', '+test3', '&test4',
                    '#test5', '!test5', '&test6', '&test7',
                    '+test8', '#test9']

channel_groups = {group: [channel for channel in channels_to_test
                                  if channel[0] in group]
                  for group in results}

limit = lambda prefix: prefix.split(':')[1]

modified_channel_groups = {prefix: channels[:int(limit(prefix))]
                           for (prefix, channels) in channel_groups.items()
                           if limit(prefix)}

channel_groups.update(modified_channel_groups)

result_channels = list(itertools.chain.from_iterable(channel_groups.values()))

print(result_channels)

Но здесь я должен сделать предположение: я предположил, что канал может соответствовать не более одногоэлемент results.Другими словами, никакие два элемента results не будут соответствовать одному каналу.Скажите, если это не так в вашей ситуации.

Вот изменения, которые я сделал:

  • Я создал channel_groups, используя словарное понимание, где значение каждого элемента равнопонимание списка
  • Я создал modified_channel_groups, который содержит элементы channel_groups, которые были сокращены
  • I обновлены элементы channel_groups с элементамиmodified_channel_groups
  • Я создал лямбда-выражение , чтобы я мог включить его в определение modified_channel_groups.
  • Я извлек result_channels, используя itertools.chain.from_iterable()
0 голосов
/ 22 декабря 2018

Вы можете даже дальше, чтобы создать свой ответ channel_groups напрямую, но его становится труднее читать.Поэтому я не рекомендую это:

Решение 2a

import itertools

results = ['#+:2', '&:']
channels_to_test = ['#test1', '#test2', '+test3', '&test4',
                    '#test5', '!test5', '&test6', '&test7',
                    '+test8', '#test9']

limit = lambda prefix: prefix.split(':')[1]

channel_groups = {group: [channel for channel in channels_to_test if channel[0] in group][:int(limit(group)) if limit(group) else None]
                  for group in results}

result_channels = list(itertools.chain.from_iterable(channel_groups.values()))

print(result_channels)

Несколько замечаний:

  • channel_groupsсоздается как в Решение 2 , но каждое значение словаря является списком (полученным из понимания), который нарезается целочисленным значением текущего group или None, что будет означать принятие всехзначения.
0 голосов
/ 22 декабря 2018

Есть много способов решения этой проблемы.Делая некоторые минимальные упрощения, вы можете получить что-то подобное:

Решение 1

results = ['#+:2', '&:']
channels_to_test = ['#test1', '#test2', '+test3', '&test4',
                    '#test5', '!test5', '&test6', '&test7',
                    '+test8', '#test9']

channel_groups = {k: [] for k in results}
for channel in channels_to_test:
    for group in results:
        if channel[0] in group:
            channel_groups[group].append(channel)
            break

for prefix, channels in channel_groups.items():
    limit = prefix.split(':')[1]
    if limit:
        limit = int(limit)
        channel_groups[prefix] = channels[:limit]

result_channels = [
    channel for chanlist in channel_groups.values() for channel in chanlist]

print(result_channels)

Вот изменения, которые я сделал:

  • Я создал channel_groups напрямую вместо создания списка кортежей (prefix_groups) и затем использовал его для создания channel_groups
  • Я итерировал group по results вместо итерации по prefix_groups
  • Я не проверял, если len(channels) > int(limit), потому что даже если длина channels меньше или равна limit, channels[:limit] вернет все channels
...