improve efficiency of a nested loop - python

I need to find the number of pairs with consecutive numbers in a list. If elements in the list are repeated, they should be treated as members of a distinct pair. For instance, if the list were [1, 1, 1, 2, 2, 5, 8, 8], then there are three ways to choose 1 and two ways to choose 2, or a total of 3×2=63×2=6 ways to choose the pair (1, 2), so that the answer would, in this case, be 6.
My solution currently contains a nested loop as below. The code works but I want to optimize for a runtime of less than 2 seconds.
Can anyone give me some pointers on how to improve the runtime of this solution?
L = [1, 2, 5, 8]
count = 0
for i in range(0,len(L)-1):
for x in range(i+1, len(L)):
if L[x] == L[i] + 1 or L[x] == L[i] -1 :
count+=1

You could use the Counter class from collection to classify and count the available numbers, then sum up the product of counts for existing pairs of consecutive values:
from collections import Counter
L = [1, 1, 1, 2, 2, 5, 8, 8]
counts = Counter(L)
r = sum(c*counts[n+1] for n,c in counts.items())
print(r) # 6

Related

Fill lists in list with zeros if their length less than

I have a list of lists with different sizes but I want to make them all the same length. For example, make them with length of 5 by padding with zeros if length less than 5 or cut the list if length is more than 5. For example, I have a list:
foo = [
[1, 2, 3],
[1, 2, 3, 4, 5],
[1, 2, 3, 4, 5, 6, 7]]
result = [
[1, 2, 3, 0, 0],
[1, 2, 3, 4, 5],
[1, 2, 3, 4, 5]]
Do you have an idea of optimal and fast solution, if the list of lists is large?
List comprehension
Make a fill list and use slicing to get the appropriate lengths.
n = 5
fill = [0] * n
result = [sublist[:n] + fill[len(sublist):] for sublist in foo]
result = []
for sublist in foo:
size = len(sublist)
result.append(sublist[:5] + [0]*(5 - size))
To perform this optimization, i sliced additional elements beyond n = 5, and replaced with 0 those not reaching n = 5 by checking how many elements they miss.
def listOptimization(foo, n):
# slicing foo to have n elements on all child elements of foo
for i in range(len(foo)):
foo[i] = foo[i][:n]
# optimizing
for i in range(len(foo)):
# check if foo child element is less than n
# if true, append to the list with 0 depending on how many
# elements to reach n
if len(foo[i])<n:
temp = n-len(foo[i])
for x in range(temp):
foo[i].append(0)
return foo
result = [[bar[i] if i<len(bar)else 0 for i in range(5)] for bar in foo]
there are 5 elements in a row, so for i in range(5), the exceed 5 will be discard. then assign value directly,if the length of each row is less than i, assign 0
Actually, I found a pretty fast solution for me. If you have an idea how to solve it without a for loop please post.
for row in foo:
while len(row) < 5:
row.append(0)
else:
row[:5]

Remove common elements between two list with repeated elements

