Checking for a sequence in a python list - python

I have a list [T20, T5, T10, T1, T2, T8, T16, T17, T9, T4, T12, T13, T18]
I have stripped out the T's, coverted to integer type and sorted the list to get this:
sorted_ids=[1, 2, 4, 5, 8, 9, 10, 12, 13, 16, 17, 18, 20]
I'm looping over the list and checking if the next number to current number is in numerical sequence. If not I want to insert a "V" in its position.
So eventually the list should look like: [1, 2, V, 4, 5, V, V, 8, 9, 10, V, 12, 13, V, V, 16, 17, 18, V, 20]
However, I'm not able to insert the exact no of V's at the right positions.
def arrange_tickets(tickets_list):
ids=[]
for item in tickets_list:
new_str=item.strip("T")
ids.append(int(new_str))
sorted_ids = sorted(ids)
temp_ids = []
print("Sorted: ",sorted_ids)
#size = len(sorted_ids)
for i in range(len(sorted_ids)-1):
temp_ids.append(sorted_ids[i])
if sorted_ids[i]+1 != sorted_ids[i+1] :
temp_ids.insert(i+1,"V")
print(temp_ids)
#print(sorted_ids)
tickets_list = ['T20', 'T5', 'T10', 'T1', 'T2', 'T8', 'T16', 'T17', 'T9', 'T4', 'T12', 'T13', 'T18']
print("Ticket ids of all the available students :")
print(tickets_list)
result=arrange_tickets(tickets_list)
Actual Result: [1, 2, 'V', 4, 'V', 5, 8, 'V', 9, 'V', 10, 12, 'V', 13, 16, 17, 18]
Expected Result: [T1, T2, V, T4, T5, V, V, T8, T9, T10, V, T12, T13, V, V, T16, T17, T18, V, T20]

Here is a list comprehension which gets you what you want:
sorted_ids=[1, 2, 4, 5, 8, 9, 10, 12, 13, 16, 17, 18, 20]
a = sorted_ids[0]
b = sorted_ids[-1]
nums = set(sorted_ids)
expected = ["T" + str(i) if i in nums else 'V' for i in range(a,b+1)]
print(expected)
Output:
['T1', 'T2', 'V', 'T4', 'T5', 'V', 'V', 'T8', 'T9', 'T10', 'V', 'T12', 'T13', 'V', 'V', 'T16', 'T17', 'T18', 'V', 'T20']

Here is a solution:
sorted_ids=[1, 2, 4, 5, 8, 9, 10, 12, 13, 16, 17, 18, 20]
def arrange(inList):
newList = []
newList.append('T'+str(inList[0]))
for i in range(1,len(inList)):
diff = inList[i] - inList[i-1]
if diff > 1:
for d in range(diff-1):
newList.append('V')
newList.append('T'+str(inList[i]))
else:
newList.append('T'+str(inList[i]))
return newList
print(arrange(sorted_ids))
Output:
['T1', 'T2', 'V', 'T4', 'T5', 'V', 'V', 'T8', 'T9', 'T10', 'V', 'T12', 'T13', 'V', 'V', 'T16', 'T17', 'T18', 'V', 'T20']

Here's another solution worth considering:
sorted_ids=[1, 2, 4, 5, 8, 9, 10, 12, 13, 16, 17, 18, 20]
for i in range(min(sorted_ids), max(sorted_ids)):
if sorted_ids[i] != i + 1:
sorted_ids.insert(i, 'V')
final_list = [ "T" + str(x) if isinstance(x, int) else x for x in sorted_ids]
result:
['T1', 'T2', 'V', 'T4', 'T5', 'V', 'V', 'T8', 'T9', 'T10', 'V', 'T12', 'T13', 'V', 'V', 'T16', 'T17', 'T18', 'V', 'T20']

