Reducing nested-loops of a python question on array - python

for _ in range(int(input())):
num=int(input())
a=list(map(int,input().split()))[:num]
sum=0
for i in range(len(a)):
j=a[i]
count=0
key=0
for k in range(len(a)):
if j==a[k]:
key=k
sum+=abs(key-i)
print(sum)
Given an integer array. The task is to calculate the sum of absolute difference of indices of first and last occurrence for every integer that is present in the array.
Required to calculate the sum of the answer for every such that occurs in the array.
One input:
1 2 3 3 2
Sample Output:
4
Explanation: The elements which occur in the array are 1,2,3.
it has only occurred once so the answer for 1 is 0.
it has two occurrences at 2 and 5 so |5-2|=3
it has two occurrences at 3 and 4 so |4-3|=1.
So total sum=0+3+1=4.
p.s: The first loop is for test cases.
Pleae suggest me to reduce time-complexity.

intially you can create a dictiory of unique number and append all the index of each number and then in second loop you can get the diffrence of each integar.
for _ in range(int(input())):
num=int(input())
a=list(map(int,input().split()))[:num]
sum=0
nums = {}
for i in range(len(a)):
j=a[i]
if j not in nums:
nums[j] = []
nums[j].append(i)
for key in nums:
sum += abs(nums[key][-1] - nums[key][0])
print(sum)

This answer uses the same reasoning as others: that is storing the indices as a list of values in a dictionary, but uses a few built-in functions and methods to reduce code and make it 'cleaner'.
In [11]: array = [1, 2, 3, 3, 2]
In [12]: indices = {}
In [13]: for ix, num in enumerate(array, start=1):
...: indices.setdefault(num, []).append(ix)
...:
In [14]: total = 0
In [15]: for num, ixes in indices.items():
...: if len(ixes) == 1:
...: continue
...: else:
...: total += abs(ixes[-1] - ixes[0])
...:
In [16]: total
Out[16]: 4
enumerate is a function that creates a sequence of tuple pairs from a given sequence like a list. The first element is an "index" (by default, set to 0, but you can start from any integer) and the second is the actual value from the original sequence.
setdefault is a method on a dictionary that returns the value for a given key, but if that key doesn't exist, inserts the key and sets as its default value the item passed in as the second parameter; in this case, it's an empty list to store the indices.
items is again a method on dictionaries with which one can loop through one key-value pair at a time.

Sounds like hackerrank. As usual, most of the provided information of the problem is irrelevant and can be forgotten as soon as seen.
You need:
the index when an element occures first: you add it as negative to the total and put it into the dictionary
if the value is already in the dict, update the position only
at the end you sum all values of the dict and add it to your summation
Code:
num = 5
a = list(map(int,"1 2 3 3 2".split()))
s = 0
d = {}
for idx, num in enumerate(a):
if num not in d:
s -= idx
d[num] = idx
print(s+sum(d.values()))
Output:
4
This uses a dictionary and strictly 2 loops - one over the n given numbers and one over u distinct numbers inside it if you ignore the int-conversion step wich already loops once over all of them.
Space:
the total sum and 1 index for each unique number which makes it worstcase O(n+1) in space (each number is also unique)
Time:
normally you get O(n + u) wich is less then the worst case (all numbers are unique) which would be O(2*n). 2 is only a factor - so it is essentially O(n) in time.
If you still have time-problems, using a collections.defaultdict(int) should be faster.

Solution 1 (dict):
One way to it is by using a dictionary for each item, saving all indices and get the difference of last and first occurence for each item. See below:
def get_sum(a):
d={i:[] for i in set(a)}
for i in range(len(a)):
d[a[i]].append(i)
sum=0
for i in d.values():
sum+=i[-1]-i[0]
return sum
Solution 2 (reversed list):
Another way is to use the reversed list and use list.index(item) for both original and reverse list and get the difference. See below:
def get_sum2(a):
m=a[::-1]
return sum(len(m)-m.index(i)-1-a.index(i) for i in set(a))
Output:
>>>get_sum([1,2,3,3,2])
4
>>>get_sum2([1,2,3,3,2])
4

Related

Function that returns the length of the longest run of repetition in a given list

