Is there a way to create combinations that preserve the order of elements in a list? - python-3.x

I have a function that supplies me with a list of lists. The length of the list corresponds to the length of the combination, while the length of the sublist corresponds to the different letters that can be used in that position. So, for instance the expected combinations for this list [['W'], ['I'], ['C', 'J'], ['K', 'Y']] are "WICK", "WICY", "WIJK", and "WIJY". I know how to generate those combinations using nested for loops as follows:
for a in lst[0]:
for b in lst[1]:
for c in lst[2]:
for d in lst[3]:
print(a+b+c+d)
However, since the length of each list may vary, doing it manually is not feasible for my program. Is there a way I can do this automatically?

I think what you're looking for is product (short for Cartesian product) which is in the itertools module. You can read about it here.
Here is the sample code:
import itertools as it
data = [['W'], ['I'], ['C', 'J'], ['K', 'Y']] #not a very good variable name
combos = list(it.product(*data))

This is hardcoreded considering 4 elements in Parent List # As per Qn
lst = [['W'], ['I'], ['C', 'J'], ['K', 'Y']]
for a in range(len(lst[0])):
for b in range(len(lst[1])):
for c in range(len(lst[2])):
for d in range(len(lst[3])):
print(lst[0][a]+lst[1][b]+lst[2][c]+lst[3][d])

Related

How to find match between two 2D lists in Python?

Lets say I have two 2D lists like this:
list1 = [ ['A', 5], ['X', 7], ['P', 3]]
list2 = [ ['B', 9], ['C', 5], ['A', 3]]
I want to compare these two lists and find where the 2nd item matches between the two lists e.g here we can see that numbers 5 and 3 appear in both lists. The first item is actually not relevant in comparison.
How do I compare the lists and copy those values that appear in 2nd column of both lists? Using 'x in list' does not work since these are 2D lists. Do I create another copy of the lists with just the 2nd column copied across?
It is possible that this can be done using list comprehension but I am not sure about it so far.
There might be a duplicate for this but I have not found it yet.
The pursuit of one-liners is a futile exercise. They aren't always more efficient than the regular loopy way, and almost always less readable when you're writing anything more complicated than one or two nested loops. So let's get a multi-line solution first. Once we have a working solution, we can try to convert it to a one-liner.
Now the solution you shared in the comments works, but it doesn't handle duplicate elements and also is O(n^2) because it contains a nested loop. https://wiki.python.org/moin/TimeComplexity
list_common = [x[1] for x in list1 for y in list2 if x[1] == y[1]]
A few key things to remember:
A single loop O(n) is better than a nested loop O(n^2).
Membership lookup in a set O(1) is much quicker than lookup in a list O(n).
Sets also get rid of duplicates for you.
Python includes set operations like union, intersection, etc.
Let's code something using these points:
# Create a set containing all numbers from list1
set1 = set(x[1] for x in list1)
# Create a set containing all numbers from list2
set2 = set(x[1] for x in list2)
# Intersection contains numbers in both sets
intersection = set1.intersection(set2)
# If you want, convert this to a list
list_common = list(intersection)
Now, to convert this to a one-liner:
list_common = list(set(x[1] for x in list1).intersection(x[1] for x in list2))
We don't need to explicitly convert x[1] for x in list2 to a set because the set.intersection() function takes generator expressions and internally handles the conversion to a set.
This gives you the result in O(n) time, and also gets rid of duplicates in the process.

Why is sorted() not sorting my list of strings?

Problem:
So I was trying to alphabetically sort my list of strings maybe I overlooked something very minor. I have tried both .sort and sorted() but maybe I didn't do it correctly?
Here is my Code:
words = input("Words: ")
list1 = []
list1.append(words.split())
print(sorted(list1))
Expected output-
Input: "a b d c"
Output: ['a', 'b', 'c', 'd']
Current output-
Input: "a b d c"
Output: [['a', 'b', 'd', 'c']]
Your code is not working because you are trying to sort a list inside a list.
When you call words.split() it returns a list. So when you do list1.append(words.split()) it is appending a list into list1.
You should do this:
words = input("Words: ")
list1 = words.split()
print(sorted(list1))
You can try a simple method as follows:
list1 = [i for i in input('Words: ').split(' ')]
print(sorted(list1))
I've tested it. And it is working
Without deviating from your current effort, the only modification you need to do to fix your code is :
words = input("Words: ")
list1 = []
list1.append(words.split())
print(sorted(list1[0]))
Explanation of what you were doing wrong:
The root cause of your confusion is append() .According to python docs,append() takes exactly one argument.
So when you do this,
words.split()
You are trying to append more than 1 element into the list1 and when you append() something more than 1 in a list, it appends as a nested list (i.e a list inside another list.)
To support my explanation you can see that your code fixed by a simple [0]
print(sorted(list1[0]))
That is because your input is stored as a list of list, AND it is stored in the first index (Point to note - 1st index in a python list is 0, hence the usage of list1[0])
Please let me know if I could have explained it in a more simpler way or if you have any other confusions that aid from the above explanation.