temp_ids.insert(i+1,"V")
This is the troublesome statement.
Update your code in following way
temp_ids=[]
for i in range(len(sorted_ids)-1):
temp_ids.append(sorted_ids[i])
if sorted_ids[i]+1 != sorted_ids[i+1] :
for i in range(sorted_ids[i+1]-sorted_ids[i]-1):
temp_ids.append("V") # appends as many V's as required
temp_ids.append(sorted_ids[-1]) # appends last element
This should work
Suppose sorted array is [1,2,6]
So our desired output should be [1,2,'V','V','V',6]. So every time
sorted_ids[i]+1 != sorted_ids[i+1]
condition holds, we will have to append few numbers of V's. Now to determine how many V's we append, see that between 2 and 6 , 3 V's will be appended. So in general we append (sorted_ids[i+1] - sorted[i] -1) V's.
Now see this line
for i in range(len(sorted_ids)-1):
Because of this line, our list only runs for [1,2] in [1,2,6] , and we never append 6 in our For Loop, so after exiting For Loop it was appended.

First consider what ids should be in the list, assuming they start from 1 and end with the largest one present. Then check if each expected id is actually present, and if not put a "V" there. As a side-effect this also sorts the list.
def arrange_tickets(tickets_list):
ids = [int(ticket[1:]) for ticket in tickets_list]
expected_ids = range(1, max(ids) + 1)
return ["T%d" % n if n in ids else "V" for n in expected_ids]
tickets_list = ['T20', 'T5', 'T10', 'T1', 'T2', 'T8', 'T16', 'T17', 'T9', 'T4', 'T12', 'T13', 'T18']
print("Ticket ids of all the available students :")
print(tickets_list)
result=arrange_tickets(tickets_list)
print(result)
result:
Ticket ids of all the available students :
['T20', 'T5', 'T10', 'T1', 'T2', 'T8', 'T16', 'T17', 'T9', 'T4', 'T12', 'T13', 'T18']
['T1', 'T2', 'V', 'T4', 'T5', 'V', 'V', 'T8', 'T9', 'T10', 'V', 'T12', 'T13', 'V', 'V', 'T16', 'T17', 'T18', 'V', 'T20']

You can use this itertools recipe to first group consecutive numbers:
from itertools import groupby
from operator import itemgetter
def groupby_consecutive(lst):
for _, g in groupby(enumerate(lst), lambda x: x[0] - x[1]):
yield list(map(itemgetter(1), g))
sorted_ids = [1, 2, 4, 5, 8, 9, 10, 12, 13, 16, 17, 18, 20]
print(list(groupby_consecutive(lst=sorted_ids)))
# [[1, 2], [4, 5], [8, 9, 10], [12, 13], [16, 17, 18], [20]]
Then you can make a function that gets the interspersing V values from the previous groupings:
def interperse(lst):
for x, y in zip(lst, lst[1:]):
yield ["V"] * (y[0] - x[-1] - 1)
groups = list(groupby_consecutive(lst))
print(list(interperse(groups)))
# [['V'], ['V', 'V'], ['V'], ['V', 'V'], ['V']]
Then you can finally zip the above results together:
def add_prefix(lst, prefix):
return [prefix + str(x) for x in lst]
def create_sequences(lst, prefix='T'):
groups = list(groupby_consecutive(lst))
between = list(interperse(groups))
result = add_prefix(groups[0], prefix)
for x, y in zip(between, groups[1:]):
result.extend(x + add_prefix(y, prefix))
return result
sorted_ids = [1, 2, 4, 5, 8, 9, 10, 12, 13, 16, 17, 18, 20]
print(create_sequences(lst=sorted_ids))
# ['T1', 'T2', 'V', 'T4', 'T5', 'V', 'V', 'T8', 'T9', 'T10', 'V', 'T12', 'T13', 'V', 'V', 'T16', 'T17', 'T18', 'V', 'T20']