I'm trying to write a function that returns the length of the longest run of repetition in a given list
Here is my code:
def longest_repetition(a):
longest = 0
j = 0
run2 = 0
while j <= len(a)-1:
for i in a:
run = a.count(a[j] == i)
if run == 1:
run2 += 1
if run2 > longest:
longest = run2
j += 1
run2 = 0
return longest
print(longest_repetition([4,1,2,4,7,9,4]))
print(longest_repetition([5,3,5,6,9,4,4,4,4]))
3
0
The first test function works fine, but the second test function is not counting at all and I'm not sure why. Any insight is much appreciated
Just noticed that the question I was given and the expected results are not consistent. So what I'm basically trying to do is find the most repeated element in a list and the output would be the number of times it is repeated. That said, the output for the second test function should be 4 because the element '4' is repeated four times (elements are not required to be in one run as implied in my original question)
First of all, let's check if you were consistent with your question (function that returns the length of the longest run of repetition):
e.g.:
a = [4,1,2,4,7,9,4]
b = [5,3,5,6,9,4,4,4,4]
(assuming, you are only checking single position, e.g. c = [1,2,3,1,2,3] could have one repetition of sequence 1,2,3 - i am assuming that is not your goal)
So:
for a, there is no repetitions of same value, therefore length equals 0
for b, you have one, quadruple repetition of 4, therefore length equals 4
First, your max_amount_of_repetitions=0 and current_repetitions_run=0' So, what you need to do to detect repetition is simply check if value of n-1'th and n'th element is same. If so, you increment current_repetitions_run', else, you reset current_repetitions_run=0.
Last step is check if your current run is longest of all:
max_amount_of_repetitions= max(max_amount_of_repetitions, current_repetitions_run)
to surely get both n-1 and n within your list range, I'd simply start iteration from second element. That way, n-1 is first element.
for n in range(1,len(a)):
if a[n-1] == a[n]:
print("I am sure, you can figure out the rest")
you can use hash to calculate the frequency of the element and then get the max of frequencies.
using functional approach
from collections import Counter
def longest_repitition(array):
return max(Counter(array).values())
other way, without using Counter
def longest_repitition(array):
freq = {}
for val in array:
if val not in freq:
freq[val] = 0
freq[val] += 1
values = freq.values()
return max(values)

most efficient way to iterate over a large array looking for a missing element in Python

I was trying an online test. the test asked to write a function that given a list of up to 100000 integers whose range is 1 to 100000, would find the first missing integer.
for example, if the list is [1,4,5,2] the output should be 3.
I iterated over the list as follow
def find_missing(num)
for i in range(1, 100001):
if i not in num:
return i
the feedback I receives is the code is not efficient in handling big lists.
I am quite new and I couldnot find an answer, how can I iterate more efficiently?
The first improvement would be to make yours linear by using a set for the repeated membership test:
def find_missing(nums)
s = set(nums)
for i in range(1, 100001):
if i not in s:
return i
Given how C-optimized python sorting is, you could also do sth like:
def find_missing(nums)
s = sorted(set(nums))
return next(i for i, n in enumerate(s, 1) if i != n)
But both of these are fairly space inefficient as they create a new collection. You can avoid that with an in-place sort:
from itertools import groupby
def find_missing(nums):
nums.sort() # in-place
return next(i for i, (k, _) in enumerate(groupby(nums), 1) if i != k)
For any range of numbers, the sum is given by Gauss's formula:
# sum of all numbers up to and including nums[-1] minus
# sum of all numbers up to but not including nums[-1]
expected = nums[-1] * (nums[-1] + 1) // 2 - nums[0] * (nums[0] - 1) // 2
If a number is missing, the actual sum will be
actual = sum(nums)
The difference is the missing number:
result = expected - actual
This compulation is O(n), which is as efficient as you can get. expected is an O(1) computation, while actual has to actually add up the elements.
A somewhat slower but similar complexity approach would be to step along the sequence in lockstep with either a range or itertools.count:
for a, e in zip(nums, range(nums[0], len(nums) + nums[0])):
if a != e:
return e # or break if not in a function
Notice the difference between a single comparison a != e, vs a linear containment check like e in nums, which has to iterate on average through half of nums to get the answer.
You can use Counter to count every occurrence of your list. The minimum number with occurrence 0 will be your output. For example:
from collections import Counter
def find_missing():
count = Counter(your_list)
keys = count.keys() #list of every element in increasing order
main_list = list(range(1:100000)) #the list of values from 1 to 100k
missing_numbers = list(set(main_list) - set(keys))
your_output = min(missing_numbers)
return your_output

