【问题标题】:Python: How to generate a range skipping every 4th number?Python:如何生成每第四个数字跳过的范围?
【发布时间】:2018-08-03 00:57:48
【问题描述】:

我需要创建一个范围,从 5 开始,每隔 4 个数字跳过一次。例如,如果范围 a 是 1-20,则将排除数字 5、9、13、17。

a = [1, 2, 3, 4, 6, 7, 8, 10, 11, 12, 14, 15, 16, 18, 19, 20]

我尝试创建一个常规范围,然后创建一个包含我要跳过的数字的第二个范围,然后从第一个范围中删除第二个范围。

a = list(range(1,21))
b = list(range(5,21,4))
for x in b:
   if x in a:
      a.remove(x)

这可行,但不适用于很大的范围。 有没有更有效的方法?

【问题讨论】:

  • @RushabhMehta 不太重复。
  • 范围根据定义仅支持开始、停止、步进。您需要保留范围 API 的哪些部分?大小? O(1) 成员资格? O(1) 内存?逆转?切片?

标签: python python-3.x


【解决方案1】:

解决方案:

为了提高效率,我建议使用这样的生成器表达式:

r = (x for x in range(1,21) if x not in range(5,21,4))

或者,等效地,并且不需要写两次上限:

r = (x for x in range(1,21) if x == 1 or x % 4 != 1)

您可以像使用普通序列(列表/元组)*一样使用此生成器,如果绝对需要,可以将生成器转换为带有list() 的列表。

理由:

这种方法的优点是它不需要将所有项目都存储在内存中,因此您可以将上限设置为任意大而不会出现任何性能问题。


*(有一些注意事项,如下面的评论者所述。例如,如果您想要快速的成员资格检查,最好分别使用这两个范围)

【讨论】:

  • 生成器不同于范围对象。范围是大小 (len) 并支持 O(1) 成员资格测试。
  • 赞成第二个,我实际上发现它更灵活,也可以理解,但这可能只是我的扭曲思想:-)
【解决方案2】:

使用set 和另一个列表理解:

a = range(1, 21)
b = set(range(5, 21, 4))

[i for i in a if i not in b]
# [1, 2, 3, 4, 6, 7, 8, 10, 11, 12, 14, 15, 16, 18, 19, 20]

您可以从第二个范围中删除 set,但我发现这比设置包含检查要慢:

函数

def chris(m):
  a = range(1, m)
  b = set(range(5, m, 4))
  return [i for i in a if i not in b]

def chris2(m):
  a = range(1, m)
  b = range(5, m, 4)
  return [i for i in a if i not in b]


def ollin(m):
  return list(x for x in range(1,m) if x not in range(5,m,4))

def ollin2(m):
  return list(x for x in range(1,m) if x == 1 or x % 4 != 1)

def smac(m):
  return [v for i, v in enumerate(range(1,m)) if i == 0 or i % 4 != 0]

设置

from timeit import timeit

import pandas as pd
import matplotlib.pyplot as plt

res = pd.DataFrame(
       index=['chris', 'chris2', 'ollin', 'ollin2', 'smac'],
       columns=[10, 50, 100, 500, 1000, 5000, 10000],
       dtype=float
)

for f in res.index: 
    for c in res.columns:
        stmt = '{}(c)'.format(f)
        setp = 'from __main__ import c, {}'.format(f)
        res.at[f, c] = timeit(stmt, setp, number=50)

ax = res.div(res.min()).T.plot(loglog=True) 
ax.set_xlabel("N"); 
ax.set_ylabel("time (relative)");

plt.show()

【讨论】:

  • 不需要b = set(range(5, 21, 4)),范围对象对整数进行恒定时间成员资格测试
  • 如果我只使用range(5, 21, 4),我的时间会变慢(甚至包括创建集合的开销)
  • 你在使用 Python 3 吗?你是怎么计时的?无论如何,根据各种情况,它会更慢或更快,但两者都应该是恒定时间
  • 我会发布我的时间安排
  • @juanpa.arrivillaga 我确认:set 的速度是range 的两倍。
【解决方案3】:

您可以使用此列表推导:

>>> print ([v for i, v in enumerate(range(1,21)) if i == 0 or i % 4 != 0])
[1, 2, 3, 4, 6, 7, 8, 10, 11, 12, 14, 15, 16, 18, 19, 20]

【讨论】:

    【解决方案4】:

    也许有一定的差异?

    numItems = 21
    r1 = set(range(1,numItems))
    r2 = set(range(5,numItems,4))
    
    r3 = list(r1 - r2)
    

    【讨论】:

    • 我对使用集合也有同样的想法,但不认为它会以递增的顺序保留整数,但惊讶地发现顺序被保留了!列表(集((5,4,3,2,1)))= [1, 2, 3, 4, 5]!然而 list(set(('y', 'c', 'r', 'd'))) = ['d', 'c', 'y', 'r'] 很奇怪......
    【解决方案5】:

    如果我们考虑numpy

    import numpy as np 
    a=np.arange(1,21)
    a[np.logical_or(a%4!=1,a==1)]
    Out[209]: array([ 1,  2,  3,  4,  6,  7,  8, 10, 11, 12, 14, 15, 16, 18, 19, 20])
    

    【讨论】:

      【解决方案6】:

      编写自己的生成器函数可能会很有用:

      def weird_range():
         for i in range(1, 21):
            if i < 5 or i % 4 != 1:
               yield i
      
      >>> list(weird_range())
      [1, 2, 3, 4, 6, 7, 8, 10, 11, 12, 14, 15, 16, 18, 19, 20]
      

      这会让你做各种奇怪的序列。


      另一种方法是创建自己的范围对象:

      class WeirdRange:
         def __contains__(self, val):
            return val < 5 or val % 4 != 1
      
      >>> list(_ for _ in range(1,21) if _ in WeirdRange())
      [1, 2, 3, 4, 6, 7, 8, 10, 11, 12, 14, 15, 16, 18, 19, 20]
      

      或者您可以创建自己的迭代器:

      class WeirdIterator:
      
         def __init__(self):
            self._idx = 0
      
         def __iter__(self):
            return self
      
         def __next__(self):
            self._idx += 1
            if self._idx >= 5  and  self._idx % 4 == 1:
               self._idx += 1
            if self._idx > 20:
               raise StopIteration()
            return self._idx
      
      >>> list(WeirdIterator())
      [1, 2, 3, 4, 6, 7, 8, 10, 11, 12, 14, 15, 16, 18, 19, 20]
      

      【讨论】:

        【解决方案7】:
        
        a = list(range(1,21))
        
        for x in a:
            if x!=1 and x%4==1:
                continue
            print(x)
        

        【讨论】:

        • 您的答案不正确-请仔细检查并修正
        • 虽然此代码可能会回答问题,但提供有关此代码为何和/或如何回答问题的额外上下文可提高其长期价值。
        猜你喜欢
        • 1970-01-01
        • 2014-08-31
        • 1970-01-01
        • 2014-02-20
        • 1970-01-01
        • 2018-06-17
        • 2021-12-10
        • 2021-04-27
        • 2014-07-28
        相关资源
        最近更新 更多