一个循环?蟒蛇

时间:2012-06-12 06:06:17

标签: python

所以我写了这个函数给出了可能的数字,它必须找到构成给定数字的可能数字中的两个数字。但是,我仍然在学习Python(一种非常好的语言),所以我只能使用一组有限的函数。

我创建了这个函数:

def sumPair(theList, n):

    theList = charCount(theList) #charCount is a function i made to convert the list into a dictionary
    for i in theList:
        for a,b in theList.iteritems():
            print a,b
            if a + i == n:
                if theList[b] > 1:
                    return [i, b]
                if a != i:
                    return [i, b]
        return "[]"
print sumPair([6,3,6,8,3,2,8,3,2], 11)   

就像我说的,它找到了两个加起来给定数字的数字。 charCount是我编写的一个函数,它将数组添加到字典中。

在这个程序中,我确保该值大于1,以防添加的数字相同。有时如果它检查总和为10并且你给它一个5的数字,它只会将5添加到自身并返回10.这就是为什么if theList[b] > 1: 在那里。

我为什么来这里?我的导师对两个循环不满意。我花了5个小时进行故障排除,无处可去。我需要将这个程序转换成一个循环程序。

我整天都花在这上面,我不是想让你做我的作业,我真的被困住了,我需要你的帮助。我听说我应该检查一下是否有钥匙才能完成。

7 个答案:

答案 0 :(得分:9)

教师可能不满意您的算法需要的时间比以前长。试试这个:

for each element x in theList
  if there exists an element y in theList such that x+y = n, you have a match

你需要快速进行“if exists”测试,这就是你使用字典的方法。一个循环将构建此字典,第二个将搜索。这将花费线性时间与您的O(n ^ 2)时间。

关于5匹配自己的观点是一个很好的观点。您想使用称为multiset或bag的数据结构。阅读它,然后以这种方式实现您的代码:

for each element x in theList
  if there exists an element y in theList such that x+y == n:
    if x != y or (x == y and x occurs more than once):
      you have a match
祝你好运。