Matching the first element of a list with other first elements in the list

I am trying to solve the question given in this video https://www.youtube.com/watch?reload=9&v=XCeDBWI4sa4
My list contains sub-lists that constitute each digit of a number of the type strings.
Example: I turned my list of strings
['58','12','50','17'] into four sub-lists like so [['5','8'],['1','2'],['5','0'],['1','7']] because I want to compare the first digit of each number and if the first digits are equal, I increment the variable "pair" which is currently 0. pair=0
Since 58 and 50 have the same first digit, they constitute a pair, same goes for 12 and 17. Also, a pair can only be made if both the numbers are at either even position or odd position. 58 and 50 are at even indices, hence they satisfy the condition. also, at most two pairs can be made for the same first digit. So 51,52, 53 would constitute only 2 pairs instead of three. How do I check this? A simple solution will be appreciated.
list_1=[['5','8'],['1','2'],['5','0'],['1','7']]
and test_list= ['58','12','50','17']
for i in range(0,len(test_list)):
for j in range(1,len(test_list)):
if (list_1[i][0] == list_1[j][0] and (i,j%2==0 or i,j%2==1)):
pair =pair+1
print (pair)
That is what I came up with but I am not getting the desired output.
pair = 0
val_list = ['58','12','50','17', '57', '65', '51']
first_digit, visited_item_list = list(), list()
for item in val_list:
curr = int(item[0])
first_digit.append(curr)
for item in first_digit:
if item not in visited_item_list:
occurences = first_digit.count(item)
if occurences % 2 == 0:
pair = pair + occurences // 2
visited_item_list.append(item)
print(pair)
Using collections.Counter to count occurrences for each first digit. Sum up the totals minus the total number of unique types (to account for more than one).
Iterates over even and odd separately:
Uncomment #return sum(min(c,2) for x in c) - len(c) if you want it to never count more than 2 for digit duplicates. eg: [51,52,53,54,56,57,58,59,50,...] will still return 4, no matter how many more 5X you add. (min(c,2) guarantees the value will never exceed 2)
from collections import Counter
a = ['58','12','50','17','50','18']
def dupes(a):
c = Counter(a).values() # count instances of each element in a, get list of counts
#return sum(min(c,2) for x in c) - len(c) # maximum value of 2 for counts
return sum(c) - len(c) # sum up all the counts, subtract unique elements (you want the counts starting from 0)
even = dupes(a[x][0] for x in range(0, len(a), 2))
# a[x][0]: first digit of even a elements
# range(0, len(a), 2): range of numbers from 0 to length of a, skip by 2 (evens)
# call dupes([list of first digit of even elements])
odd = dupes(a[x][0] for x in range(1, len(a), 2))
# same for odd
print(even+odd)
Here's a fairly simple solution:
import collections
l= [['5','8'],['1','2'],['5','0'],['1','7']]
c = collections.Counter([i[0] for i in l])
# Counter counts the occurrences of items in a list (or other
# collection). After the previous line, c is
# Counter({'5': 2, '1': 2})
sum([c-1 for c in c.values()])
The output, in this case, is 2.

How to find the product of the odd-indexed values in a list