In one shot, directly form the original array
array = ['T20', 'T5', 'T10', 'T1', 'T2', 'T8', 'T16', 'T17', 'T9', 'T4', 'T12', 'T13', 'T18']
You can define a method that does all the job:
def add_vs_between_not_cons(array):
iterable = sorted(array, key= lambda x: int(x[1:]))
i, size = 0, len(iterable)
while i < size:
delta = int(iterable[i][1:]) - int(iterable[i-1][1:])
for _ in range(delta-1):
yield "V"
yield iterable[i]
i += 1
So, you can call:
print(list(add_vs_between_not_cons(array)))
#=> ['T1', 'T2', 'V', 'T4', 'T5', 'V', 'V', 'T8', 'T9', 'T10', 'V', 'T12', 'T13', 'V', 'V', 'T16', 'T17', 'T18', 'V', 'T20']

my solution for infytq queries:
def arrange_tickets(tickets_list):
ids = [int(ticket[1:]) for ticket in tickets_list]
expected_ids = range(1, max(ids) + 1)
listt=["T%d" % n if n in ids else "V" for n in expected_ids]
list1=listt[0:10]
list2=listt[11:]
for i in range(10):
if 'V' in list2:
list2.remove('V')
for j in range(0,len(list2)):
for n, i in enumerate(list1):
if i == 'V':
list1[n] = list2[j]
j+=1
return list1
tickets_list = ['T5','T7','T1','T2','T8','T15','T17','T19','T6','T12','T13']
print("Ticket ids of all the available students :")
print(tickets_list)
result=arrange_tickets(tickets_list)
print()
print("Ticket ids of the ten students in Group-1:")
print(result[0:10])

Related

remove the key value if a column is repeated and taking only the first one in nested dictionary

Input: I have a dictionary in this form with a lot more data
d = {
'ag': pd.DataFrame({'ID': ['id1', 'id1', 'id1'], 'name': ['a', 's', 'd'], 'num': [10, 7, 2]}),
'jk': pd.DataFrame({'ID': ['id2', 'id2', 'id2'], 'name': ['w', 'r', 'y'], 'num': [15, 8, 1]}),
'rp': pd.DataFrame({'ID': ['id1', 'id1'], 'name': ['f', 'n'], 'num': [13, 11]})
}
Expected Output: I want to remove the key value from dictionary(d), if the ID(id1) is repeated again in next key(rp).
d = {
'ag': pd.DataFrame({'ID': ['id1', 'id1', 'id1'], 'name': ['a', 's', 'd'], 'num': [10, 7, 2]}),
'jk': pd.DataFrame({'ID': ['id2', 'id2', 'id2'], 'name': ['w', 'r', 'y'], 'num': [15, 8, 1]})
}
code I tried:
new_d = {}
unique_ids = set()
for key in sorted(d.keys()):
key_ids = set(d[key]['ID'].tolist())
if not(key_ids & unique_ids):
new_d[key] = d[key]
unique_ids |= key_ids
print(new_d)
I need a different approach, this is not giving me good results for a large dataset.
Came up with a function to do the task
def remove_duplicate_key(d):
# 'dt' temp variable to iterate over
dt=d.copy()
for i, key in zip(range(len(dt)), dt.keys()):
var = 'id'+str(i+1)
temp_df=dt.get(key, None)
if temp_df['ID'].value_counts().index[0]!=var:
d.pop(key, None)
print(d)
else:
continue
return d
Its creating the variable var='id'+str(i) since id is anyway incrementing. Then call the function remove_duplicate_key(d)

Python. How to modify/replace elements of a list which is a value in a dictionary?