编辑因为有太多次优的解决方案,这里是简单的线性解决方案(它是线性的,因为列表中有2个循环,但循环一个接一个地循环。所以,2 * n次迭代,O(n)。它非常快。

#!/usr/bin/python2.6

from collections import defaultdict

def sum_list(l, n):
  count = defaultdict(lambda: 0)

  for x in l:      # O(n)
    count[x] += 1  # This can be done in constant time O(1)

  for x in l:      # O(n)
    y = n - x
    if count[y] > 0 and (x != y or count[y] > 1): # O(1)
      return x, y

答案 1 :(得分:4)

您可以单独检查特殊情况。 n%2表示“n mod 2”,因此0为偶数时为n

def sumPair(theList, n):
    # If n is even, check whether n/2 occurs twice or more in theList
    if n%2 == 0 and theList.count(n/2) > 1:
        return [n/2, n/2]

    theSet = set(theList)
    for i in theSet:
        if n-i in theSet:
            return [i, n-i]
    return []

答案 2 :(得分:3)

用“铅笔和纸”或者甚至只查看纸张上的数字行,我将如何用手来思考这个问题。但是,更好的解决方案最初可能看起来过于复杂,并且它们的优势可能不是首先看起来 - 请参阅gnibbler's solution(他的答案是我个人的获胜者,见下文)。

首先,您需要将一个数字与所有其他数字进行比较。然后使用其余的第二个数字等。当使用朴素方法时,使用单个处理器时无法避免两个嵌套循环。那么时间复杂度总是为O(n ^ 2),其中n是序列的长度。事实是,有些循环可能会隐藏在inlist.index()等操作中,这些操作原则上不会使解决方案更好。

想象一下数字的笛卡尔积 - 它由数字的夫妻组成。这些夫妇中有n ^ 2个,但是相对于加法操作的可交换性质,大约一半是相同的,并且其中n个是与itsef的对。这意味着您只需要检查n^2 / 2 - n对。如果它们适合测试,最好避免循环遍历不必要的对,而不是稍后进行测试:

for each first element in theList:
    for each second element in the rest of theList from the checked one on:
        if the first and the second elements give the solution:
            report the result
            possibly early break if only the first should be reported

中的其余列表使用切片,使用第一个(也可能是第二个)循环中的enumerate()来了解索引。

最小化循环中的操作始终是个好主意。想想内循环体是最多次完成的。这样,您可以在进入内循环之前计算搜索到的数字:searched = sum - first。然后第二个循环加上if可以替换为if searched in the rest of theList:

[此处出现更多完整解决方案后编辑]

这是找到第一次出现或无的O(n ^ 2)解决方案(纯Python,简单,没有图书馆,内置函数和仅切片,几行):

def sumPair(theList, n):
    for index, e in enumerate(theList):     # to know the index for the slicing below
        complement = n - e                  # we are searching for the complement
        if complement in theList[index+1:]: # only the rest is searched
            return e, complement            

print sumPair([6,3,6,8,3,2,8,3,2], 11)

[在gnibbler对切片和复制的评论之后添加]

gnibbler关于切片是正确的。切片是副本。 (问题是切片是否未使用“写入时复制”技术进行优化 - 我不知道。如果是,那么切片将是一个廉价的操作。)为了避免复制,可以使用{来完成测试{1}}允许传递起始索引的方法。唯一的奇怪的事情是,当找不到该项时,它会引发list.index()异常。这样ValueError必须替换为if complement...

try ... except

Gnibbler的评论让我更多地思考这个问题。事实是def sumPair2(theList, n): for ind, e in enumerate(theList): try: theList.index(n - e, ind + 1) return e, n - e except ValueError: pass 可以接近O(1)来测试它是否包含元素和O(n)来构造集合。对于非数字元素(其中集合类型不能实现为位数组)并不清楚。当哈希数组出现在游戏中并且可能使用其他技术解决可能的冲突时,质量取决于实现。

如有疑问,请测量。这里gnibbler的解决方案稍作修改,与其他解决方案一样:

set

问题中的原始数字用于第一次测试。当找不到解决方案时,import timeit def sumPair(theList, n): for index, e in enumerate(theList): if n - e in theList[index+1:]: return e, n - e def sumPair2(theList, n): for ind, e in enumerate(theList): try: theList.index(n - e, ind + 1) return e, n - e except ValueError: pass def sumPair_gnibbler(theList, n): # If n is even, check whether n/2 occurs twice or more in theList if n%2 == 0 and theList.count(n/2) > 1: return n/2, n/2 theSet = set(theList) for e in theSet: if n - e in theSet: return e, n - e 会导致最坏的情况:

n = 1

它在我的控制台上产生以下输出:

theList = [6,3,6,8,3,2,8,3,2]

n = 11
print '---------------------', n
print sumPair(theList, n), 
print timeit.timeit('sumPair(theList, n)', 'from __main__ import sumPair, theList, n', number = 1000)

print sumPair2(theList, n), 
print timeit.timeit('sumPair2(theList, n)', 'from __main__ import sumPair2, theList, n', number = 1000)

print sumPair_gnibbler(theList, n),
print timeit.timeit('sumPair_gnibbler(theList, n)', 'from __main__ import sumPair_gnibbler, theList, n', number = 1000)

n = 1
print '---------------------', n
print sumPair(theList, n), 
print timeit.timeit('sumPair(theList, n)', 'from __main__ import sumPair, theList, n', number = 1000)

print sumPair2(theList, n), 
print timeit.timeit('sumPair2(theList, n)', 'from __main__ import sumPair2, theList, n', number = 1000)

print sumPair_gnibbler(theList, n),
print timeit.timeit('sumPair_gnibbler(theList, n)', 'from __main__ import sumPair_gnibbler, theList, n', number = 1000)

从短的数字序列和一个特殊情况来看,从时间复杂性的角度来说,质量是不可能的。无论如何,gnibbler的解决方案赢了。

当序列包含唯一值时,gnibbler的解决方案使用最多的内存。让我们尝试更长的包含0,1,2,...,9999的序列.n等于11和3000表示具有解决方案的任务。对于n等于30000的情况,无法找到这对数字。必须检查所有元素 - 最坏的情况:

--------------------- 11
(3, 8) 0.00180958639191
(3, 8) 0.00594907526295
(8, 3) 0.00124991060067
--------------------- 1
None 0.00502748219333
None 0.026334041968
None 0.00150958864789

请注意,序列要长得多。该测试仅重复100次,以便在合理的时间内得到结果。 (除非您将时间除以theList = range(10000) n = 11 print '---------------------', n print sumPair(theList, n), print timeit.timeit('sumPair(theList, n)', 'from __main__ import sumPair, theList, n', number = 100) print sumPair2(theList, n), print timeit.timeit('sumPair2(theList, n)', 'from __main__ import sumPair2, theList, n', number = 100) print sumPair_gnibbler(theList, n), print timeit.timeit('sumPair_gnibbler(theList, n)', 'from __main__ import sumPair_gnibbler, theList, n', number = 100) n = 3000 print '---------------------', n print sumPair(theList, n), print timeit.timeit('sumPair(theList, n)', 'from __main__ import sumPair, theList, n', number = 100) print sumPair2(theList, n), print timeit.timeit('sumPair2(theList, n)', 'from __main__ import sumPair2, theList, n', number = 100) print sumPair_gnibbler(theList, n), print timeit.timeit('sumPair_gnibbler(theList, n)', 'from __main__ import sumPair_gnibbler, theList, n', number = 100) n = 30000 print '---------------------', n print sumPair(theList, n), print timeit.timeit('sumPair(theList, n)', 'from __main__ import sumPair, theList, n', number = 100) print sumPair2(theList, n), print timeit.timeit('sumPair2(theList, n)', 'from __main__ import sumPair2, theList, n', number = 100) print sumPair_gnibbler(theList, n), print timeit.timeit('sumPair_gnibbler(theList, n)', 'from __main__ import sumPair_gnibbler, theList, n', number = 100) ,否则时间无法与之前的测试进行比较。)它在我的控制台上显示以下内容:

number

对于非最坏情况,gnibbler的解决方案似乎很慢。原因是它需要经历所有序列的准备阶段。天真的解决方案在第一次通过的大约三分之一中找到了数字。什么告诉anythig是最糟糕的情况。 gnibbler的解决方案快了大约1000倍,并且对于更长的序列,差异会增加。 Gnibbler的解决方案是明显的赢家。

答案 3 :(得分:2)

我的评论:

  • 如果将列表转换为字典,请将名称从theList更改为其他名称。有一个名为theList的变量可以容纳一个字典。

  • 如果两个数字总是彼此相邻,你可以尝试编写一个循环,将索引变量i设置为0,然后递增i,检查看看theList[i] + theList[i + 1]是否等于所需的数字。

  • 如果找不到两个数字,那就更难了。最明显的方法是使用两个循环:一个依次查看每个数字,另一个查看以下数字以查看它们是否与目标相加。如果两个数字不必彼此相邻,并且教师希望您只使用一个循环,那么您将需要使用某些东西来保存列表值(如字典)或者可能使用“隐式”循环”。

“隐式循环”是什么意思? Python提供了一个运算符in,它将告诉您对象是否在Python列表中。这通过遍历列表来工作,但是你不编写循环;循环是在Python内完成的。

所以,你可以这样做:依次看每个数字。从目标值中减去数字,然后使用in查看计算值是否在列表的其余部分中。要搜索列表的其余部分,并跳过当前值,请使用“list slicing”。如果您还没有学过切片,那么您的教练可能不会寻找切片答案。

这是一个例子。如果您将i设置为0,并且您正在查看列表中的第一个条目,则值为6.目标值为11.因此计算值为(11 - 6)或5.然后您会检查是否computed_value in theList。要仅查看列表的其余部分,您可以使用切片,然后您将拥有computed_value in theList[1:]。如果您有一个索引变量i,那么您会看到computed_value = 11 - theList[i]之类的内容,然后查看是否computed_value in theList[i:]

  • 不要忘记可以使用for循环来创建从0到列表长度的索引,并使用索引索引到列表中。在Python中,通常最好使用for x in lst:x设置为列表中的连续对象,但有时您会遇到使用for i in xrange(len(lst)):然后使用{{1或} lst[i]lst[i+1]或其他。

  • 使用您老师想要的任何编码风格。如果您的老师想要“camelCase”这样的名字,比如“theList”,那就去做吧。但Python的通常样式称为“PEP 8”,该样式的变量名称是带有下划线的小写,如“the_list”。 http://www.python.org/dev/peps/pep-0008/

答案 4 :(得分:2)

纯python - 没有使用库:

def combinations(lst):
    l = lst[:] # copy of source list
    while len(l) > 1:
        a = l.pop(0)
        for b in l:
           yield a,b

def first_pair(iterator):
    for i in iterator:
       # just return first element
       return i

def sum_pair(lst, sum_val):
    return first_pair((a,b) for a,b in combinations(lst) if (a+b) == sum_val)

print sum_pair([6,3,6,8,3,2,8,3,2], 11)
# result (3,8)

使用itertools:

from itertools import combinations, islice

def sum_pair(lst, sum_val):
    return list(islice(((a,b)
        for a,b in combinations(lst, 2) if (a+b) == sum_val), 0, 1))

print sum_pair([6,3,6,8,3,2,8,3,2], 11)
# result [(3,8)]

答案 5 :(得分:2)

这是一个非常好的问题,我曾经被要求以恒定的空间在线性时间内完成它,直到今天我还不知道如何实现这一目标。

这是一个简单的实现,我确定这是一种使用缓存更快一点的方法但是假设列表中的每个整数都不是唯一的,如果是的话我不认为缓存会有所帮助。 ..

def get_sum_pairs(sum = None, list_of_numbers = None):
    assert sum != None and list_of_numbers != None
    list_of_numbers = sorted(list_of_numbers) # sort the list of numbers O(n log n)        
    for index, number in enumerate(list_of_numbers): # search for each number that is less than the sum O(n)
        if number < sum: # if number greater then sum, theres nothing we can do.
            for index, number_1 in enumerate(list_of_numbers[(index + 1):]): # search the second list, this isn't exactly O(n) since its incremented being incremented
                if number + number_1 == sum: # found a solution.
                    return [(number, number_1)]
                if (number_1 > sum) or (number + number_1 > sum): # if number greater then sum, theres nothing we can do.
                    break                                       # if the addition of two sorted numbers is greater then sum, then theres no need to keep searching since the rest will also be greater, since their sorted.
        else:
            break
    return [()]

唯一的方法是使用某种数学公式或技巧,遗憾的是我没有。

在测量运行时间时,我们在大多数情况下考虑最坏情况,在这种情况下,它将是一个数字列表,其中每个数字都小于总和,并且是唯一的。

所以这些唯一数字都小于总和,通过排序我们可以减少检查次数,因为我们只需要向前移动,因为1 + 2 == 2 + 1 :)但我们仍然需要检查2 + 3 ,3 + 4等等...但是还要注意,如果检查的金额大于给定金额,我们也可以停止,因为金额将会增加...:)