I need your help because I'm a bit stuck with this issue.
I've got two list with repeated elements:
L = [1,2,5,2,4,1,9]
S =[4,2,9,1]
I want to get the "difference" between the two list, as if I do LS = L-S, and the result should be LS = [1,2,5].
I know that l2 will always be a subset of L, thus S will never have a value not present in L.
Note that only one 1 and one 2 has been removed from L, because there was only one 1 and one 2 in S. L can have elements repeated n times, not just 2 times.
Do you know if there's an "easy" way to do this? I wouldn't like to get an O(n^2) program.
Thank you so much!
Use collections.Counter (Python's version of a multi-set)
from collections import Counter
L = [1, 2, 5, 2, 4, 1, 9]
S = [4, 2, 9, 1]
LS = list((Counter(L) - Counter(S)).elements())
print(LS)
Output
[1, 2, 5]
The overall complexity of this approach is O(n).
You can use this:
l = [1, 2, 5, 2, 4, 1, 9]
s = [4, 2, 9, 1]
for i in range(len(l)-1,-1,-1):
if l[i] in s:
s.remove(l[i])
l.pop(i)
print(l)
But i have no idea about its complexity.

Compare many values and tell if neither of them equal

I'm working on a project and I need to compare some values between each other and tell if they DO NOT match. I have a list of thirteen lists and each of those have more than 500 values. All thirteen lists have the same length. I would like to find an index of the item in any of those thirteen lists.
However I tried to simplify the problem by making three lists and each of those contain four items.
list1 = [1, 2, 2, 2]
list2 = [1, 3, 2, 2]
list3 = [2, 4, 2, 2]
Blist = [list1, list2, list3]
for i in range(len(Blist)): #0, 1, 2
for j in range(len(Blist)): #0, 1, 2
if i == j:
pass
else:
for k in range(len(list1)): #0, 1, 2, 3
st = Blist[i][k] != Blist[j][k]
print(st)
I could compare two lists at a time but I can't come up with the solution that would compare all items with the same index and return me a value of the index "ind" whose values don't match (when you compare list1[ind], list2[ind] and list3[ind]).
If there were only three lists I could write
for i in range(len(list1)):
if (list1[i] != list2[i] and list1[i] != list3[i] and list2[i] != list3[i])
print(i)
But I'd like to solve a problem even if it has hundreds of lists with hundreds of items.
For every index, create a set of values taking values from a single index for each nested list. As a set can't have duplicate elements, the length of the set should be equal to the total number of nested lists. Otherwise, there were duplicates, which means all the values of that index were not unique.
values = [
[1, 2, 2, 2, 5],
[1, 3, 2, 2, 7],
[2, 4, 2, 2, 1]
]
n = len(values[0]) # Number of values in each nested list
total = len(values) # Total number of nested lists
for i in range(n):
s = {v[i] for v in values}
if len(s) == total:
print(i)
Output:
1
4
If you've understood the above approach, the code can be cut down using a somewhat functional approach. Basically 2 lines of python code. (written in multiple lines for improved readability).
values = [
[1, 2, 2, 2, 5],
[1, 3, 2, 2, 7],
[2, 4, 2, 2, 1]
]
total = len(values)
# Using a list comprehension to create a list with the unique indices
unique_indices = [
idx
for idx, s in enumerate(map(set, zip(*values)))
if len(s) == total
]
print(unique_indices)
Output:
[1, 4]
if you are allowed to use numpy,
array_nd = np.array(Blist)
uniqueValues , indicesList, occurCount= numpy.unique(array_nd, return_index=True, return_counts=True)
from the above filter all of them which has occurCount as 1 and you can get its index from indicesList.

Count number of element in a array that is less than the element in another array

I am not sure whether this is the right place to ask this question, but I am struggling to get to the right answer myself. I have done a programming test and I can not figure why my code fails in certain test cases.
The problem is that given an unsorted array of elements, e.g [1, 4, 2, 4] and an array of maximum, e.g [3,5], gives the solution of the count of elements in the first array that is less than the maximums in the second array. i.e [2, 4]
some example would be
inputs
nums = [2, 10, 5, 4, 8]
maxes = [3, 1, 7, 8]
outputs
solution = [1, 0, 3, 4]
inputs
nums = [1, 4, 2, 4]
maxes = [3, 5]
outputs
solution = [2, 4]
since there is one element is that is less than or equal to 3, and 0 element that is less than or equal to 1 and 3 element that is less than or equal to 3 and 4 element that is less than or equal to 8
Pseudocode:
Sort number array
Sort maximum array
create a list counts
for max elem in max array
find first elem in number array that is greater than or
equal to max elem and save its (index+1) in counts
So now counts will be a parallel array to the sorted number array. At index 0 of counts we have the number of elements smaller than the number at index 0 of max array.
You can do the below if you need your data in a dictionary form (as you did in your solution) as opposed to two parallel lists.
Set index to 0
for max elem in max array:
dictionary[max elem] = counts[index]
index += 1
Note you can use enumerate above but I wasn't sure you knew about it yet so I tried to give the simplest solution.
Here's a simplified version of what you need to do
nums = [1, 4, 2, 4]
maxes = [3,5]
result = [0]*len(maxes)
for index, max_val in enumerate(maxes):
for num in nums:
if num <= max_val:
result[index] += 1
print(result)
I'd just write this as
[sum(1 for x in nums if x < m) for m in maxes]
Demo:
>>> nums = [2, 10, 5, 4, 8]
>>> maxes = [3, 1, 7, 8]
>>>
>>> [sum(1 for x in nums if x < m) for m in maxes]
[1, 0, 3, 3]
>>>
>>> nums = [1, 4, 2, 4]
>>> maxes = [3, 5]
>>>
>>> [sum(1 for x in nums if x < m) for m in maxes]
[2, 4]
It's readable, memory efficient, uses fast comprehensions implemented in C and you would have to work hard to find an example where further microoptimizations are justified.
def find_total_matches(team_a, team_b):
if not team_b:
return []
if not team_a:
return [0]*len(team_b)
team_a.sort()
team_b_sorted = sorted(team_b)
a_i = 0
b_i = 0
c_sum = 0
cnt = dict()
while a_i <len(team_a) and b_i < len(team_b_sorted):
val= team_b_sorted [b_i]
if val not in cnt:
while a_i < len(team_a) and val >= team_a[a_i]:
c_sum += 1
a_i += 1
cnt[val] = c_sum
b_i += 1
result = []
for val in team_b:
result.append(cnt[val])
return result

Get common elements majority of lists in python

Given 4 lists, I want to get elements that are common to 3 or more lists.
a = [1, 2, 3, 4]
b = [1, 2, 3, 4, 5]
c = [1, 3, 4, 5, 6]
d = [1, 2, 6, 7]
Hence, the output should be [1, 2, 3, 4].
My current code is as follows.
result1 = set(a) & set(b) & set(c)
result2 = set(b) & set(c) & set(d)
result3 = set(c) & set(d) & set(a)
result4 = set(d) & set(a) & set(b)
final_result = list(result1)+list(result2)+list(result3)+list(result4)
print(set(final_result))
It works fine, and give the desired output. However, I am interested in knowing if there is an easy way of doing this in Python, ie: are there any built in functions for this?
Using a Counter, you can do this like:
Code:
a = [1, 2, 3, 4]
b = [1, 2, 3, 4, 5]
c = [1, 3, 4, 5, 6]
d = [1, 2, 6, 7]
from collections import Counter
counts = Counter(sum(([list(set(i)) for i in (a, b, c, d)]), []))
print(counts)
more_than_three = [i for i, c in counts.items() if c >= 3]
print(more_than_three)
Results:
Counter({1: 4, 2: 3, 3: 3, 4: 3, 5: 2, 6: 2, 7: 1})
[1, 2, 3, 4]
Iterate over the values in all lists to create a dict of {value: number_of_lists_the_value_appears_in}:
from collections import defaultdict
counts = defaultdict(int)
for list_ in (a, b, c, d):
for value in set(list_): # eliminate duplicate values with `set`
counts[value] += 1
Then in the second step remove all values with a count < 3:
result = [value for value, count in counts.items() if count >= 3]
print(result) # [1, 2, 3, 4]
The code below will solve the generalised problem (with n lists, and a requirement that a common element must be in at least k of them). It will work with non-hashable items, which is the main disadvantage of all the other answers:
a = [1, 2, 3, 4]
b = [1, 2, 3, 4, 5]
c = [1, 2, 3, 4, 4, 5, 6]
d = [1, 2, 6, 7]
lists = [a, b, c, d]
result = []
desired_quanity = 3
for i in range(len(lists) - desired_quanity + 1): #see point 1 below
sublist = lists.pop(0) #see point 2
for item in sublist:
counter = 1 #1 not 0, by virute of the fact it is in sublist
for comparisonlist in lists:
if item in comparisonlist:
counter += 1
comparisonlist.remove(item) #see point 3
if counter >= desired_quanity:
result.append(item)
This has the disadvantage that for each element in every list, we have to check in every other list to see if it is there, but we can make things more efficient in a few ways. Also look-ups are alot slower in lists than sets (which we can't use since the OP has non-hashable items in the lists), and so this may be slow for very large lists.
1) If we require an item to be in k lists, we don't need to check each item in the last k-1 lists, as we would have already picked it up whilst searching through the first k lists.
2) Once we have searched through a list, we can discard that list, since any items in the just-searched-list that might contribute to our final result, will again already have been dealt with. This means that with each iteration we have fewer lists to search through.
3) When we have checked if an item is in enough lists, we can remove that item from the list, which means not only is the number of lists getting shorter as we proceed, the lists themselves are getting shorter, meaning quicker lookups.
As an aftersort, if we the original lists happen to be sorted beforehand, this might also help this algorithm work efficiently.
create a dictionary of counts and filter out those with count less than 3

Categories

Resources