Python算法挑战?

时间:2011-07-20 22:29:43

标签: python algorithm

我有一个python函数(称之为myFunction),它作为输入一个数字列表,并且在复杂计算后,返回结果计算(数字)。

该功能如下所示:

def myFunction( listNumbers ):
    # initialize the result of the calculation
    calcResult = 0

    # looping through all indices, from 0 to the last one
    for i in xrange(0, len(listNumbers), 1):
        # some complex calculation goes here, changing the value of 'calcResult'

    # let us now return the result of the calculation
    return calcResult

我测试了这个功能,它按预期工作。

通常情况下,myFunction会提供一个listNumbers参数,其中包含 5,000,000 个元素。正如您所料,计算需要时间。我需要这个功能尽可能快地运行

接下来是挑战:假设现在时间是凌晨5点,listNumbers中只包含 4,999,999 值。意思是,它的最后一个值尚不可用。此值仅在早上6点时可用。

显然,我们可以执行以下操作第一模式):等到早上6点。然后,将最后一个值附加到listNumbers,然后运行myFunction。这个解决方案有效,但需要一段时间才能myFunction返回我们的计算结果(因为我们需要从第一个元素处理整个数字列表上)。请记住,我们的目标是在早上6点之后尽快获得结果

我正在考虑一种更有效的解决方法(第二模式):因为(早上5点)我们有listNumbers,其中 4,999,999 值,让我们立即开始运行myFunction。让我们处理尽我们所能(记住,我们还没有最后一段数据),然后 - 恰好在早上6点 - “插入”新数据 - 并生成计算结果。这应该明显更快,因为大部分处理将在早上6点之前完成,因此,我们只需要处理新数据 - 这意味着计算结果应立即可用早上6点以后

我们假设没有办法让我们检查myFunction的代码或修改它。是否有任何编程技巧/设计理念,我们可以myFunction 原样,并使用它做一些事情(不更改其代码),以便我们可以让它在第二模式中运行,而不是第一模式

请不要建议使用c++ / numpy + cython / parallel computing等来解决此问题。这里的目标是查看是否有任何编程技术设计模式可以轻松用于解决此类问题。

8 个答案:

答案 0 :(得分:10)

您可以使用generator作为输入。只有在有可供处理的数据时,生成器才会返回。

更新:感谢您的精彩评论,我想删除此条目:)

class lazylist(object):
    def __init__(self):
        self.cnt = 0
        self.length = 5000000

    def __iter__(self):
        return self

    def __len__(self):
        return self.length

    def next(self):
        if self.cnt < self.length:
            self.cnt += 1
            #return data here or wait for it
            return self.cnt #just return a counter for this example
        else:
            raise StopIteration()

    def __getitem__(self, i):
        #again, block till you have data.
        return i+1 #simple counter

myFunction(lazylist())

更新:从评论和其他解决方案中可以看出,你的循环结构和len调用引起了很多麻烦,如果你可以消除它,你可以使用更优雅的解决方案。 for e in lienumerate是pythonic的方式。

答案 1 :(得分:5)

按“数字列表”,您的意思是实际的内置list类型吗?

  • 如果没有,那很简单。 Python使用duck-typing,因此传递任何支持迭代的序列都可以。使用yield关键字传递generator

    def delayed_list():
        for val in numpy_array[:4999999]:
            yield val
        wait_until_6am()
        yield numpy_array[4999999]
    

然后,

    myFunction(delayed_list())
  • 如果是,那就更难了:)

另外,请查看PEP8以获取推荐的Python代码样式:

  • 括号内没有空格
  • my_function代替myFunction
  • for i, val in enumerate(numbers):代替for i in xrange(0, len(listNumbers), 1):等。

答案 2 :(得分:4)

子类列表,以便当函数尝试读取它阻塞的最后一个值,直到另一个线程提供该值。

import threading
import time

class lastblocks(list):
    def __init__(self,*args,**kwargs):
        list.__init__(self,*args,**kwargs)
        self.e = threading.Event()
    def __getitem__(self, index):
        v1 = list.__getitem__(self,index)
        if index == len(self)-1:
            self.e.wait()
            v2 = list.__getitem__(self,index)
            return v2
        else:
            return v1


