2

I'm in need of a method to quickly return the number of differences between two large lists. The contents of each list item is either 1 or 0 (single integers), and the amount of items in each list will always be 307200.

This is a sample of my current code:

list1 = <list1> # should be a list of integers containing 1's or 0's
list2 = <list2> # same rule as above, in a slightly different order

diffCount = 0
for index, item in enumerate(list1):
    if item != list2[index]:
        diffCount += 1

percent = float(diffCount) / float(307200)

The above works but it is way too slow for my purposes. What I would like to know is if there is a quicker way to obtain the number of differences between lists, or the percentage of items that differ?

I have looked at a few similar threads on this site but they all seem to work slightly different from what I want, and the set() examples don't seem to work for my purposes. :P

animuson
  • 53,861
  • 28
  • 137
  • 147
AWainb
  • 868
  • 2
  • 13
  • 27
  • Don't think you can do any better with ints. If they were bytes, you could XOR them but I don't see how you can get better performance out of ints. –  Apr 04 '11 at 04:14
  • @bdares, ty for your reply. Would it help if they were strings instead of int? the characters and list order when comparing are what's important here really. – AWainb Apr 04 '11 at 04:19
  • Should the 6th line be `if item != list2[index]`? I'm not sure where `i` comes from. – hwiechers Apr 04 '11 at 04:24
  • @bdares, it was a typo, list2[i] should have been list2[index], naturally ;) – AWainb Apr 04 '11 at 04:27
  • @hwiechers you're right. it's a typo. – smessing Apr 04 '11 at 04:30

6 Answers6

10

You can get at least another 10X speedup if you use NumPy arrays instead of lists.

import random
import time
import numpy as np
list1 = [random.choice((0,1)) for x in xrange(307200)]
list2 = [random.choice((0,1)) for x in xrange(307200)]
a1 = np.array(list1)
a2 = np.array(list2)

def foo1():
    start = time.clock()
    counter = 0
    for i in xrange(307200):
        if list1[i] != list2[i]:
            counter += 1
    print "%d, %f" % (counter, time.clock()-start)

def foo2():
    start = time.clock()
    ct = np.sum(a1!=a2)
    print "%d, %f" % (ct, time.clock()-start)
    
foo1() #153490, 0.096215
foo2() #153490, 0.010224
Glorfindel
  • 21,988
  • 13
  • 81
  • 109