这里有一些测试...

assert all([get_sum_pairs(**test[0]) == test[1] for test in
     [({'list_of_numbers':[6,3,6,8,3,2,8,3,2], 'sum':11}, [(3, 8)]),
     ({'list_of_numbers':[1,2,3,4,1,2], 'sum':1}, [()]),
     ({'list_of_numbers':[1,2,3,1,23,1,23,123], 'sum':124}, [(1, 123)]),
     ({'list_of_numbers':[1,2,3,12,3,2,1,23,4,1,23,4,5,12332], 'sum':14}, [(2, 12)]),
     ({'list_of_numbers':[-1,2,-2, -3, 1, 2, 3, 2, -1.3], 'sum':1}, [(-1, 2)])
     ] ])

答案 6 :(得分:2)

我不确定@ samy.vilar的'恒定空间',但这是一个线性时间和空间的解决方案(与n成比例,而不是len(numbers)):

def sumpairs(numbers, n):
    numbers = [None] + numbers + [None] * (n-len(numbers))
    for k in range(len(numbers)):
        a = numbers[k]
        if a is None or a==k: continue
        if numbers[n-a]==n-a:
            return a, n-a
        numbers[k] = None
        while numbers[a] != a and a is not None:
            b = n-a
            if numbers[a] is None:
                numbers[a] = a
                break
            if numbers[b]==b:
                return a, n-a
            numbers[a], a = a, numbers[a]

