Python sum pairs from text file - python

I am given a sorted array of positive integers and a number X and I need to print out all pairs of numbers whose sum is equal to X. Print out only unique pairs and the pairs should be in ascending order.
However the program needs to read lines of text from standard input where each line contains a comma separated list of sorted numbers, followed by a semicolon, followed by the integer X.
e.g. test input:1,2,3,4,6;5
output: 1,4;2,3
I have tried
list1 = []
for line in sys.stdin:
for n in line:
list1.append(n)
strlist = [ x for x in list1 if x.isdigit() ]
numlist = list(map(int, strlist))
sum = numlist[-1]
res = []
while numlist:
num = numlist.pop()
diff = sum - num
if diff in numlist:
res.append((diff, num))
res.reverse()
print(res, end="")
But i get
Test Input: 1,2,3,4,6;5
Expected Output: 1,4;2,3
My Output: [(2, 3), (1, 4)]

This might not be the most efficient, but I came up with this that worked.
Replace the test_case with sys.stdin and remove the test_case should work.
Sorry if this looks bad, it's my first time posting on stackoverflow.
test_case = ["1,2,3,4,6;5"]
for line in test_case:
line = line.split(";")
numbers = line[0].split(",")
pairs = []
for number in numbers:
difference = int(line[1]) - int(number)
difference = str(difference)
if difference in numbers and difference != number:
pair = ",".join((number, difference))
pairs.append(pair)
del pairs[len(pairs)//2:len(pairs)]
print(";".join(pairs))
Output : 1,4;2,3

Solution 1:
Time complexity of O(n^2). This is based on the original solution. The corrections made were:
The iteration of the string and parsing per character wouldn't work for 2-digit numbers (or more) e.g. 12 thus it is incorrect. Also it is not efficient. Instead of manually iterating each character, checking .isdigit(), and then redoing it again for the next number for the whole list, just split the string by the characters ; and , and then transform them to int.
In your original logic, the target sum would be the last element in the list numlist. But you are considering that number for the sum (since it will be the first number to be popped out). You should pop it first before entering the while loop.
The order of the pairs would start from the highest number (pop until list is empty). So, the pair for the highest number would be the smallest number e.g. 1 is to 4, then 2 is to 3, thus no need to call .reverse() as it will do the opposite.
for input_text in [
"1,2,3,4,6;5",
"5,10,15,20,25,30,35,40,45;50",
"0,2,4,6,8,10,12,14;12",
]:
num_str, sum_str = input_text.split(';')
num_list = list(map(int, num_str.split(',')))
target_sum = int(sum_str)
res = []
while num_list:
num = num_list.pop()
diff = target_sum - num
if diff in num_list:
res.append((diff, num))
res_formatted = ";".join(map(lambda pair: f"{pair[0]},{pair[1]}", res))
print(f"{input_text}\n\t{res}\n\t{res_formatted}")
Solution 2:
Time complexity of O(n). Since we already have sorted data, instead of iterating the whole list finding the other number that will arrive to the target sum for each number, just use 2 pointers, one at the start/left lhs and one at the end/right rhs.
If the sum is lower than the target, then it means that we need to move forward/right the lhs. Why? Because if we already added it to the maximum number which is rhs, then there is no point of trying it with the other numbers. What we need is to increase lhs and see the next result.
If the sum is higher than the target, then it means we need to move back/left the rhs. Why? Same point as above, If the sum was already higher, then we can't use the number in rhs as it is too high even if added with the smallest number lhs, thus move it to the next smaller number.
If the sum is equal to the target sum, then we include in the result the numbers pointed to by lhs and rhs. Then we move both pointers respectively.
def sum_pairs(numbers, target_sum):
lhs = 0
rhs = len(numbers) - 1
pairs = []
while lhs < rhs:
current_sum = numbers[lhs] + numbers[rhs]
if current_sum == target_sum:
pairs.append((numbers[lhs], numbers[rhs]))
lhs += 1
rhs -= 1
elif current_sum > target_sum:
rhs -= 1
else:
lhs += 1
return pairs
for input_text in [
"1,2,3,4,6;5",
"5,10,15,20,25,30,35,40,45;50",
"0,2,4,6,8,10,12,14;12",
]:
num_str, sum_str = input_text.split(';')
num_list = list(map(int, num_str.split(',')))
target_sum = int(sum_str)
res = sum_pairs(num_list, target_sum)
res_formatted = ";".join(map(lambda pair: f"{pair[0]},{pair[1]}", res))
print(f"{input_text}\n\t{res}\n\t{res_formatted}")
Output
1,2,3,4,6;5
[(1, 4), (2, 3)]
1,4;2,3
5,10,15,20,25,30,35,40,45;50
[(5, 45), (10, 40), (15, 35), (20, 30)]
5,45;10,40;15,35;20,30
0,2,4,6,8,10,12,14;12
[(0, 12), (2, 10), (4, 8)]
0,12;2,10;4,8

Related

Calculate circular shift pairs in a list

A circular shift moves some of the digits in a number to the beginning of the number, and shifts all other digits forward to the next position. For example, all of the circular shifts of 564 are 564, 645, 456.
Lets say two numbers of equal length a and b are circular pairs if a can become b via circular shifting. Using the example above, the circular pairs of 564 are 645 and 456.
Given an array of positive integers nums, count the number of circular pairs i and j where 0 <= i < j < len(nums)
For example, circular_shifts([13, 5604, 31, 2, 13, 4560, 546, 654, 456]) should return 5. With the pairs being (13, 31), (13, 13), (5604, 4560), (31, 13), (546, 654).
I wrote a brute force solution that saves all circular shifts of a number in a dictionary shifts and for every number num in nums, I check all the following numbers and if a number num2 the same digits as num, I see if num2 is in num's circular shifts. If it is then I added this to the total count.
Unfortunately this algorithm runs too slowly and times out, I'm looking for a faster way to solve this problem can anyone think of optimizations, clever tricks, or other strategies to speed this up?
Not very elegant as not much time but the following should be faster as does not repeatedly access the list. Shifting is done on string for simplicity.
from collections import Counter
def big(num):
max = num
s = str(num)
for _ in s:
x = int(s[-1] + s[0:-1])
if x > max:
max = x
return max
circs = [big(z) for z in mylist]
c = Counter(circs)
total = 0
for i in c:
if c[i] > 1:
total += c[i]
print(total)
Replace every number in the array with its greatest cyclic shift. 1234, for example, would become 4123
Count the occurrences of each resulting number
If a number occurs n times, that represents n(n-1)/2 cyclic shift pairs. Add them all up.
Here's my solution, where we
iterate through the list
rotate each number length-1 times
and print only if the number is found in the remaining list
numbers = [13, 5604, 31, 2, 13, 4560, 546, 654, 456]
# shift_pairs = [(13,31),(13,13),(5604,4560),(31,13),(546,654)]
pairsFound = 0
for i in range(len(numbers)):
number = numbers[i]
length = len(str(number))
numberToRotate = number
shift_remaining = length - 1
temp_list = numbers[i+1:]
while(shift_remaining >= 0):
t_remainder = numberToRotate % 10 # 4
numberToRotate = numberToRotate // 10 # 560
numberToRotate = numberToRotate + t_remainder * 10 **(length-1) # 4560
# we should also check for length for cases like 4560, where on a shift we get 456
if(numberToRotate in temp_list and len(str(numberToRotate)) == length):
print("({},{})".format(number,numberToRotate))
pairsFound += 1
shift_remaining -= 1
print("no of pairs:", pairsFound)
output
(13,31)
(13,13)
(5604,4560)
(31,13)
(546,654)
no of pairs: 5

How to add a function that adds two intergers to my Collatz code

I want to add a function onto this already mad collatz function code that gives me two intergers, the first intergers gives the length of Collatz sequence and the other is the sequence that has the largest length.
EXAMPLE, the function returns numbers
20 and 9
Which means that among the numbers 1, 2, 3,…, 10, nine has the
longest Collatz sequence, and its length is equal to 20.
i am thinking i need to create another function that accepts intergers and passes through a loop onto the collatz or something like so. Here is a picture of my already made
Just not sure how to start the process as i am quite new, thanks
EDIT-
n = []
def collatzSequence(n):
print(n)
if n==1:
return n
if n % 2 == 0:
n = n // 2
else:
n = ((n*3) + 1) // 2
return collatzSequence(n)
print(collatzSequence(3))
A few notes on your code:
you don't create a Collatz sequence, you just print it, but in the end your function will always return 1 (or raise an error, see below). That n=[] line seems to suggest you thought to work on a list, but you're just working with single numbers instead
the usual way of showing a Collatz sequence is listing each passage, as in 3, 10, 5, 16, 8, 4, 2, 1, while you are skipping (most) even numbers
i wouldn't recommend a recursive solution here. Although Python's recursion depth limit is usually set to 1000, if you try some large numbers you will exceed that limit and get an error
So let's build a sequence:
def collatzSequence(n):
l=[]
while n > 1:
l.append(n)
if n % 2 == 0:
n = n // 2
else:
n = n*3 + 1
l.append(1)
return l
Now we can write a second function to find the longest sequence. It will accept a list or any iterable which yelds ints, perhaps a range object:
def longestCollatz(numbers):
maxlen = 0
longest = None
for num in numbers:
newlen = len(collatzSequence(num))
if newlen > maxlen:
longest = num
maxlen = newlen
return (longest,maxlen)
longestCollatz(range(1,13,2))
(9, 20)

How to find the smallest four digit number, whose digits dont repeat, but add up to a randomized number

I need to take a number, lets say 6, and then find the smallest 4 digit number, whose digits do not repeat and add up to 6.
For example(These will not add up to the same number):
1023
3045
2345
These numbers are all ok because their digits do not repeat and are four digits
While:
1122
3344
123
These are not ok, because they either are not four digits or their numbers repeat
I'm currently at a roadblock where I can find said four digit number, but one: it is in a list which the program i need to plug this into wont accept and two: the digits aren't in the same order as the answers on the program (ie the smallest four digit number that does not have repeat digits, but adds up to six is 1023, but my program returns 0123, which is incorret.
Here is my current code:
x = 6
Sum = 0
#Find the four digit number
for i in range (999, 10000):
#Find the sum of those numbers
Sum = sum(map(int, str(i)))
#Check if sum is = to x
if Sum == x:
num = i
#Get rid of any identical numbers
result = list(set(map(int, str(num))))
#Make sure the length is 4
if len(result) == 4:
print(result)
#Output [0,1,2,3]
Any help on how I could change this to work for what I want would be great
Using recursive algorithm:
def get_num(n, s, v=""):
for el in range(1 if v=="" else 0, 10):
if str(el) not in v:
temp_sum=sum(int(l) for l in v+str(el))
if(temp_sum>s):
break
elif len(v)==n-1:
if(temp_sum==s):
return v+str(el)
else:
ret=get_num(n, s, v+str(el))
if(ret): return ret
Outputs:
print(get_num(4,6))
>> 1023
I've changed your program to print i instead of result, and break out of the loop when it finds the first number (which logically must be the smallest):
x = 6
Sum = 0
#Find the four digit number
for i in range (999, 10000):
#Find the sum of those numbers
Sum = sum(map(int, str(i)))
#Check if sum is = to x
if Sum == x:
num = i
#Get rid of any identical numbers
result = list(set(map(int, str(num))))
#Make sure the length is 4
if len(result) == 4:
print(i)
break
This program will print 1023.
You could neaten it up a bit by turning it into a function with x as a parameter and returning the result instead of breaking and printing. Also it looks as if the num variable is redundant, you can just stick with i.
Changed your code a little:
x = 6
Sum = 0
result={}
#Find the four digit number
for i in range (999, 10000):
#Find the sum of those numbers
Sum = sum(map(int, str(i)))
#Check if sum is = to x
if Sum == x:
num = i
aux = ''.join(list(set(map(str, str(num)))))
if not aux in result:
result[aux] = []
result[aux].append(i)
for k in result:
print(k, min(result[k]))
You might be interested in itertools package which is designed for solving combinatoric problems like yours.
for n in combinations(range(10),4):
if sum(n)==6 and n[0]!=0:
print(n)
break
combinations method used here returns a generator of all tuples of length 4 that contains distinct digits from 0 to 9, they are sorted alphabetically.
You need to use from itertools import combinations to make it work.
Since you need the smallest integer, you can stop the search at the first encountered with a break:
sum=6 # The sum of digits required.
found=0
for i in range(1000, 10000): # i from 1000 to 9999.
a=i%10;
b=i//10%10
c=i//100%10
d=i//1000%10
if (a!=b)&(a!=c)&(a!=d)&(b!=c)&(b!=d)&(c!=d)&(a+b+c+d==sum):
found=True
break
if found:
print(i)
else:
print('Not found such a number.')

Google Foobar Question - Please Pass The Coded Message

Google Foobar Question:
Please Pass the Coded Messages
You need to pass a message to the bunny prisoners, but to avoid detection, the code you agreed to use is... obscure, to say the least. The bunnies are given food on standard-issue prison plates that are stamped with the numbers 0-9 for easier sorting, and you need to combine sets of plates to create the numbers in the code. The signal that a number is part of the code is that it is divisible by 3. You can do smaller numbers like 15 and 45 easily, but bigger numbers like 144 and 414 are a little trickier. Write a program to help yourself quickly create large numbers for use in the code, given a limited number of plates to work with.
You have L, a list containing some digits (0 to 9). Write a function answer(L) which finds the largest number that can be made from some or all of these digits and is divisible by 3. If it is not possible to make such a number, return 0 as the answer. L will contain anywhere from 1 to 9 digits. The same digit may appear multiple times in the list, but each element in the list may only be used once.
Languages
To provide a Python solution, edit solution.py
To provide a Java solution, edit solution.java
Test cases
Inputs:
(int list) l = [3, 1, 4, 1]
Output:
(int) 4311
Inputs:
(int list) l = [3, 1, 4, 1, 5, 9]
Output:
(int) 94311
Use verify [file] to test your solution and see how it does. When you are finished editing your code, use submit [file] to submit your answer. If your solution passes the test cases, it will be removed from your home folder.
So that's the question, my python code only passes 3 out of 5 tests cases. I spent a few hours but can't find out what cases I am missing. Here is my code:
maximum = [0, 0, 0, 0, 0,0,0,0,0]
def subset_sum(numbers, target, partial=[]):
global maximum
s = sum(partial)
if s%3 == 0:
if s != 0:
str1 = ''.join(str(e) for e in partial)
y = int(str1)
str1 = ''.join(str(e) for e in maximum)
z = int(str1)
if y>z:
maximum = partial
# print maximum
if s >= target:
return # if we reach the number why bother to continue
for i in range(len(numbers)):
n = numbers[i]
remaining = numbers[i+1:]
subset_sum(remaining, target, partial + [n])
def answer(l):
global maximum
#maximum = [0, 0, 0, 0, 0]
subset_sum(l,sum(l))
maximum = sorted(maximum, key=int, reverse=True)
str1 = ''.join(str(e) for e in maximum)
y = int(str1)
return y
print(answer([3,1,4,1,5,9]))
So what test cases am I not accounting for, and how could I improve it?
try this using combination it may help:
from itertools import combinations
def answer(nums):
nums.sort(reverse = True)
for i in reversed(range(1, len(nums) + 1)):
for tup in combinations(nums, i):
if sum(tup) % 3 == 0: return int(''.join(map(str, tup)))
return 0
Presently, you are forming a number by using adjacent digits only while the question does not say so.
A quick fix would be to set remaining list properly -
remaining = numbers[:i] + numbers[i+1:]
But you need to think of better algorithm.
Update
inputNumbers = [2, 1, 1, 1, 7, 8, 5, 7, 9, 3]
inputNumSorted = sorted(inputNumbers)
sumMax = sum(inputNumbers)
queue = [(sumMax, inputNumSorted)]
found = False
while (len(queue) > 0):
(sumCurrent, digitList) = queue.pop()
remainder = sumCurrent%3
if (remainder == 0):
found = True
break
else :
for index, aNum in enumerate(digitList):
if(aNum%3 == remainder):
sumCurrent -= remainder
digitList.remove(aNum)
found = True
break
else:
newList = digitList[:index]+digitList[index+1:]
if (len(newList) > 0):
queue.insert(0, (sumCurrent-aNum, newList))
if(found):
break
maxNum = 0
if (found):
for x,y in enumerate(digitList):
maxNum += (10**x)*y
print(maxNum)
I believe the solution looks something like this:
Arrange the input digits into a single number, in order from largest to smallest. (The specific digit order won't affect its divisibility by 3.)
If this number is divisible by 3, you're done.
Otherwise, try removing the smallest digit. If this results in a number that is divisible by 3, you're done. Otherwise start over with the original number and try removing the second-smallest digit. Repeat.
Otherwise, try removing digits two at a time, starting with the two smallest. If any of these result in a number that is divisible by 3, you're done.
Otherwise, try removing three digits...
Four digits...
Etc.
If nothing worked, return zero.
Here's the actual solution and this passes in all the test cases
import itertools
def solution(l):
l.sort(reverse = True)
for i in reversed(range(1, len(l) + 1)):
for j in itertools.combinations(l, i):
if sum(tup) % 3 == 0: return int(''.join(map(str, j)))
return 0
Here is a commented solution (that passed all test cases):
def solution(l):
# sort in decending order
l = sorted(l, reverse = True)
# if the number is already divisible by three
if sum(l) % 3 == 0:
# return the number
return int("".join(str(n) for n in l))
possibilities = [0]
# try every combination of removing a single digit
for i in range(len(l)):
# copy list of digits
_temp = l[:]
# remove a digit
del _temp[len(_temp) - i - 1]
# check if it is divisible by three
if sum(_temp) % 3 == 0:
# if so, this is our solution (the digits are removed in order)
return int("".join(str(n) for n in _temp))
# try every combination of removing a second digit
for j in range(1, len(_temp)):
# copy list of digits again
_temp2 = _temp[:]
# remove another digit
del _temp2[len(_temp2) - j - 1]
# check if this combination is divisible by three
if sum(_temp2) % 3 == 0:
# if so, append it to the list of possibilities
possibilities.append(int("".join(str(n) for n in _temp2)))
# return the largest solution
return max(possibilities)
I tried a lot but test case 3 fails .Sorry for bad variable names
import itertools
def solution(l):
a=[]
k=''
aa=0
b=[]
for i in range(len(l)+1):
for j in itertools.combinations(l,i):
a.append(j)
for i in a:
if sum(i)>=aa and sum(i)%3==0 and len(b)<len(i):
aa=sum(i)
b=i[::-1]
else:
pass
b=sorted(b)[::-1]
for i in b:
k+=str(i)
if list(k)==[]:
return 0
else:
return k

Faster Python technique to count triples from a list of numbers that are multiples of each other

Suppose we have a list of numbers, l. I need to COUNT all tuples of length 3 from l, (l_i,l_j,l_k) such that l_i evenly divides l_j, and l_j evenly divides l_k. With the stipulation that the indices i,j,k have the relationship i<j<k
I.e.;
If l=[1,2,3,4,5,6], then the tuples would be [1,2,6], [1,3,6],[1,2,4], so the COUNT would be 3.
If l=[1,1,1], then the only tuple would be [1,1,1], so the COUNT would be 1.
Here's what I've done so far, using list comprehensions:
def myCOUNT(l):
newlist=[[x,y,z] for x in l for y in l for z in l if (z%y==0 and y%x==0 and l.index(x)<l.index(y) and l.index(y)<l.index(z))]
return len(newlist)
>>>l=[1,2,3,4,5,6]
>>>myCOUNT(l)
3
This works, but as l gets longer (and it can be as large as 2000 elements long), the time it takes increases too much. Is there a faster/better way to do this?
We can count the number of triples with a given number in the middle by counting how many factors of that number are to its left, counting how many multiples of that number are to its right, and multiplying. Doing this for any given middle element is O(n) for a length-n list, and doing it for all n possible middle elements is O(n^2).
def num_triples(l):
total = 0
for mid_i, mid in enumerate(l):
num_left = sum(1 for x in l[:mid_i] if mid % x == 0)
num_right = sum(1 for x in l[mid_i+1:] if x % mid == 0)
total += num_left * num_right
return total
Incidentally, the code in your question doesn't actually work. It's fallen into the common newbie trap of calling index instead of using enumerate to get iteration indices. More than just being inefficient, this is actually wrong when the input has duplicate elements, causing your myCOUNT to return 0 instead of 1 on the [1, 1, 1] example input.
Finding all tuples in O(n2)
You algorithm iterates over all possible combinations, which makes it O(n3).
Instead, you should precompute the division-tree of your list of numbers and recover triples from the paths down the tree.
Division tree
A division tree is a graph which nodes are numbers and children are the multiples of each number.
By example, given the list [1, 2, 3, 4], the division tree looks like this.
1
/|\
2 | 3
\|
4
Computing the division tree requires to compare each number against all others, making its creation O(n2).
Here is a basic implementation of a division-tree that can be used for your problem.
class DivisionTree:
def __init__(self, values):
values = sorted(values)
# For a division-tree to be connected, we need 1 to be its head
# Thus we artificially add it and note whether it was actually in our numbers
if 1 in values:
self._has_one = True
values = values[1:]
else:
self._has_one = False
self._graph = {1: []}
for v in values:
self.insert(v)
def __iter__(self):
"""Iterate over all values of the division-tree"""
yield from self._graph
def insert(self, n):
"""Insert value in division tree, adding it as child of each divisor"""
self._graph[n] = []
for x in self:
if n != x and n % x == 0:
self._graph[x].append(n)
def paths(self, depth, _from=1):
"""Return a generator of all paths of *depth* down the division-tree"""
if _from == 1:
for x in self._graph[_from]:
yield from self.paths(depth , _from=x)
if depth == 1:
yield [_from]
return
if _from != 1 or self._has_one:
for x in self._graph[_from]:
for p in self.paths(depth - 1, _from=x):
yield [_from, *p]
Usage
Once we built a DivisionTree, it suffices to iterate over all paths down the graph and select only those which have length 3.
Example
l = [1,2,3,4,5,6]
dt = DivisionTree(l)
for p in dt.paths(3):
print(p)
Output
[1, 2, 4]
[1, 2, 6]
[1, 3, 6]
This solution assumes that the list of number is initially sorted, as in your example. Although, the output could be filtered with regard to the condition on indices i < j < k to provide a more general solution.
Time complexity
Generating the division-tree is O(n2).
In turn, there can be up to n! different paths, although stopping the iteration whenever we go deeper than 3 prevents traversing them all. This makes us iterate over the following paths:
the paths corresponding to three tuples, say there are m of them;
the paths corresponding to two tuples, there are O(n2) of them;
the paths corresponding to one tuples, there are O(n) of them.
Thus this overall yields an algorithm O(n2 + m).
I suppose this solution without list comprehension will be faster (you can see analogue with list comprehension further):
a = [1, 2, 3, 4, 5, 6]
def count(a):
result = 0
length = len(a)
for i in range(length):
for j in range(i + 1, length):
for k in range(j + 1, length):
if a[j] % a[i] == 0 and a[k] % a[j] == 0:
result += 1
return result
print(count(a))
Output:
3
In your solution index method is too expensive (requires O(n) operations). Also you don't need to iterate over full list for each x, y and z (x = a[i], y = a[j], z = a[k]). Notice how I use indexes in my loops for y and z because I know that a.index(x) < a.index(y) < a.index(z) is always satisfied.
You can write it as one liner too:
def count(a):
length = len(a)
return sum(1 for i in range(length)
for j in range(i + 1, length)
for k in range(j + 1, length)
if a[j] % a[i] == 0 and a[k] % a[j] == 0)
P.S.
Please, don't use l letter for variables names because it's very similar to 1:)
There is a way to do this with itertools combinations:
from itertools import combinations
l=[1,2,3,4,5,6]
>>> [(x,y,z) for x,y,z in combinations(l,3) if z%y==0 and y%x==0]
[(1, 2, 4), (1, 2, 6), (1, 3, 6)]
Since combinations generates the tuples in list order, you do not then need to check the index of z.
Then your myCOUNT function becomes:
def cnt(li):
return sum(1 for x,y,z in combinations(li,3) if z%y==0 and y%x==0)
>>> cnt([1,1,1])
1
>>> cnt([1,2,3,4,5,6])
3
This is a known problem.
Here are some timing for the solutions here:
from itertools import combinations
class DivisionTree:
def __init__(self, values):
# For a division-tree to be connected, we need 1 to be its head
# Thus we artificially add it and note whether it was actually in our numbers
if 1 in values:
self._has_one = True
values = values[1:]
else:
self._has_one = False
self._graph = {1: []}
for v in values:
self.insert(v)
def __iter__(self):
"""Iterate over all values of the division-tree"""
yield from self._graph
def insert(self, n):
"""Insert value in division tree, adding it as child of each divisor"""
self._graph[n] = []
for x in self:
if n != x and n % x == 0:
self._graph[x].append(n)
def paths(self, depth, _from=1):
"""Return a generator of all paths of *depth* down the division-tree"""
if _from == 1:
for x in self._graph[_from]:
yield from self.paths(depth , _from=x)
if depth == 1:
yield [_from]
return
if _from != 1 or self._has_one:
for x in self._graph[_from]:
for p in self.paths(depth - 1, _from=x):
yield [_from, *p]
def f1(li):
return sum(1 for x,y,z in combinations(li,3) if z%y==0 and y%x==0)
def f2(l):
newlist=[[x,y,z] for x in l for y in l for z in l if (z%y==0 and y%x==0 and l.index(x)<l.index(y) and l.index(y)<l.index(z))]
return len(newlist)
def f3(a):
result = 0
length = len(a)
for i in range(length):
for j in range(i + 1, length):
for k in range(j + 1, length):
if a[j] % a[i] == 0 and a[k] % a[j] == 0:
result += 1
return result
def f4(l):
dt = DivisionTree(l)
return sum(1 for _ in dt.paths(3))
def f5(l):
total = 0
for mid_i, mid in enumerate(l):
num_left = sum(1 for x in l[:mid_i] if mid % x == 0)
num_right = sum(1 for x in l[mid_i+1:] if x % mid == 0)
total += num_left * num_right
return total
if __name__=='__main__':
import timeit
tl=list(range(3,155))
funcs=(f1,f2,f3,f4,f5)
td={f.__name__:f(tl) for f in funcs}
print(td)
for case, x in (('small',50),('medium',500),('large',5000)):
li=list(range(2,x))
print('{}: {} elements'.format(case,x))
for f in funcs:
print(" {:^10s}{:.4f} secs".format(f.__name__, timeit.timeit("f(li)", setup="from __main__ import f, li", number=1)))
And the results:
{'f1': 463, 'f2': 463, 'f3': 463, 'f4': 463, 'f5': 463}
small: 50 elements
f1 0.0010 secs
f2 0.0056 secs
f3 0.0018 secs
f4 0.0003 secs
f5 0.0002 secs
medium: 500 elements
f1 1.1702 secs
f2 5.3396 secs
f3 1.8519 secs
f4 0.0156 secs
f5 0.0110 secs
large: 5000 elements
f1 1527.4956 secs
f2 6245.9930 secs
f3 2074.2257 secs
f4 1.3492 secs
f5 1.2993 secs
You can see that f1,f2,f3 are clearly O(n^3) or worse and f4,f5 are O(n^2). f2 took more than 90 minutes for what f4 and f5 did in 1.3 seconds.
Solution in O(M*log(M)) for a sorted list containing positive numbers
As user2357112 has answered, we can count the number of triplets in O(n^2) by calculating for every number the number of its factors and multiples. However, if instead of comparing every pair we go over its multiples smaller than the largest number and check whether they are in the list, we can change the efficiency to O(N+M*log(N)), when M is the largest number in the list.
Code:
def countTriples(myList):
counts = {} #Contains the number of appearances of every number.
factors = {} #Contains the number of factors of every number.
multiples = {} #Contains the number of multiples of every number.
for i in myList: #Initializing the dictionaries.
counts[i] = 0
factors[i] = 0
multiples[i] = 0
maxNum = max(myList) #The maximum number in the list.
#First, we count the number of appearances of every number.
for i in myList:
counts[i] += 1
#Then, for every number in the list, we check whether its multiples are in the list.
for i in counts:
for j in range(2*i, maxNum+1, i):
if(counts.has_key(j)):
factors[j] += counts[i]
multiples[i] += counts[j]
#Finally, we count the number of triples.
ans = 0
for i in counts:
ans += counts[i]*factors[i]*multiples[i] #Counting triplets with three numbers.
ans += counts[i]*(counts[i]-1)*factors[i]/2 #Counting triplets with two larger and one smaller number.
ans += counts[i]*(counts[i]-1)*multiples[i]/2 #Counting triplets with two smaller numbers and one larger number.
ans += counts[i]*(counts[i]-1)*(counts[i]-2)/6 #Counting triplets with three copies of the same number.
return ans
While this solution will work quickly for lists containing many small numbers, it will not work for lists containing large numbers:
countTriples(list(range(1,1000000)) #Took 80 seconds on my computer
countTriples([1,2,1000000000000]) #Will take a very long time
Fast solution with unknown efficiency for unsorted lists
Another method to count the number of multiples and factors of every number in the list would be to use a binary tree data structure, with leaves corresponding to numbers. The data structure supports three operations:
1) Add a number to every position which is a multiple of a number.
2) Add a number to every position which is specified in a set.
3) Get the value of a position.
We use lazy propagation, and propagate the updates from the root to lower nodes only during queries.
To find the number of factors of every item in the list, we iterate over the list, query the number of factors of the current item from the data structure, and add 1 to every position which is a multiple of the item.
To find the number of multiples of every item, we first find for every item in the list all its factors using the algorithm described in the previous solution.
We then iterate over the list in the reverse order. For every item, we query the number of its multiples from the data structure, and add 1 to its factors in the data structure.
Finally, for every item, we add the multiplication of its factors and multiples to the answer.
Code:
'''A tree that supports two operations:
addOrder(num) - If given a number, adds 1 to all the values which are multiples of the given number. If given a tuple, adds 1 to all the values in the tuple.
getValue(num) - returns the value of the number.
Uses lazy evaluation to speed up the algorithm.
'''
class fen:
'''Initiates the tree from either a list, or a segment of the list designated by s and e'''
def __init__(this, l, s = 0, e = -1):
if(e == -1): e = len(l)-1
this.x1 = l[s]
this.x2 = l[e]
this.val = 0
this.orders = {}
if(s != e):
this.s1 = fen(l, s, (s+e)/2)
this.s2 = fen(l, (s+e)/2+1, e)
else:
this.s1 = None
this.s2 = None
'''Testing if a multiple of the number appears in the range of this node.'''
def _numGood(this, num):
if(this.x2-this.x1+1 >= num): return True
m1 = this.x1%num
m2 = this.x2%num
return m1 == 0 or m1 > m2
'''Testing if a member of the group appears in the range of this node.'''
def _groupGood(this, group):
low = 0
high = len(group)
if(this.x1 <= group[0] <= this.x2): return True
while(low != high-1):
mid = (low+high)/2;
if(group[mid] < this.x1): low = mid
elif(group[mid] > this.x2): high = mid
else: return True
return False
def _isGood(this, val):
if(type(val) == tuple):
return this._groupGood(val)
return this._numGood(val)
'''Adds an order to this node.'''
def addOrder(this, num, count = 1):
if(not this._isGood(num)): return
if(this.x1 == this.x2): this.val += count
else :this.orders[num] = this.orders.get(num, 0)+count
'''Pushes the orders to lower nodes.'''
def _pushOrders(this):
if(this.x1 == this.x2): return
for i in this.orders:
this.s1.addOrder(i, this.orders[i])
this.s2.addOrder(i, this.orders[i])
this.orders = {}
def getValue(this, num):
this._pushOrders()
if(num < this.x1 or num > this.x2):
return 0
if(this.x1 == this.x2):
return this.val
return this.s1.getValue(num)+this.s2.getValue(num)
def countTriples2(myList):
factors = [0 for i in myList]
multiples = [0 for i in myList]
numSet = set((abs(i) for i in myList))
sortedList = sorted(list(numSet))
#Calculating factors.
tree = fen(sortedList)
for i in range(len(myList)):
factors[i] = tree.getValue(abs(myList[i]))
tree.addOrder(abs(myList[i]))
#Calculating the divisors of every number in the group.
mxNum = max(numSet)
divisors = {i:[] for i in numSet}
for i in sortedList:
for j in range(i, mxNum+1, i):
if(j in numSet):
divisors[j].append(i)
divisors = {i:tuple(divisors[i]) for i in divisors}
#Calculating the number of multiples to the right of every number.
tree = fen(sortedList)
for i in range(len(myList)-1, -1, -1):
multiples[i] = tree.getValue(abs(myList[i]))
tree.addOrder(divisors[abs(myList[i])])
ans = 0
for i in range(len(myList)):
ans += factors[i]*multiples[i]
return ans
This solution worked for a list containing the numbers 1..10000 in six seconds on my computer, and for a list containing the numbers 1..100000 in 87 seconds.

Categories

Resources