Paul
  • 42,322
  • 15
  • 106
  • 123
  • On my machine, it's about three times faster to XOR them, np.sum(a1 ^ a2) – Jay P. Apr 04 '11 at 05:19
  • @Jeff The OP should be able to change their code to always use arrays instead of Python lists, then you don't need to worry about the time taken to convert a list to an array via np.array() – Jay P. Apr 04 '11 at 05:24
  • @Paul, thank you for your suggestion. You are correct, the numpy.array does work much faster than a list. The problen is that the conversion from list to numpy array has to be taken into consideration as well. When I add a1 = np.array(list1) & a2 = np.array(list2) to your foo function after start, then total time averages much higher: 0.199788, 0.165243, 0.164008, etc. It ends up doubling the time from my original example. :( – AWainb Apr 04 '11 at 06:24
  • @Jay P, I will try your mod in a sec. – AWainb Apr 04 '11 at 06:26
  • @Jay P, Please see class Images3 in the [originating post](http://stackoverflow.com/questions/5524179/how-to-detect-motion-between-two-pil-images-wxpython-webcam-integration-example). The "lists" are actually ImagimgCore objects from PIL. I used lists in this example because the object can be indexed just the same and I was trying not to over detail what I thought would be somethimg more simplistic; wishful thinking. ;) Tbh, I'm not sure how I could adapt the code to always use np arrays? I'm pretty sure doing so would delay the frames even more. :( – AWainb Apr 04 '11 at 06:43
  • 1
    @AWainb. ImageCore objects translate very well to Numpy arrays. I do it all the time. (I even did so in the answer to your originating post) They both use the same buffer structure and translation is nearly instant. Just do something like: `np.array(img)` – Paul Apr 04 '11 at 12:07
  • @Paul Interesting. For me np.sum(a1 ^ a2) is consistently three times faster than np.sum(a1 != a2). I have them both run 1000 times, the XOR version takes a total of ~1.6s while the != is ~4.4s – Jay P. Apr 04 '11 at 12:17
  • @Jay P. Yeah, I had to delete my comment (I had a typo when I tested: `*` instead of `^`) XOR is faster. But strangely slower when I use `dtype=bool` in the array creation. Does this have to do with the fact that they have to be upconverted to ints to do `sum`? if so, is there a `count` like method for `bool` arrays? – Paul Apr 04 '11 at 12:24
  • I would assume it's converting the bools to ints, but I don't actually know that for sure. And I don't recall ever using bools in a NumPy array before, and a quick glance doesn't show any methods that might work to count bools. – Jay P. Apr 04 '11 at 12:48
4

If possible, use Paul/JayP's answer of using numpy (with xor), if you can only use python's stdlib, itertools' izip in a list comprehension seems the fastest:

import random
import time
import numpy
import itertools
list1 = [random.choice((0,1)) for x in xrange(307200)]
list2 = [random.choice((0,1)) for x in xrange(307200)]
a1 = numpy.array(list1)
a2 = numpy.array(list2)

def given():
  diffCount = 0
  for index, item in enumerate(list1):
      if item != list2[index]:
          diffCount += 1
  return diffCount

def xrange_iter():
  counter = 0
  for i in xrange(len(list1)):
    if list1[i] != list2[i]:
      counter += 1
  return counter

def np_not_eq():
  return numpy.sum(a1!=a2)

def np_xor():
  return numpy.sum(a1^a2)

def np_not_eq_plus_array():
  arr1 = numpy.array(list1)
  arr2 = numpy.array(list2)
  return numpy.sum(arr1!=arr2)

def np_xor_plus_array():
  arr1 = numpy.array(list1)
  arr2 = numpy.array(list2)
  return numpy.sum(arr1^arr2)

def enumerate_comprehension():
  return len([0 for i,x in enumerate(list1) if x != list2[i]])

def izip_comprehension():
  return len([0 for a,b in itertools.izip(list1, list2) if a != b])

def zip_comprehension():
  return len([0 for a,b in zip(list1, list2) if a != b])

def functional():
  return sum(map(lambda (a,b): a^b, zip(list1,list2)))

def bench(func):
  diff = []
  for i in xrange(100):
    start = time.clock()
    result = func()
    stop = time.clock()
    diff.append(stop - start)
  print "%25s -- %d, %f" % (func.__name__, result, sum(diff)/float(len(diff)))

bench(given)
bench(xrange_iter)
bench(np_not_eq)
bench(np_xor)
bench(np_not_eq_plus_array)
bench(np_xor_plus_array)
bench(enumerate_comprehension)
bench(zip_comprehension)
bench(izip_comprehension)
bench(functional)

I got this (on Python 2.7.1, Snow Leopard):

                    given -- 153618, 0.046746
              xrange_iter -- 153618, 0.049081
                np_not_eq -- 153618, 0.003069
                   np_xor -- 153618, 0.001869
     np_not_eq_plus_array -- 153618, 0.081671
        np_xor_plus_array -- 153618, 0.080536
  enumerate_comprehension -- 153618, 0.037587
        zip_comprehension -- 153618, 0.083983
       izip_comprehension -- 153618, 0.034506
               functional -- 153618, 0.117359
Community
  • 1
  • 1
Jeff
  • 5,013
  • 1
  • 33
  • 30
  • thank you for your effort but you also forgot to take into consideration the fact that the np.array(list) calls have to be included within the bench test. your examples are much higher once this has been added into the mix: given = 0.218102, xrange_iter = 0.217878, np_not_eq = 0.164513, np_xor = 0.161727, enumerate_comprehension = 0.216057, zip_comprehension = 0.302612, izip_comprehension = 0.211152. Please add the array creation to your bench function, and please note that list_comprehension, zip_iter, and izip_iter are misnamed. ;) – AWainb Apr 04 '11 at 07:14
  • This is still a great example as it demonstrates quite a few methods to accomplish the same goals. :P One thing I noticed with my results is that the two np-based functions return a lower difference count (153163) than the rest (153476), any idea why? – AWainb Apr 04 '11 at 07:33
  • @AWainb thanks for the corrections. I'ved fixed them (I hope). I didn't take np.array(list) into consideration since the conversion was expected to be a one-time operation, but only the ones that use the numpy should be penalized, not all the other ones. I've created two more numpy tests to show the performance hit np.array causes. I'm not a numpy expert, but if there is a float in the array, the entire array will be coerced to floats, which could cause some mismatches then. – Jeff Apr 04 '11 at 08:03
0

I don't actually know if this is faster, but you might experiment with some of the "functional" methods python offers. It's usually better for loops to be run by internal, hand-coded subroutines.

Something like this:

diffCount = sum(map(lambda (a,b): a^b, zip(list1,list2)))
I. J. Kennedy
  • 24,725
  • 16
  • 62
  • 87
  • I just ran a test on this function but it unfortunately takes almost twice the time as @Amber's approach. Either way, thanks for the suggestion! :D – AWainb Apr 04 '11 at 05:08
  • 1
    If possible rather use a `built-in` than a `lambda`. Using `map(operator.xor, list1, list2)` is about 1.7 times faster on my machine than the `lambda` version. – lafras Apr 04 '11 at 12:48
0
>>> import random
>>> import time
>>> list1 = [random.choice((0,1)) for x in xrange(307200)]
>>> list2 = [random.choice((0,1)) for x in xrange(307200)]
>>> def foo():
...   start = time.clock()
...   counter = 0
...   for i in xrange(307200):
...     if list1[i] != list2[i]:
...       counter += 1
...   print "%d, %f" % (counter, time.clock()-start)
...
>>> foo()
153901, 0.068593

Is 7 hundredths of a second too slow for your application?

Amber
  • 507,862
  • 82
  • 626
  • 550
  • @Amber, I'll let you know in a couple of minutes, fair enough?? :P I know that sounds small but this code is being run between frames from my webcam. The longer it takes the comparison, the choppier the video gets. Out of curiosity, how does xrange differ from regular range? – AWainb Apr 04 '11 at 04:39
  • xrange returns an “xrange object” instead of a list, and does not load data to the memory, the data is created only when needed, which makes a difference when a very large range is used on a memory-starved machine or when all of the range’s elements are never used. (from python docs) – P2bM Apr 04 '11 at 04:52
  • @AWainb xrange() is an iterator, it doesn't create an actual list up-front like range() does. This means it doesn't need to allocate the large chunk of memory required, and in many cases can result in faster code. – Jay P. Apr 04 '11 at 04:53
  • @Amber, ty for that explanation. tested it out using xrange instead of range, I probably should have copied my above code to properly reflect my code, in my version I use range already, after comparing xrange and range results they appear to be the same; that said, yes, apparently 7 hundredths of a second is too slow. :( basically I am capturing an image from my cam, compare to the previous image, than place the image within a wxPanel in my gui. Without the comparison code I have a live cam feed in my gui which updates each new frame perfectly. – AWainb Apr 04 '11 at 04:59
  • Once I add in the comparison code it slows down and the movement in my gui delays by a few seconds while each frame is compared. I attempted to add the comparison into a thread queue but the queue fills up way too fast and eats my mem until the app crashes. – AWainb Apr 04 '11 at 05:00
  • see [here](http://stackoverflow.com/questions/5524179/how-to-detect-motion-between-two-pil-images-wxpython-webcam-integration-example) for more info. This formula will be added to the Images3 class to replace the junk that is in there right now. You can find it under UPDATE 2 within my question. – AWainb Apr 04 '11 at 05:03
  • Is a slower framerate when looking for motion necessarily a bad thing? After all, if there isn't motion, framerate probably doesn't matter - and as soon as you detect motion, you can boost the framerate. – Amber Apr 04 '11 at 17:17
  • @Amber. Yes it does, unfortunately, otherwise the video playback is way too choppy. It really is that bad so far. I wish i could thread the motion detection but as mentioned the queue builds so high it crashes my system. :( – AWainb Apr 04 '11 at 22:40
  • 1
    @AWainb - You might be able to do threading if you didn't try to run your motion detection diff-ing between every single frame - instead, compare every, say, 10th frame. – Amber Apr 05 '11 at 08:15
  • @Amber, I will give that a try now. Just to clarify, are you suggesting I compare the first frame with the tenth frame? Or compare the current frame with the previous frame, every ten frames (frame 9 compared to frame 10)? – AWainb Apr 06 '11 at 21:48
  • 1st with 10th (and then 10th with 20th, etc). – Amber Apr 07 '11 at 02:16
0

I would also try the following stdlib-only method:

import itertools as it, operator as op

def list_difference_count(list1, list2):
    return sum(it.starmap(op.ne, it.izip(list1, list2)))

>>> list_difference_count([1, 2, 3, 4], [1, 2, 1, 2])
2

This works correctly for len(list1) == len(list2). If you are sure that the list items are always integers, you can substitute op.xor for op.ne (could improve performance).

The percentage of difference is: float(list_difference_count(l1, l2))/len(l1).

tzot
  • 92,761
  • 29
  • 141
  • 204
0

list3 and list4 are equivalent. Different methods to get the same results

list_size=307200
list1=[random.choice([0,1]) for i in range(list_size)]
list2=[random.choice([0,1]) for i in range(list_size)]

list3=[]
for i in range(list_size):
    list3.append(list1[i]&list2[i])

#print(list3)
print(sum(list3)/list_size)

#or

list4=[x&y for (x,y) in list(zip(list1,list2))]
print(sum(list4)/list_size)
 
Golden Lion
  • 3,840
  • 2
  • 26
  • 35