I am able to solve this problem up the the 'even' part but I'm getting stuck in the odd part.
You will be given an array of n numbers. Your task is to first reverse the array (first number becomes last, 2nd number becomes 2nd from the last and so on) and then print the sum of the numbers at even indices and print the product of the numbers at odd indices.
Input
First line contains single integer N: number of elements
followed by N different integers separated by spaces
Output
Two space separated integers representing sum of the numbers at even places and the product of the numbers at odd places.
My code so far:
n = int(input())
arr = [int(x) for x in input().split()]
arr.reverse()
for ele in arr:
print(ele, end=" ")
print()
sum = 0
count = 1
while count <= n:
if count % 2 == 0:
sum += count
count += 1
print(sum)
There's a couple of issues in the code you've supplied which I'll address first:
Firstly, you need to be clear about what is meant by odd and even indices. In some languages (Matlab) for example, the first element of an array is index position 1. In Python and Java it's 0, so, whilst your example has assumed 1, it probably should be 0 unless otherwise specified.
Second, in your line sum+=count you're summing the index positions, not the index values, so that isn't what your question is asking for.
Last point on your code is that you've used sum as a variable name. Whilst that works, sum is also a Python keyword and you should avoid using them as variable names as if you later want to use the sum function, you will get the error TypeError: 'int' object is not callable because you've redefined the sum function to be an integer.
To the answer:
Considering the above, this provides the answer to part 1 by fixing your code:
total = 0
count = 0
while count < n:
if count % 2 == 0:
total += arr[count]
count += 1
print(total)
It's worth noting that as you're looking for even numbers, you could better write that as:
total = 0
count = 0
while count < n:
total += arr[count]
count += 2
print(total)
However, there are even easier ways to do this in much less code, and they involve list slicing. You can slice a list by specifying [start: end: step], so arr[::2] specifies a start of position 0 (the default), an end of the end of the list the default) and a step of 2. This means that if arr contains [1,2,3,4,5,6], then arr[::2] will be [1,3,5] (i.e. the values at all of the even indices) or if you specify a start position of 1 i.e. arr[1::2] you will get [2,4,6] (i.e. the values at all of the even indices).
So, rather using a while loop. You could use a for loop over just the even values:
total = 0
for even_val in arr[::2]:
total += even_val
print(total)
but for sum you can even more easily write is as a simple sum command on the list slice:
print(sum(arr[::2]))
Before Python 3.8, there was no simple equivalent of sum for product, so if you're using a lower version, you can either reuse the method above, allowing for the fact that you would need to prime the total with the first value, then multiply from the next one, i.e.:
total = arr[1]
count = 3
while count < n:
total *= arr[count]
count += 2
print(total)
or with a for loop:
total = arr[1]
for odd_val in arr[3::2]:
total *= odd_val
print(total)
But from Python 3.8 (documentation here) you can now import prod from the math library which will work in the same way as sum:
from math import prod
print(prod(arr[1::2]))
[Thanks #HeapOverflow for the nudge]
As this is for a problem set, it may not be an issue as all examples may have an array length N > 2, but the examples above do assume that there will be at least two entries in arr. If that's not the case, you should put in some validation before trying to access arr[1]
Here's a cute little recursive function to do that (assuming one-based indices):
# def prodSum(increment,multiplier=1,*rest): if zero based indices
def prodSum(multiplier,increment=0,*rest):
if not rest: return multiplier,increment
product,total = prodSum(*rest)
return (product * multiplier, total + increment)
x = [1,2,3,4,5]
print(prodSum(*reversed(x))) # 15,6

How do I find the number of ordered group possible as in a sublist from a super list in python?

Suppose I have a smaller list A = [1,2,3] and larger list B = [1,2,3,1,1,2,2,3,2,3].
B has no other elements except A's but elements order is not maintained.
I want to find how many times A appears in B preserving A's order. For this example A appears 3 times in B. I could solve this for two elements, like [1,2] comes 2 times in [1,1,2,2,1,1]. In other words, I want to find how many ordered group such A is possible from the larger list as B.
From what I understood, you want to count how many times all the elements of A are repeated in order in B, even if there are other elements inbetween.
If that's the case, you can use:
A = [1,2,3]
B = [1,1,1,1,1,1,1,1,1,2,3,1,1,2,2,3,2,3,3,3,3,3,3,3,3]
counters = [0 for _ in A] # initialize a list with the same number of values of A, but all at 0
for x in B: # for each element in B
for n in range(len(A)): # for all the indexes in A
if x == A[n]: # if the element in B is present in A
if n == 0 or (counters[n] < counters[n-1]):
# if n == 0, is the first element of A: we know it's a start of a possible match
# if the previous number in index is higher of the current number, means that we are looking for the match to continue
counters[n] += 1 # add 1 to the current number
break
print counters[-1] # the last number of the counters represent the times that you reached the end of a match
An efficient approach is to build a dict of queues of indices for each item in B, then cycle through items in A to look for the next item in the dict whose index is greater than the index of the last found item by keeping dequeueing until such an index is found or break the loop if any queue is exhausted, with each completed cycle incrementing the count by 1:
from collections import deque
index = {}
for i, n in enumerate(B):
index.setdefault(n, deque()).append(i)
count = 0
while True:
last = -1
try:
for n in A:
while True:
i = index[n].popleft()
if i > last:
last = i
break
except (IndexError, KeyError):
break
count += 1
count becomes:
3

Categories