I'm pretty new to programming and python. I was asked to find out a pair of socks from a given list of numbers.
My question was - "There is a large pile of socks that must be paired by color. Given an array of integers representing the color of each sock, determine how many pairs of socks with matching colors there are."
Sample Input
STDIN Function
----- --------
9 n = 9
10 20 20 10 10 30 50 10 20 ar = [10, 20, 20, 10, 10, 30, 50, 10, 20]
Sample Output
3
So my logic was pretty simple, iterate through the list, take a number, compare it with others. If two same numbers are found, count them as a pair and remove them from the list. Then do the same untiil none are left
# Complete the sockMerchant function below.
def sockMerchant(n, ar):
print(ar)
l=[]
result=0
for i in ar:
a=i
c=0
print("a",a)#line for checking
ar.remove(i)
l=ar
print("ar",ar)#line for checking
print("l", l)#line for checking
for j in l:
f=l.index(j)
print("index", f))#line for checking
print("j",j))#line for checking
if j == a:
c=c+1
print("c",c))#line for checking
ar.remove(j)
print("ar2",ar))#line for checking
result=c/2
print("c2",c))#line for checking
return result
n=9
ar=[10, 20, 20, 10, 10, 30, 50, 10, 20]
sockMerchant(n, ar)
Please ignore the line of code beside the comments. They are just there to see the control flow. and here is my output:
[10, 20, 20, 10, 10, 30, 50, 10, 20]
a 10
ar [20, 20, 10, 10, 30, 50, 10, 20]
l [20, 20, 10, 10, 30, 50, 10, 20]
index 0
j 20
index 0
j 20
index 2
j 10
c 1
ar2 [20, 20, 10, 30, 50, 10, 20]
index 3
j 30
index 4
j 50
index 2
j 10
c 2
ar2 [20, 20, 30, 50, 10, 20]
a 20
ar [20, 30, 50, 10, 20]
l [20, 30, 50, 10, 20]
index 0
j 20
c 1
ar2 [30, 50, 10, 20]
index 1
j 50
index 2
j 10
index 3
j 20
c 2
ar2 [30, 50, 10]
a 10
ar [30, 50]
l [30, 50]
index 0
j 30
index 1
j 50
c2 0
Python is full of wonderful utils that can be helpful. Counters from collections can be used for counting how many socks of each color you got and then you just divide by 2 to get the number of pairs.
from collections import Counter
from typing import List
def sock_merchant(socks: List[int]) -> int:
counter = Counter(ar)
return sum((count // 2 for count in counter.values())
Initializing counter with an array will give you something that looks like
Counter({10: 4, 20: 3, 30: 1, 50: 1})
which is the value from the array (i.e color of the sock) and the number of times it occurs in the array.
Like with normal dicts, counters also have a .values() methods that will give you only the values, and since we want the number of pairs, we take the sum of the values after doing integer division on each of them.
Related
there is a series like below
s = pd.Series([25, 33, 39])
0 25
1 33
2 39
dtype: int64
and a list
list = [5, 10, 20, 26, 30, 31, 32, 35, 40]
[5, 10, 20, 26, 30, 31, 32, 35, 40]
I'd like to find the nearest number in the list and **change the number **in the series
for example
first number is the series is 25
but the list is [5, 10, 20, 26, 30, 31, 32, 35, 40]
so the firtst nearest number(corresponding to 25 in the series)
is 20 (Actually 26 is nearest number, but I need a number less than 25)
and then the second number is 31, thrid is 35
after finding the number and change that in the series
desired out s is
0 20
1 31
2 35
please give me advice. It's a very important task for me.
if possilbe? without for loop plz
Find the nearest number(but not exceed) in the list and change numbers in the series(Python)
You are looking for merge_asof:
s = pd.Series([25, 33, 39], name="s")
l = pd.Series([5, 10, 20, 26, 30, 31, 32, 35, 40], name="l")
pd.merge_asof(s, l, left_on="s", right_on="l")
A few notes:
There is a bug in your expected output. The closest number to 33 is 32.
Don't name your variable list. It overwrites the name of a very common Python class.
Make sure l is sorted.
What would be the most efficient way to find the frequency/count of elements in non-overlapping intervals? For example:
limits = [0, 25, 40, 60]
data = [15, 5, 2, 56, 45, 23, 6, 59, 33, 18]
For the above lists, I want to find the number of elements in data that are within two adjacent limits. So for the above, the count would be something like:
0-25: 6;
25-40: 1;
40-60: 3;
All I can think of is O(n^2) in time. Is there a better way to do it?
Doesn't need Counter to count as number of bins is known, swaps dict to array accesses for binning..
from bisect import bisect_right
def bin_it(limits, data):
"Bin data according to (ascending) limits."
bins = [0] * (len(limits) + 1) # adds under/over range bins too
for d in data:
bins[bisect_right(limits, d)] += 1
return bins
if __name__ == "__main__":
limits = [0, 25, 40, 60]
data = [15, 5, 2, 56, 45, 23, 6, 59, 33, 18]
bins = bin_it(limits, data)
print(f" < {limits[0]:2} :", bins[0])
for lo, hi, count in zip(limits, limits[1:], bins[1:]):
print(f">= {lo:2} .. < {hi:2} :", count)
print(f">= {limits[-1]:2} ... :", bins[-1])
"""
SAMPLE OUTPUT:
< 0 : 0
>= 0 .. < 25 : 6
>= 25 .. < 40 : 1
>= 40 .. < 60 : 3
>= 60 ... : 0
"""
I recommend you this approach which implements what you want in order of O(nlogn)
limits = [0, 25, 40, 60] # m
data = [15, 5, 2, 56, 45, 23, 6, 59, 33, 18] # n
data += limits # O(n+m)
data.sort() # O((n+m)log(n+m)) = O(nlogn)
result=dict() # O(1)
cnt = 0 # O(1)
aux ='' # O(1)
i = 0 # O(1)
for el in data: # O(n+m)
if el == limits[i]:
i+=1
if cnt > 0:
aux+='-'+str(el)
result[aux] = cnt # average = O(1)
cnt = 0
aux = str(el)
else:
aux = str(el)
else:
cnt+=1
print(result)
# {'0-25': 6, '25-40': 1, '40-60': 3}
I showed the time complexity of each important line to calculate the total time complexity of the code. the total time complexity of the code is equal to O((n+m)log(n+m)) which can be shown as O(nlogn).
Improvement
you can improve it if you have some assumptions about the inputs. if you have info about the range of limits and data, then you can change the sorting algorithm to counting sort. the time complexity of counting sort is considered as O(n) and the total time complexity of code would be O(n)
Here is a simple O(NlogN) approach. Sort your data, then use a two pointer approach to place each element in the correct interval.
limits = [0, 25, 40, 60]
data = [15, 5, 2, 56, 45, 23, 6, 59, 33, 18]
data.sort()
n,m = len(data), len(limits)
count = [0]*(m-1)
# count[i] represents count between limits[i] and limits[i+1]
low = 0 # lower index of interval we are currently checking
ptr = 0
while ptr < n:
i = data[ptr]
if i >= limits[low] and i <= limits[low+1]:
count[low] += 1
ptr += 1
elif i>=limits[low]:
if low == len(limits)-1:
break
low += 1
print(count)
limits = [0, 25, 40, 60, 80]
data = [15, 5, 2, 56, 45, 23, 6, 59, 33, 18, 25, 45, 85]
dict_data = {}
i = 0
count = 1
while i < len(limits)-1:
for j in data:
if j in range(limits[i], limits[i+1]):
if '{}-{}'.format(limits[i],limits[i+1]) in dict_data:
dict_data['{}-{}'.format(limits[i],limits[i+1])] +=count
else:
dict_data['{}-{}'.format(limits[i],limits[i+1])] = count
i+=1
print(dict_data)
You could use Counter (from collections) to manage the tallying and bisect to categorize:
from collections import Counter
from bisect import bisect_left
limits = [0, 25, 40, 60, 80]
data = [15, 5, 2, 56, 45, 23, 6, 59, 33, 18]
r = Counter(limits[bisect_left(limits,d)-1] for d in data)
print(r)
Counter({0: 6, 40: 3, 25: 1})
This has a time complexity of O(NLogM) where M is the number of limit breaks and N is the number of data items
I am able to generate the desired output but i need 10 of them and each list has to be unicue. The best solution i thought of was to create a 2nd function, generate a emty list and populate each element with list from 1st function. The output i got so far is x amount of lists but they are not unique and python gives me error when i try to call on the first function inside the 2nd one.
import random
numbers = list(range(1, 35))
out = []
final = []
print(numbers) # to see all numbers
# returns 7 unique pop'd numbers from list 'numbers' and appends them to list 'out'
def do():
for x in range(7):
out.append(numbers.pop(random.randrange(len(numbers))))
print(sorted(out))
# In other words i want to print output from function do() 10 times but each item in list has to be unique, not the lists themself
def do_ten():
for x in range(10):
final.append(out)
# do() python doesnt like this call
print(sorted(final))
do_ten()
This generates a specific amount of lists, in a list, which contain random numbers from 1 to 100, you can use l and n to control the amount of lists and numbers respectively.
import random
l, n = 3, 5 # The amount of lists and numbers respectively.
lis = [[i for i in random.sample(range(1, 35), n)] for group in range(l)]
print(lis)
Random Output:
[[16, 11, 17, 13, 9], [26, 6, 16, 29, 24], [24, 2, 4, 1, 20]]
You are popping 10 times 7 numbers from a list containing 34 elements (from 1 to 34). This is not possible. You need to have at least 70 elements in your list numbers(for example, from 0 to 69).
This is a solution that should work, based on the code you've already written:
import random
numbers = list(range(0, 70))
final = []
print(numbers) # to see all numbers
# returns a list of 7 unique popped numbers from list 'numbers'
def do():
out = []
for x in range(7):
l = len(numbers)
r = random.randrange(l)
t = numbers.pop(r)
out.append(t)
return out
# Call 10 times do() and add the returned list to 'final'
def do_ten():
for x in range(10):
out = do() # Get result from do()
final.append(out) # Add it to 'final'
do_ten()
print(final)
Does it help:
num_lists = 10
len_list = 10
[list(np.random.randint(1,11,len_list)) for _ in range(num_lists)]
As some people may have different definitin of "uniqueness", you may try:
source_list = range(0, num_lists*len_list,1)
[list(np.random.choice(source_list, len_list, replace=False)) for _ in range(num_lists)]
Pulling 7 of 34 numbers from your numberrange without repeats can be done using random.sample - to ensure you do not get duplicate lists, you can add a tuple of the list to a set and your final result and only add to final if this tuple is not yet in the set:
import random
numbers = range(1, 35) # 1...34
final = []
chosen = set()
while len(final) < 10:
# replace popping numbers with random.sample
one_list = random.sample(numbers, k=7) # 7 numbers out of 34 - no repeats
# create a tuple of this list and only add to final if not yet added
t = tuple(one_list)
if t not in chosen:
chosen.add(t)
final.append(one_list)
print (final)
Output:
[[1, 5, 10, 26, 14, 33, 6],
[3, 11, 1, 30, 7, 21, 18],
[24, 23, 28, 2, 13, 18, 1],
[4, 25, 32, 15, 22, 8, 27],
[32, 9, 10, 16, 17, 26, 12],
[34, 32, 10, 26, 16, 21, 20],
[6, 34, 22, 11, 26, 12, 5],
[29, 17, 25, 15, 3, 6, 5],
[24, 8, 31, 28, 17, 12, 15],
[6, 19, 11, 22, 30, 33, 15]]
If you dont need unique resulting lists, you can simplify this to a one-liner but it might have dupes inside:
final = [random.sample(range(1,11),k=7) for _ in range(10)]
I have an array [ 0 10 15 20 10 0 35 25 15 35 0 30 20 25 30 0] and I need to insert each element of another array ' [5,7,8,15] ' at locations with an increment of 5 such that the final array looks [ 0 10 15 20 5 10 0 35 25 7 15 35 0 30 8 20 25 30 0 15] length is 20
I am trying with this code
arr_fla = [ 0 10 15 20 10 0 35 25 15 35 0 30 20 25 30 0]
arr_split = [5,7,8,15]
node = 5
node_len = node * (node-1)
for w in range(node, node_len, 5):
for v in arr_split:
arr_fla = np.insert(arr_fla,w,v)
print(arr_fla)
The result I am getting is
'[ 0 10 15 20 10 15 8 7 5 0 15 8 7 5 35 15 8 7 5 25 15 35 0 30
20 25 30 0]' length 28
Can someone please tell me where I am going wrong.
If the sizes line up as cleanly as in your example you can use reshape ...
np.reshape(arr_fla,(len(arr_split),-1))
# array([[ 0, 10, 15, 20],
# [10, 0, 35, 25],
# [15, 35, 0, 30],
# [20, 25, 30, 0]])
... append arr_split as a new column ...
np.c_[np.reshape(arr_fla,(len(arr_split),-1)),arr_split]
# array([[ 0, 10, 15, 20, 5],
# [10, 0, 35, 25, 7],
# [15, 35, 0, 30, 8],
# [20, 25, 30, 0, 15]])
... and flatten again ...
np.c_[np.reshape(arr_fla,(len(arr_split),-1)),arr_split].ravel()
# array([ 0, 10, 15, 20, 5, 10, 0, 35, 25, 7, 15, 35, 0, 30, 8, 20, 25,
# 30, 0, 15])
I have corrected it:
arr_fla = [0,10,15,20,10,0,35,25,15,35,0,30,20,25,30,0]
arr_split = [5,7,8,15]
node = 5
for w in range(len(arr_split)):
arr_fla = np.insert(arr_fla, (w+1)*node-1, arr_split[w])
print(arr_fla)
'''
Output:
[ 0 10 15 20 5 10 0 35 25 7 15 35 0 30 8 20 25 30 0 15]
'''
In your code:
for v in arr_split:
This gets all the elements at once (in total w times), but you need just one element at a time. Thus you do not need an extra for loop.
You want to have a counter that keeps going up every time you insert the item from your second array arr_split.
Try this code. My assumption is that your last element can be inserted directly as the original array has only 16 elements.
arr_fla = [0,10,15,20,10,0,35,25,15,35,0,30,20,25,30,0]
arr_split = [5,7,8,15]
j = 0 #use this as a counter to insert from arr_split
#start iterating from 4th position as you want to insert in the 5th position
for i in range(4,len(arr_fla),5):
arr_fla.insert(i,arr_split[j]) #insert at the 5th position every time
#every time you insert an element, the array size increase
j +=1 #increase the counter by 1 so you can insert the next element
arr_fla.append(arr_split[j]) #add the final element to the original array
print(arr_fla)
Output:
[0, 10, 15, 20, 5, 10, 0, 35, 25, 7, 15, 35, 0, 30, 8, 20, 25, 30, 0, 15]
You could split the list in even chunks, append to each the split values to each chunk, and reassemble the whole (credit to Ned Batchelder for the chunk function ):
arr_fla = [0,10,15,20,10,0,35,25,15,35,0,30,20,25,30,0]
arr_split = [5,7,8,15]
node = 5
def chunks(lst, n):
"""Yield successive n-sized chunks from lst."""
for i in range(0, len(lst), n):
yield lst[i:i + n]
tmp_arr = chunks(arr_fla, node)
arr_out = []
for index, chunk in enumerate(tmp_arr):
if arr_split[index]: # make sure arr_split is not exhausted
chunk.append(arr_split[index]) # we use the index of the chunks list to access the split number to insert
arr_out += chunk
print(arr_out)
Outputs:
[0, 10, 15, 20, 10, 5, 0, 35, 25, 15, 35, 7, 0, 30, 20, 25, 30, 8, 0, 15]
you can change to below and have a try.
import numpy as np
arr_fla = [0, 10, 15, 20, 10, 0, 35, 25, 15, 35, 0, 30, 20, 25, 30, 0]
arr_split = [5, 7, 8, 15]
index = 4
for ele in arr_split:
arr_fla = np.insert(arr_fla, index, ele)
index += 5
print(arr_fla)
the result is
[ 0 10 15 20 5 10 0 35 25 7 15 35 0 30 8 20 25 30 0 15]
about the wrong part of yours, I think it's have two questions:
the second loop is no need, it will cause np insert all the element of arr_split at the same position
the position is not start at 5, it should be 4
given a list of strings like so (in reality I have a much longer list but I'll keep it short for here):
items=['fish','headphones','wineglass','bowtie','cheese','hammer','socks']
I would like to pick a subset, say 3, of this list randomly so that items can only get picked once. This is easy enough using the following:
import itertools
import random
def random_combination(iterable, r):
"Random selection from itertools.combinations(iterable, r)"
pool = tuple(iterable)
n = len(pool)
indices = sorted(random.sample(xrange(n), r))
return tuple(pool[i] for i in indices)
items=['fish','headphones','wineglass','bowtie','cheese','hammer','socks']
randomPick=random_combination(items,3)
Next, to be a pain, I don't want to do this just once, but several times say 10 times. The final product would be 10 lists of randomly-only-picked-once items with the constraint that over those 10 lists items are presented an equal amount of times across lists. I'd like to avoid the "socks" to be picked up 10 times and the "hammer" only once for example.
This is the step that I'm stuck with, I simply don't know enough programming or enough about the available functions in python to perform such a thing.
Can anyone help?
The following code might help. It pops a random element until (a copy of) iterable is empty, then starts over from the entire list. The downside is every item is picked once before a single item can be picked a second time. However, as you can see from the output, the distribution of items ends up about equal.
import random
def equal_distribution_combinations(iterable, n, csize):
"""
Yield 'n' lists of size 'csize' containing distinct random elements
from 'iterable.' Elements of 'iterable' are approximately evenly
distributed across all yielded combinations.
"""
i_copy = list(iterable)
if csize > len(i_copy):
raise ValueError(
"csize cannot exceed len(iterable), as elements could not distinct."
)
for i in range(n):
comb = []
for j in range(csize):
if not i_copy:
i_copy = list(iterable)
randi = random.randint(0, len(i_copy) - 1)
# If i_coppy was reinstantiated it would be possible to have
# duplicate elements in comb without this check.
while i_copy[randi] in comb:
randi = random.randint(0, len(i_copy) - 1)
comb.append(i_copy.pop(randi))
yield comb
Edit
Apologies for Python 3. The only change to the function for Python 2 should be range -> xrange.
Edit 2 (answering comment question)
equal_distribution_combinations should result in an even distribution for any n, csize, and length of iterable, as long as csize does not exceed len(iterable) (as the combination elements could not be distinct).
Here's a test using the specific numbers in your comment:
items = range(30)
item_counts = {k: 0 for k in items}
for comb in equal_distribution_combinations(items, 10, 10):
print(comb)
for e in comb:
item_counts[e] += 1
print('')
for k, v in item_counts.items():
print('Item: {0} Count: {1}'.format(k, v))
Output:
[19, 28, 3, 20, 2, 9, 0, 25, 27, 12]
[29, 5, 22, 10, 1, 8, 17, 21, 14, 4]
[16, 13, 26, 6, 23, 11, 15, 18, 7, 24]
[26, 14, 18, 20, 16, 0, 1, 11, 10, 2]
[27, 21, 28, 24, 25, 12, 13, 19, 22, 6]
[23, 3, 8, 4, 15, 5, 29, 9, 7, 17]
[11, 1, 8, 28, 3, 13, 7, 26, 16, 23]
[9, 29, 14, 15, 17, 21, 18, 24, 12, 10]
[19, 20, 0, 2, 25, 5, 22, 4, 27, 6]
[12, 13, 24, 28, 6, 7, 26, 17, 25, 23]
Item: 0 Count: 3
Item: 1 Count: 3
Item: 2 Count: 3
Item: 3 Count: 3
Item: 4 Count: 3
Item: 5 Count: 3
Item: 6 Count: 4
Item: 7 Count: 4
Item: 8 Count: 3
Item: 9 Count: 3
Item: 10 Count: 3
Item: 11 Count: 3
Item: 12 Count: 4
Item: 13 Count: 4
Item: 14 Count: 3
Item: 15 Count: 3
Item: 16 Count: 3
Item: 17 Count: 4
Item: 18 Count: 3
Item: 19 Count: 3
Item: 20 Count: 3
Item: 21 Count: 3
Item: 22 Count: 3
Item: 23 Count: 4
Item: 24 Count: 4
Item: 25 Count: 4
Item: 26 Count: 4
Item: 27 Count: 3
Item: 28 Count: 4
Item: 29 Count: 3
As can be seen, the items are evenly distributed.
i would do something like this:
items = set(items)
res = []
for _ in xrange(10):
r = random.sample(items, 3)
res.append(r)
items -= set(r)
all this does is grab 3 elements, store them, and then subtract them from the original list so they can't be selected again.
Ok in the end I resorted in doing the following.
It is a more constrained implementation where I set the number of times I want to see an item repeat, for example over the 10 lists I want each items to be picked 5 times:
List = ['airplane',
'fish',
'watch',
'balloon',
'headphones',
'wineglass',
'bowtie',
'guitar',
'desk',
'bottle',
'glove'] #there is more in my final list but keeping it short here
numIters = 5
numItems = len(List)
finalList=[]
for curList in range(numIters):
random.shuffle(List)
finalList.append(List[0 : numItems/2]) #append first list
finalList.append(List[numItems/2 : -1]) #append second list
return finalList