l = lastblocks(range(5000000-1)+[None])

def reader(l):
    s = 0
    for i in xrange(len(l)):
        s += l[i]
    print s

def writer(l):
    time.sleep(10)
    l[5000000-1]=5000000-1
    l.e.set()
    print "written"

reader = threading.Thread(target=reader, args=(l,))
writer = threading.Thread(target=writer, args=(l,))
reader.start()
writer.start()

打印:

written
12499997500000

for numpy:

import threading
import time

import numpy as np

class lastblocks(np.ndarray):
    def __new__(cls, arry):
        obj = np.asarray(arry).view(cls)
        obj.e = threading.Event()
        return obj
    def __array_finalize__(self, obj):
        if obj is None: return
        self.e = getattr(obj, 'e', None)

    def __getitem__(self, index):
        v1 = np.ndarray.__getitem__(self,index)
        if index == len(self)-1:
            self.e.wait()
            v2 = np.ndarray.__getitem__(self,index)
            return v2
        else:
            return v1


l = lastblocks(np.asarray(range(5000000-1)+[None]))

def reader(l):
    s = 0
    for i in xrange(len(l)):
        s += l[i]
    print s

def writer(l):
    time.sleep(10)
    l[5000000-1]=5000000-1
    l.e.set()
    print "written"

reader = threading.Thread(target=reader, args=(l,))
writer = threading.Thread(target=writer, args=(l,))
reader.start()
writer.start()

答案 3 :(得分:1)

当其他答案(生成器和模拟对象)中建议的技术不可用时,

Memory protection barriers是解决此类问题的一般方法。

内存屏障是一种硬件功能,当程序试图访问禁止的内存区域(通常在页面级别可控)时会导致中断。然后,中断处理程序可以采取适当的操作,例如暂停程序直到数据准备就绪。

因此,在这种情况下,您将在列表的最后一页设置一个屏障,中断处理程序将等到06:00,然后才允许程序继续。

答案 4 :(得分:1)

您可以创建自己的迭代器来迭代5,000,000个元素。这样可以做任何你需要做的事情来等待最后的元素(不能具体,因为问题中的例子相当抽象)。我假设您不关心代码挂到6:00,或者知道如何在后台线程中执行此操作。

有关编写自己的迭代器的更多信息位于http://docs.python.org/library/stdtypes.html#iterator-types

答案 5 :(得分:1)

我对于无法调查myFunction感到有些困惑。至少你必须知道你的列表是否被索引迭代或访问。您的示例可能会建议使用索引。如果要利用迭代器/生成器,则必须进行迭代。我知道你说myFunction是不可更改的,但只是想指出,大多数pythonic版本将是:

def myFunction( listNumbers ):
    calcResult = 0

    # enumerate if you really need an index of element in array
    for n,v in enumerate(listNumbers):
        # some complex calculation goes here, changing the value of 'calcResult'

    return calcResult

现在你可以开始介绍好主意了。一个可能是使用您自己的类型包装列表并提供__iter__方法(作为生成器);你可以返回值,如果可以访问,等待更多数据,如果你期望任何数据或在屈服最后一个元素后返回。

如果您必须按索引访问列表,则可以使用{D}中的__getitem__示例。但它有一个限制,你必须提前知道数组的大小。

答案 6 :(得分:1)

有一个更简单的生成器解决方案:

def fnc(lst):
    result = 0
    index = 0
    while index < len(lst):
        while index < len(lst):
            ... do some manipulations here ...
            index += 1
        yield result

lst = [1, 2, 3]
gen = fnc(lst)
print gen.next()

lst.append(4)
print gen.next()

答案 7 :(得分:0)

难道你不能简单地做这样的事情:

processedBefore6 = myFunction([1,2,3]) # the first 4,999,999 vals.

while lastVal.notavailable:
  sleep(1)

processedAfter6 = myFunction([processedBefore6, lastVal])

如果效果是线性的(步骤1 - >步骤2 - >步骤3等),这应该允许您预先做尽可能多的工作,然后在可用时捕获最终值并完成。