print(sumpairs([6,3,6,8,3,2,8,3,2], 16))
print(sumpairs([6,3,6,8,3,2,8,3,2], 11))
print(sumpairs([6,3,5,8,3,2,8,3,2], 10))
print(sumpairs([6,3,5,8,3,2,8,3,2], 5))
print(sumpairs([6,3,5,8,3,2,8,3,2], 12)) # This should fail.

它的工作原理是将每个数字移动到列表中的相应位置(我添加了一个前导None以获得基于1的索引)。复杂性有点棘手:有一个嵌套循环,但由于每个数字在整个过程仍为O(n)时最多会改变其位置。

n与数字列表的长度相比较大时,该解决方案当然很糟糕。这是一个恒定空间的解决方案(如果你销毁输入,否则你需要它的一个副本)和O(n log n)时间,n是输入的长度:

def sumpairs(numbers, n):
    numbers = sorted(numbers)
    low = 0
    while low < len(numbers)-1:
        t = numbers[low] + numbers[-1]
        if t > n:
            numbers.pop(-1)
        elif t < n:
            low += 1
        else:
            return numbers[low], numbers[-1]

print(sumpairs([6,3,6,8,3,2,8,3,2], 16))
print(sumpairs([6,3,6,8,3,2,8,3,2], 11))
print(sumpairs([6,3,5,8,3,2,8,3,2], 10))
print(sumpairs([6,3,5,8,3,2,8,3,2], 5))
print(sumpairs([6,3,5,8,3,2,8,3,2], 12)) # This should fail.