【问题标题】:Limit occurences of strings in a list based on prefix根据前缀限制列表中字符串的出现
【发布时间】:2018-12-15 21:15:28
【问题描述】:

所以我正在编写的代码是针对 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)

【问题讨论】:

  • 下面的解决方案应该可以工作...你能澄清一下它是否能做你想做的事吗?
  • 刚刚进行了编辑,解决方案确实有效,但我觉得应该有更好的方法来做到这一点。

标签: python python-3.x irc


【解决方案1】:

我们可以走得更远:

解决方案 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 元素
  • updated 的元素channel_groupsmodified_channel_groups 的元素
  • 我创建了一个lambda expression,以便可以将它包含在modified_channel_groups 的定义中。
  • 我使用itertools.chain.from_iterable()提取了result_channels

【讨论】:

    【解决方案2】:

    有很多方法可以解决这个问题。做一些最小的简化,你可能会得到类似的东西:

    解决方案 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
    • 我在results 上迭代group 而不是在prefix_groups 上迭代
    • 我没有检查len(channels) > int(limit)是否是因为即使channels的长度小于或等于limitchannels[:limit]也会返回所有channels

    【讨论】:

      【解决方案3】:

      您甚至可以进一步直接创建答案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 的创建方式与 Solution 2 类似,但字典的每个值都是一个列表(从理解中获得),它被当前 groupNone 的整数值切片,这将意思是取所有的值。

      【讨论】:

        【解决方案4】:

        当我必须从字符串中提取一些信息时,我倾向于使用regular expressions。所以扩展Solution 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)
        

        【讨论】:

        • 我认为解决方案 2 看起来像是许多解决方案中的清洁剂,所以在这种情况下,正则表达式会提供很多好处,还是这只是您的风格?
        • 好吧,你可以说这是我的风格,在这种情况下可能有点矫枉过正。您只需在: 上拆分一个字符串即可获取您的信息。但是,如果您对提取组有更精确的要求,正则表达式可能会派上用场(例如,在您的结果描述不同的情况下)正则表达式使在文本中查找模式变得更加容易。但是,您可以做适合自己的事情。
        【解决方案5】:

        但是让我们备份一点。如果我理解正确,最后你想要一个 channels_to_test 的元素列表,它与前缀匹配并且不超过前缀的限制(如果有的话)。您可以在 generator 中实现此过滤行为:

        解决方案 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)
        

        这里有一些cmets:

        • 在这个解决方案中,我已经放回了channels_to_test 的一个元素将只匹配results 的一个元素的要求。这是因为在生成器中放置了break 语句。
        • 我们所做的是为每个 results 创建一个包含初始限制的字典,并在每次遇到与 channels_to_test 元素匹配时递减。如果该值变为 0,则生成器将跳到下一个值。这就是(在这种情况下是可选的)continue 语句的作用。

        【讨论】:

        • 首先我想说的是,你对所有这些答案的表现都很棒,比我从任何人那里得到的都多。现在,您假设通道只会匹配其中一个结果是正确的,因为通道只能有一个前缀,但应用此前缀时,总是会调用它来生成列表,使用生成器也是如此与您提供的其他解决方案相比,这样的解决方案有什么好处吗?
        • 谢谢!我想说的是,一般来说,使用生成器提供了一种方法,可以将你如何确定 for 循环的值与你对它们所做的事情解耦。在您的特定情况下,一个优势可能是您仅在 channels_to_test 上迭代一次,而不是创建一个包含您最终必须展平以提取答案的列表的字典,但更重要的是您选择一个解决方案能够在六个月内了解您何时返回此代码
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2023-04-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-03-05
        • 1970-01-01
        相关资源
        最近更新 更多