I have two dictionaries:
D_1 = {1: 'A', 2: 'B', 3: 'C', 4: 'D', 5: 'E', 6: 'F', 7: 'G', 8: 'H', 9: 'I', 10: 'J', 11: 'K', 12: 'L', 13: 'M', 14: 'N', 15: 'O', 16: 'P', 17: 'Q', 18: 'R', 19: 'S', 20: 'T', 21: 'U', 22: 'V', 23: 'W', 24: 'X', 25: 'Y', 26: 'Z'}
D_2 = {1: ['A', 'A', 'R', 'O', 'N'], 2: ['A', 'B', 'B', 'E', 'Y'], 3: ['A', 'B', 'B', 'I', 'E']}
My task is to replace the letters in values of D2 with corresponding keys from D1. For example I would want the first key and value pair in D2 to look like this : {1:[1,1,19,16,15]}
I have modified D2 where i made the values into lists instead of strings thinking that it would make my task easier.
dd = {v:k for (k, v) in D_1.items()}
{x: [dd[a] for a in y] for (x, y) in D_2.items()}
First line reverts D_1. Second line applies dd to values in D_2.
I think your D_1 is redundant. What you can do here is to use ascii_uppercase:
from string import ascii_uppercase
{k: [ascii_uppercase.index(i) + 1 for i in v] for k, v in D_2.items()}
Output:
{1: [1, 1, 18, 15, 14], 2: [1, 2, 2, 5, 25], 3: [1, 2, 2, 9, 5]}
Using ord
D_2 = {1: ['A', 'A', 'R', 'O', 'N'], 2: ['A', 'B', 'B', 'E', 'Y'], 3: ['A', 'B', 'B', 'I', 'E']}
{k: [ord(val)-64 for val in v] for (k, v) in D_2.items()}
Output:
{1: [1, 1, 18, 15, 14], 2: [1, 2, 2, 5, 25], 3: [1, 2, 2, 9, 5]}
m = map(lambda v:[{v: k for k, v in D_1.items()}[e] for e in v], D_2.values())
encapsulated in a map and create the new dictionary when useful:
dict(list(enumerate(m, start=1)))
# {1: [1, 1, 18, 15, 14], 2: [1, 2, 2, 5, 25], 3: [1, 2, 2, 9, 5]}

Create dict using a grouping column in an array and assigning the remaining columns to values of the dict

I have a type(s1) = numpy.ndarray. I want to create a dictionary by using the first column of s1 as key and rest as values to the key. The first column has repeated values.
Here is np.array.
s1 = np.array([[1L, 'R', 4],
[1L, 'D', 3],
[1L, 'I', 10],
[1L, 'K', 0.0],
[2L, 'R', 11],
[2L, 'D', 13],
[2L, 'I', 1],
[2L, 'K', 6],
[3L, 'R', 12],
[3L, 'D', 17],
[3L, 'I', 23],
[3L, 'K', 10]], dtype=object)
I want to get the following:
{'1':[['R',4],['D',3],['I',10],['K',0]],
'2':[['R',11],['D',13],['I',1],['K',6]],
'3':[['R',12],['D',17],['I',23],['K',10]]}
This is what I tried and got:
In [18]: {x[0]:[x[1],x[2]] for x in s1}
Out[18]: {1L: ['K', 0.0], 2L: ['D', 6], 3L: ['K', 10]}
I see the problem that the grouping column has repeated values. But I am unable to do the appending. What is the trick I am missing?
You can simply built them with defaultdict :
d=collections.defaultdict(list)
for k,*v in s1 : d[k].append(list(v))
for
defaultdict(list,
{1: [['R', 4], ['D', 3], ['I', 10], ['K', 0.0]],
2: [['R', 11], ['D', 13], ['I', 1], ['K', 6]],
3: [['R', 12], ['D', 17], ['I', 23], ['K', 10]]})
EDIT
You can nest dicts in dicts :
d=collections.defaultdict(dict)
for k1,k2,v in s1 : d[k1][k2]=v
#defaultdict(dict,
# {1: {'D': 3, 'I': 10, 'K': 0.0, 'R': 4},
# 2: {'D': 13, 'I': 1, 'K': 6, 'R': 11},
# 3: {'D': 17, 'I': 23, 'K': 10, 'R': 12}})
In [67]: d[2]['K']
Out[67]: 6
See here for generalization.
You might want to use itertools.groupby():
In [15]: {k: [list(x[1:]) for x in g]
....: for k,g in itertools.groupby(s1, key=lambda x: x[0])}
Out[15]:
{1L: [['R', 4], ['D', 3], ['I', 10], ['K', 0.0]],
2L: [['R', 11], ['D', 13], ['I', 1], ['K', 6]],
3L: [['R', 12], ['D', 17], ['I', 23], ['K', 10]]}

