测试列表修改是否是周期性的

时间:2014-03-19 23:25:32

标签: python list

我有一个list个整数,它在一个循环中不断被修改,我需要判断它的内容是否在经过一定量的迭代后重复,以打破循环。

如果没有,list最终将修改为[],或者当达到某个迭代限制时,循环终止。到目前为止我的解决方案:

def modify(numlist, a, b):
    numlist = [(x * a) for x in numlist]
    for i in range((len(numlist) - 1), 0, -1):
        if numlist[i] >= b: numlist[i - 1] += numlist[i] // b
        numlist[i] = numlist[i] % b
    numlist[0] %= b
    while numlist[-1] == 0:
        numlist.pop(-1)
        if numlist == []: break 

numlist = [1, 2, 3, 4, 5]
listHistory = [numlist]
a, b = someValue, anotherValue

n = 0
while numlist != [] and n <= limit:
    modify(numlist, int(a), int(b))
    listHistory.append(numlist)
    if numlist in listHistory: break
    n += 1

limit可能非常大(大约10**6 - 10**7)并且检查当前numlist对所有以前的版本变得非常慢。

是否有更有效的方法来执行此操作,甚至是一种方法来预先确定修改是否是列表初始内容的周期性,并且给定ab

3 个答案:

答案 0 :(得分:1)

好的,有点东西。 如果您查看列表中的最后一个元素,请调用它m。它会发生什么变得乘以a然后采用模数b。它永远不会与任何其他元素混合,因此如果列表的配置必须重复,则必须遵循以下内容:

m*a^n=m modulo b
<==>a^n=1 modulo b
<  >a^(n+1)=a modulo b

这是一个可以使用Fermats little theorem的问题 如果a和b是互质,那么

a^phi(b)=1 modulo b

其中phi是Eulers totient函数。

因此,这会大大减少您必须在历史记录中存储的列表配置量。您只需每隔phi(b)步骤存储一次。

我在这里找到了一个phi的实现: Computing Eulers Totient Function

更新:

好的,如果您要+= list[i] % b而不是+= list[i] // b,我找到了一个快速解决方案。否则,在最坏的情况下你需要b^4*phi(b)个步骤

UPDATE2:

我重写了C中的代码(见下文),使其更快,并实现了@ user2357112提出的“乌龟和野兔”算法。这样我每秒可以检查几百万个循环,这应该比python实现更快。 我尝试了一些不同的价值组合:

a  b    steps     b^4*phi(b)  (b/GCD(b,a))^4*phi(b/GCD(n,a))    (b/GCD(b,a))^4*phi(b/GCD(n,a))/steps
2  37   67469796  67469796    67469796                          1
3  37   33734898  67469796    67469796                          2
4  37   33734898  67469796    67469796                          2
5  37   67469796  67469796    67469796                          1
6  37   7496644   67469796    67469796                          9
7  37   16867449  67469796    67469796                          4
36 37   3748322   67469796    67469796                          18
2  36   39366     20155392    629856                            16
3  36   256       20155392    82944                             27648
4  36   19683     20155392    39366                             2
5  36   5038848   20155392    20155392                          4

所以你看到它的发展方向:周期长度似乎总是(b/GCD(b,a))^4*phi(b/GCD(n,a))的除数,所以最坏的情况是(b/GCD(b,a))^4*phi(b/GCD(n,a))步骤被怀疑

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void modify(int *, int, int);
void printl(int * );
int main(int argc, const char*argv[])
{
  int l[5]={1,2,3,4,5};
  int lz[5]={1,2,3,4,5};
  int i=1,a,b,n;
  if (argc<4) {
    printf("Not enough arguments!!\n");
    exit(1);
  }
  a=atoi(argv[1]);
  b=atoi(argv[2]);
  n=atoi(argv[3]);
  modify(l,a,b);
  while (i<n) {
    modify(l,a,b);
    modify(l,a,b);
    modify(lz,a,b);
    i++;
    if (memcmp(l,lz,sizeof(l))==0) {
      printf("success!\n");
      break;
    }
    if (i%1000000==0) printf("Step %d.000.000\n",i/1000000);
  }
  printf("Final step:  %d\n",i);
  printl(l);
  printl(lz);
  return 0;

}
void  modify(int * li, int a, int b) {
  int i=0;
  while (i<=4) {
   li[i]*=a;
   i++;
  }
  i=4;
  while (i>=1) {
    if (li[i]>=b) {
      li[i-1]+=li[i]/b;
    }
    li[i]=li[i]%b;
    i--;

  }
  li[0]=li[0]%b;
}
void printl(int * li) {
  printf("l=(%d,%d,%d,%d,%d)\n",li[0],li[1],li[2],li[3],li[4]);

答案 1 :(得分:0)

首先请允许我说,所有列表都是定期的,如果您考虑足够长的时间段。

那就是说,这可能是使用布隆过滤器的好地方,EG: https://pypi.python.org/pypi/drs-bloom-filter/

布隆过滤器是可以非常快速地执行集合成员资格测试的集合,并且可以在不实际存储元素数据的情况下向集合添加内容。这意味着它是概率性的,但您可以调整概率。因此,您可以使用布隆过滤器测试进行快速检查,并在使用布隆过滤器检测到匹配时,使用慢速+确定性算法确认结果。

用法如下:

In [1]: import bloom_filter_mod

In [2]: bloom_filter = bloom_filter_mod.Bloom_filter(1000000, 0.01)

In [3]: for i in range(10):
   ...:     bloom_filter.add(i)
   ...:

In [4]: for i in range(0, 20, 2):
   ...:     if i in bloom_filter:
   ...:         print('{} present'.format(i))
   ...:
0 present
2 present
4 present
6 present
8 present

1000000是您要在过滤器中存储的元素的最大数量,0.01是满月时误报的最大概率。

因此,您可以在过滤器中“存储”每个子序列,并快速检测重复发生。

答案 2 :(得分:0)

您的list(顺便说一下,您真正​​应该重命名)会在基座b**something中存储一个数字b。每次运行modify会将数字乘以a,然后在表示结尾处截断零。

拨打列表n最初代表的号码,并调用列表l的原始长度。如果此过程终止,它将在第一次迭代k执行此操作,以便b**ln * a**k,这将仅在b**l / gcd(n, b**l)的所有素因子时出现是a的因素。这很容易确定:

def all_prime_factors_of_first_divide_second(a, b):
    while a != 1:
        factor = gcd(a, b)
        if factor == 1:
            return False
        while not a % factor:
            a //= factor
    return True