Grouping elements in list by equivalence class

I have an issue when trying to make new lists from one list by applying sets.
Suppose I have the following list:
L=[[(a),(b),(c)],[(b),(c),(a)],[(a),(c),(b)],[(a),(d),(b)]]
And I wish to just creat ONE list from the lists in L which have the same elements. We can clearly see that:
[(a),(b),(c)], [(b),(c),(a)] and [(a),(c),(b)]
when seen as sets, they are the same, because all share the elements (a), (b) and (c).
So if I wish to create new lists from L applying this rule:
I would then need two new lists, which are:
[(a),(b),(c)] and [(a),(d),(b)]
since
[(a),(d),(b)]
seen as a set differs from the rest of the lists.
What would be an optimal way to do this? I know how to convert an element inside L as a set, but if I wish to apply this rule in order to create only two independent lists, what should I do?
A set of frozensets would get you roughly what you want (though it won't preserve order):
unique_sets = {frozenset(lst) for lst in L}
Though order is lost in the set conversion, converting back to a list of lists is fairly easy:
unique_lists = [list(s) for s in unique_sets]
You can make a set of frozensets to get only the unique collections ignoring order and counts of items:
set(map(frozenset, L))
# {frozenset({'a', 'd', 'b'}), frozenset({'a', 'c', 'b'})}
It's then pretty trivial to convert those back to lists:
list(map(list, set(map(frozenset, L))))
# [['a', 'd', 'b'], ['a', 'c', 'b']]
If you'd be willing to write a hash method for set then you could do:
import itertools
[k for k, g in itertools.groupby(sorted([set(y) for y in x], key = your_hash))]

Best way to Sort Python Dictionary by number of same Values

I have a dictionary as follows:
input = {1:'a', 2:'b', 3:'c', 4:'b', 5:'c', 6:'b'}
a - 1 time
b - 3 times
c - 2 times
I want to sort the dictionary in such a way that the value which repeats maximum times will come at fist followed by the value which repeats second most times and so on...
Desired Output
output = {1:'b', 2:'c', 3:'a'}
Please help
Thanks in advance
First of all, you need to realize you do not even care about input dict keys, only about input dict values.
real_input = input.values()
Second of all, you need to count occurences:
counted_items = collections.Counter(real_input)
Third of all, you want to iterate over them in order from most common to least common. .most_common() returns list of (key, count) tuples in expected order.
most_common_in_order = counted_items.most_common()
After that you want to convert that list to dict, with consecutive inegers as keys
result = {i: v for i, (v, _) in zip(itertools.count(1), most_common_in_order)}
Or, in concise form:
result = {i: v for i, (v, _) in zip(itertools.count(1),
collections.Counter(input.values()).most_common())}
Note that dictionaries are inherently not ordered, so actual order is implementation detail, and it's not guaranteed.
from collections import Counter
input = {1: 'a', 2: 'b', 3: 'c', 4: 'b', 5: 'c', 6: 'b'}
output = {index: value for index, (value, _)
in enumerate(Counter(input.values()).most_common(),
start=1)}
print(output)
Edited for Python 3 compatibility.
from collections import Counter
i = {1:'a', 2:'b', 3:'c', 4:'b', 5:'c', 6:'b'}
print (Counter(i.values()))
#Output
Counter({'b': 3, 'c': 2, 'a': 1})

How can I loop through a list of strings?

I have this code:
test = ["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O"]
for i in test:
if i not in ["C","D"]:
test.remove(i)
print(test)
I was expecting to get ['C','D'] as a result of running the code above, however I am getting this ['B', 'C', 'D', 'F', 'H', 'J', 'L', 'N']
How can I successfully loop through a list of strings and delete the elements I don't need using Python 3?
NOTE: I don't want to use comprehension lists
thanks
When removing from lists in other languages, I used to reverse walk the list:
test = ["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O"]
for i in reversed(test):
if i not in ["C","D"]:
test.remove(i)
print(test)
Note that reversed will create a new list so this might not be the best solution for large lists. Now, since you already walk a copy of your list, and if you need to parse in the correct order, you can use copy:
import copy
for i in copy.copy(test):
if i not in ["C","D"]:
test.remove(i)
and to avoid the import (from here):
for i in test[:]:
if i not in ["C","D"]:
test.remove(i)
Finally, the best solution for me, is a traditional, in-place reverse iteration without copying the list ("borrowed" and modified from this answer)
for i in range(len(test) - 1, -1, -1):
if test[i] not in ["C","D"]:
del test[i]
First loop: i = 'A', i not in ['C', 'D'] -> remove i. Now the first item of test is 'B'. So in the next loop i will be equal to 'C'. That is where things gone wrong...

Resources