Merge function will only work for ordered list

I have this 2 lists as input:
list1 = [['A', 14, 'I', 10, 20], ['B', 15, 'S', 30, 40], ['C', 16, 'F', 50, 60]]
list2 = [['A', 14, 'Y', 0, 200], ['B', 15, 'M', 0, 400], ['C', 17, 'G', 0, 600]]
and my desired output will be this:
finalList = [['A', 14, 'Y', 10, 200], ['B', 15, 'M', 30, 400], ['C', 16, 'F', 50, 60],['C', 17, 'G', 0, 600]]
Using this function:
def custom_merge(list1, list2):
finalList = []
for sub1, sub2 in zip(list1, list2):
if sub1[1]==sub2[1]:
out = sub1.copy()
out[2] = sub2[2]
out[4] = sub2[4]
finalList.append(out)
else:
finalList.append(sub1)
finalList.append(sub2)
return finalList
I will get indeed my desired output, but what if I switch positions (list2[1] and list2[2]) and my list2:
list2 = [['A', 14, 'Y', 0, 200], ['C', 17, 'G', 0, 600], ['B', 15, 'M', 0, 400]]
Then the output will be this:
[['A', 14, 'Y', 10, 200], ['B', 15, 'S', 30, 40], ['C', 17, 'G', 0, 600], ['C', 16, 'F', 50, 60], ['B', 15, 'M', 0, 400]]
(notice the extra ['B', 15, 'M', 0, 400])
What I have to modify in my function in order to get my first desired output if my lists have a different order in my list of lists!? I use python 3. Thank you!
LATER EDIT:
Merge rules:
When list1[listindex][1] == list2[listindex][1] (ex: when 14==14), replace in list1 -> list2[2] and list2[4] (ex: 'Y' and 200) and if not just add the unmatched list from list2 to list1 as it is (like in my desired output) and also keep the ones that are in list1 that aren't matched(ex: ['C', 16, 'F', 50, 60])
To be noted that list1 and list2 can have different len (list1 can have more lists than list2 or vice versa)
EDIT.2
I found this:
def combine(list1,list2):
combined_list = list1 + list2
final_dict = {tuple(i[:2]):tuple(i[2:]) for i in combined_list}
merged_list = [list(k) + list (final_dict[k]) for k in final_dict]
return merged_list
^^ That could work, still testing!
You can sort the lists by the first element in the sublists before merging them.
def custom_merge(list1, list2):
finalList = []
for sub1, sub2 in zip(sorted(list1), sorted(list2)):
if sub1[1]==sub2[1]:
out = sub1.copy()
out[2] = sub2[2]
out[4] = sub2[4]
finalList.append(out)
else:
finalList.append(sub1)
finalList.append(sub2)
return finalList
tests:
list1 = [['A', 14, 'I', 10, 20], ['B', 15, 'S', 30, 40], ['C', 16, 'F', 50, 60]]
list2 = [['A', 14, 'Y', 0, 200], ['C', 17, 'G', 0, 600], ['B', 15, 'M', 0, 400]]
custom_merge(list1, list2)
# returns:
[['A', 14, 'Y', 10, 200],
['B', 15, 'M', 30, 400],
['C', 16, 'F', 50, 60],
['C', 17, 'G', 0, 600]]

Join operation of 2 lists of lists based on index

I have this 2 list of lists:
list1 = [['A', 14, 'I', 10, 20], ['B', 15, 'S', 30, 40], ['C', 16, 'F', 50, 60]]
list2 = [['A', 14, 'Y', 0, 200], ['B', 15, 'M', 0, 400], ['C', 17, 'G', 0, 600]]
(this is just a sample with only 3 lists, I have more lists but they are on the exact same format and apply same rules)
And this will be my desired output:
finalList = [['A', 14, 'Y', 10, 200], ['B', 15, 'M', 30, 400], ['C', 16, 'F', 50, 60],['C', 17, 'G', 0, 600]]
This is the rule how I compute finalList:
When list1[listindex][1] == list2[listindex][1] (ex: when 14==14), replace in list1 -> list2[2] and list2[4] (ex: 'Y' and 200) and if not just add the unmatched list from list2 to list1 as it is (like in my desired output) and also keep the ones that are in list1 that aren't matched(ex: ['C', 16, 'F', 50, 60]).
How I can do this in a python 3 function? I would like a simple and straight forward function for this. Thank you so much for your time!
You can apply all of your rules using if statements in a function.
def custom_merge(list1, list2):
finalList = []
for sub1, sub2 in zip(list1, list2):
if sub1[1]==sub2[1]:
out = sub1.copy()
out[2] = sub2[2]
out[4] = sub2[4]
finalList.append(out)
else:
finalList.append(sub1)
finalList.append(sub2)
return finalList
For working on the two lists simultaneously you can use zip() Docs Here
For example:
for value in zip(list1, list2):
print (value[0], value[1])
will return:
['A', 14, 'I', 10, 20] ['A', 14, 'Y', 0, 200]
['B', 15, 'S', 30, 40] ['B', 15, 'M', 0, 400]
['C', 16, 'F', 50, 60] ['C', 17, 'G', 0, 600]
so using zip you can work on both your lists at the same time.
Here's one approach that converts the lists into a dict, and takes advantage of the fact that overlapping items from list2 will just overwrite their list1 counterparts:
combined_list = list1 + list2
final_dict = {tuple(i[:2]):tuple(i[2:]) for i in combined_list}
> {('A', 14): ('Y', 0, 200),
('B', 15): ('M', 0, 400),
('C', 16): ('F', 50, 60),
('C', 17): ('G', 0, 600)}
merged_list = [list(k) + list (final_dict[k]) for k in final_dict]
> [['C', 16, 'F', 50, 60],
['B', 15, 'M', 0, 400],
['C', 17, 'G', 0, 600],
['A', 14, 'Y', 0, 200]]
If the ordering of the list is important, you can just sort at the end or use an OrderedDict to create the merge in the first place.
Here's one way to do it using a list comprehension:
lst = [i for x, y in zip(list1, list2)
for i in (([*x[:2], y[2], x[3], y[4]],) if x[1] == y[1] else (x, y))]
print(lst)
# [['A', 14, 'Y', 10, 200], ['B', 15, 'M', 30, 400], ['C', 16, 'F', 50, 60], ['C', 17, 'G', 0, 600]]
The construction of the inner list for the matching case makes it slightly unreadable. It would be much readable in a 'deflattened' form with a for loop:
def merge_lists(list1, list2):
lst = []
for x, y in zip(list1, list2):
if x[1] == y[1]:
lst.append([*x[:2], y[2], x[3], y[4]])
else:
lst.extend((x, y))
return lst
Your "join" algorithm can work on each item independently and is straightforward:
def join_list(item1, item2):
if item1[1] == item2[1]:
result = item1[:]
result[2] = item2[2]
result[4] = item2[4]
return (result,)
return item1[:], item2[:]
This function always returns tuples: singletons in the cas of equality or couples in the general case.
You can apply this function to your two lists list1 and list2, using map() function. But the result will be a list of tuples (in fact a generator in Python 3), so you need to flatten the result:
list1 = [['A', 14, 'I', 10, 20], ['B', 15, 'S', 30, 40], ['C', 16, 'F', 50, 60]]
list2 = [['A', 14, 'Y', 0, 200], ['B', 15, 'M', 0, 400], ['C', 17, 'G', 0, 600]]
joined = [x
for row in map(join_list, list1, list2)
for x in row]
print(joined)
You get what you expect.

Categories

Resources