Filtering out a generator - python

Whats the best way to filter out some subsets from a generator. For example I have a string "1023" and want to produce all possible combinations of each of the digits. All combinations would be:
['1', '0', '2', '3']
['1', '0', '23']
['1', '02', '3']
['1', '023']
['10', '2', '3']
['10', '23']
['102', '3']
['1023']
I am not interested in a subset that contains a leading 0 on any of the items, so the valid ones are:
['1', '0', '2', '3']
['1', '0', '23']
['10', '2', '3']
['10', '23']
['102', '3']
['1023']
I have two questions.
1) If using a generator, whats the best way to filter out the ones with leading zeroes. Currently, I generate all combinations then loop through it afterwards and only continuing if the subset is valid. For simplicity I am only printing the subset in the sample code. Assuming the generator that was created is very long or if it constains a lot of invalid subsets, its almost a waste to loop through the entire generator. Is there a way to stop the generator when it sees an invalid item (one with leading zero) then filter it off 'allCombinations'
2) If the above doesn't exist, whats a better way to generate these combinations (disregarding combinations with leading zeroes).
Code using a generator:
import itertools
def isValid(subset): ## DIGITS WITH LEADING 0 IS NOT VALID
valid = True
for num in subset:
if num[0] == '0' and len(num) > 1:
valid = False
break
return valid
def get_combinations(source, comb):
res = ""
for x, action in zip(source, comb + (0,)):
res += x
if action == 0:
yield res
res = ""
digits = "1023"
allCombinations = [list(get_combinations(digits, c)) for c in itertools.product((0, 1), repeat=len(digits) - 1)]
for subset in allCombinations: ## LOOPS THROUGH THE ENTIRE GENERATOR
if isValid(subset):
print(subset)

Filtering for an easy and obvious condition like "no leading zeros", it can be more efficiently done at the combination building level.
def generate_pieces(input_string, predicate):
if input_string:
if predicate(input_string):
yield [input_string]
for item_size in range(1, len(input_string)+1):
item = input_string[:item_size]
if not predicate(item):
continue
rest = input_string[item_size:]
for rest_piece in generate_pieces(rest, predicate):
yield [item] + rest_piece
Generating every combination of cuts, so long it's not even funny:
>>> list(generate_pieces('10002', lambda x: True))
[['10002'], ['1', '0002'], ['1', '0', '002'], ['1', '0', '0', '02'], ['1', '0', '0', '0', '2'], ['1', '0', '00', '2'], ['1', '00', '02'], ['1', '00', '0', '2'], ['1', '000', '2'], ['10', '002'], ['10', '0', '02'], ['10', '0', '0', '2'], ['10', '00', '2'], ['100', '02'], ['100', '0', '2'], ['1000', '2']]
Only those where no fragment has leading zeros:
>>> list(generate_pieces('10002', lambda x: not x.startswith('0')))
[['10002'], ['1000', '2']]
Substrings that start with a zero were never considered for the recursive step.

One common solution is to try filtering just before using yield. I have given you an example of filtering just before yield:
import itertools
def my_gen(my_string):
# Create combinations
for length in range(len(my_string)):
for my_tuple in itertools.combinations(my_string, length+1):
# This is the string you would like to output
output_string = "".join(my_tuple)
# filter here:
if output_string[0] != '0':
yield output_string
my_string = '1023'
print(list(my_gen(my_string)))
EDIT: Added in a generator comprehension alternative
import itertools
my_string = '1023'
my_gen = ("".join(my_tuple)[0] for length in range(len(my_string))
for my_tuple in itertools.combinations(my_string, length+1)
if "".join(my_tuple)[0] != '0')

Related

Find all possible varients of max pair of 2

Given a string of numbers like 123456, I want to find all the possibilities they can be paired in by 2 or by itself. For example, from the string 123456 I would like to get the following:
12 3 4 5 6, 12 34 5 6, 1 23 4 56, etc.
The nearest I was able to come to was this:
strr = list("123456")
x = list("123456")
for i in range(int(len(strr)/2)):
newlist = []
for j in range(i):
newlist.append(x[j])
newlist.append(x[i] + x[i+1])
for j in range(len(x))[i+2:]:
newlist.append(x[j])
x = newlist.copy()
b = x.copy()
for f in range(len(b))[i:]:
if f == i:
print(b)
continue
b[f] = b[f - 1][1] + b[f]
b[f - 1] = b[f - 1][0]
print(b)
This code gives the output:
It's easy to solve this problem with a recursive generator. This is similar to how you solve change-making problems, just here we have only two "coins", either two characters together, or one character at a time. The total change we're trying to make is the length of the input string. The fact that the characters are digits in a numeric string is irrelevant.
def singles_and_pairs(string):
if len(string) <= 1: # base case
yield list(string) # yield either [] or [string] and then quit
return
for result in singles_and_pairs(string[:-1]): # first recursion
result.append(string[-1:])
yield result
for result in singles_and_pairs(string[:-2]): # second recursion
result.append(string[-2:])
yield result
If you plan on running this on large input strings, you might want to add memoization, since the recursive calls recalculate the same results quite often.
Pheew, this one took me some time to get right, but it seems to finally work (edited for prettier ordering):
def max_2_partitions(my_string):
if not my_string:
return [[]]
if len(my_string) == 1:
return [[my_string]]
ret = []
for i in range(len(my_string)):
for l in max_2_partitions(my_string[:i] + my_string[i + 1:]):
li = sorted([my_string[i]]+l, key = lambda x: (len(x),x))
if li not in ret:
ret.append(li)
for j in range(i+1,len(my_string)):
for l in max_2_partitions(my_string[:i]+my_string[i+1:j]+my_string[j+1:]):
li = sorted([my_string[i] + my_string[j]] + l, key = lambda x: (len(x),x))
if li not in ret:
ret.append(li)
return sorted(ret, key=lambda x: (-len(x),x))
Example:
print(max_2_partitions("1234"))
# [['1', '2', '3', '4'], ['1', '2', '34'], ['1', '3', '24'], ['1', '4', '23'], ['2', '3', '14'], ['2', '4', '13'], ['3', '4', '12'], ['12', '34'], ['13', '24'], ['14', '23']]
12 lines of code, full permutations:
You can first create permutations of the string, and then add spacing:
from itertools import permutations
def solution(A):
result = []
def dfs(A,B):
if not B:
result.append(A)
else:
for i in range(1,min(2,len(B))+1):
dfs(A+[B[:i]],B[i:])
for x in permutations(A):
dfs([],''.join(x))
return result
print(f"{solution('123') = }")
# solution('123') = [['1', '2', '3'], ['1', '23'], ['12', '3'], ['1', '3', '2'], ['1', '32'], ['13', '2'], ['2', '1', '3'], ['2', '13'], ['21', '3'], ['2', '3', '1'], ['2', '31'], ['23', '1'], ['3', '1', '2'], ['3', '12'], ['31', '2'], ['3', '2', '1'], ['3', '21'], ['32', '1']]

how to check where an element is in a list

i am trying to check where in the list a element is so i can replace it in that position in a separate list
i have tried to do this using .index but this only works when done in reverse
variable = input("enter a number")
list1 = ['1','2','3','4']
list2 = ['0','0','0','0']
L = 0
L = list1.index(variable)
list2.pop(L)
list2.insert(L, variable)
print(list1)
print(list2
i expected the output to be:
['1','2','3','4']
['1','0','3','0']
but the output was:
['1','2','3','4']
['3','0','0','0']
The del statement There is a way to remove an item from a list given its index instead of its value: the del statement. This differs from the pop() method which returns a value. The del statement can also be used to remove slices from a list or clear the entire list (which we did earlier by assignment of an empty list to the slice) reference
Code:
list1 = ['1', '2', '3', '4']
list2 = ['0', '0', '0', '0']
variable = '1'
idx = list1.index(variable)
del list1[idx]
list2[idx] = variable
print(f"list1: {list1}")
print(f"list2: {list2}")
Output:
list1: ['2', '3', '4']
list2: ['1', '0', '0', '0']
Also, you can delete a slice of a list with del:
>>> list1 = ['1', '2', '3', '4']
>>> del list1[2:3]
>>> list1
['1', '2', '4']
If you use insert() you will get this:
list.insert(i, x) Insert an item at a given position. The first argument is the index of the element before which to insert, so a.insert(0, x) inserts at the front of the list, and a.insert(len(a), x) is equivalent to a.append(x). reference
Code:
list1 = ['1', '2', '3', '4']
list2 = ['0', '0', '0', '0']
variable = '1'
idx = list1.index(variable)
del list1[idx]
list2.insert(idx, variable)
print(f"list1: {list1}")
print(f"list2: {list2}")
Output:
list1: ['2', '3', '4']
list2: ['1', '0', '0', '0', '0']
Implement this with pop():
list.pop([i]) Remove the item at the given position in the list, and return it. If no index is specified, a.pop() removes and returns the last item in the list. (The square brackets around the i in the method signature denote that the parameter is optional, not that you should type square brackets at that position. You will see this notation frequently in the Python Library Reference.) reference
Code:
def do_some_stuff(variable):
list1 = ['1', '2', '3', '4']
list2 = ['0', '0', '0', '0']
idx = list1.index(variable)
list2[idx] = list1.pop(idx)
print(f"list1: {list1}")
print(f"list2: {list2}")
if __name__ == '__main__':
do_some_stuff(variable='1')
Output:
list1: ['2', '3', '4']
list2: ['1', '0', '0', '0']
The .index isn't the problem, the .pop/.insert is.
Instead of:
list2.pop(L)
list2.insert(L, guess)
do:
list2[L] = guess

How to create lists in list where every future list is separate by spaces in list

User provides input with spaces:
row = list(input())
print(row)
['1','2','3',' ','4','5','6',' ','7','8','9',' ']
So I need to create 'row' list into the below. The list is divided into sub-lists based on whitespace:
[['1','2','3'],['4','5','6'],['7','8','9']]
You can use str.split to split by whitespace:
myinput = '123 456 789'
row = list(map(list, myinput.split()))
print(row)
[['1', '2', '3'], ['4', '5', '6'], ['7', '8', '9']]
Alternatively, using a list comprehension:
row = [list(i) for i in myinput.split()]
You can usestr.split to split the input on spaces to give a list of sub-strings.
E.g. '123 456 789' would become ['123', '456', '789'].
Then use a list-comprehension to convert these strings into lists of characters with the list() constructor (as you are already familiar with).
Making the final code:
row = [list(s) for s in input().split()]
#[['1', '2', '3'], ['4', '5', '6'], ['7', '8', '9']]
Starting with your list rather than the string, you can do that using itetools.groupby:
from itertools import groupby
row = ['1','2','3',' ','4','5','6',' ','7','8','9',' ']
out = [list(group) for key, group in groupby(row, lambda x: x != ' ') if key]
print(out)
# [['1', '2', '3'], ['4', '5', '6'], ['7', '8', '9']]
We group the values depending on whether or not they are spaces, and only keep the groups that are not made of spaces.
Try this:
abc=['1','2','3',' ','4','5','6',' ','7','8','9',' ']
newList=list()
temp=list()
for i in abc:
if(i==' '):
newList.append(temp)
temp=list()
else:
temp.append(i)
print(newList)
Output:
[['1', '2', '3'], ['4', '5', '6'], ['7', '8', '9']]

If all 9 strings in list is replaced: do something

As the title:
If all 9 strings in list is replaced, then a task should run.
Here is the code:
list = [
['0', '0', '0'],
['0', '0', '0'],
['0', '0', '0']
]
If all of them is either replaced by a "1" or "2" doesnt matter which is replaced by what. Then it should run a task.
So how do I if all of the 9 spots have been replaced by either a "1" or "2"?
There is simply too many possibilities to write all the combinations down, and compare them to the list.
How about this,
s = set(item for sublist in lists for item in sublist) # flat a list of lists into a set
if '0' not in s:
do_something()
You may achieve it using set() and itertools.chain() function as:
from itertools import chain
set(chain(*my_list)).issubset('12')
# ^ ^ ^ all items in set are either '1' or '2'
# ^ ^ creates a single list comprising the sub-list
# ^ uniques values in the chained list
where my_list is your nested list.
Note: Do not use list as variable type because list is the built-in keyword denoting the list data-type in Python.
Sample run:
# Sample function
>>> def check_list(my_list):
... return set(chain(*my_list)).issubset('12')
...
# Test Run:
>>> check_list([['0', '0'], ['0', '0']])
False
>>> check_list([['0', '1'], ['0', '0']])
False
>>> check_list([['1', '1'], ['1', '1']])
True
>>> check_list([['1', '2'], ['1', '2']])
True
Never name a variable a reserved word, i.e. list. But here is an easy way.
l = [
['0', '0', '0'],
['0', '0', '0'],
['0', '0', '0']
]
if all(all(int(x) for x in row) for row in l):
#do something
To check if they have been replaced by '1' or '2' and nothing else
all(all(i in ('1', '2') for i in sublist) for sublist in list)
I read your question is how to I check to see if all sublists contain only '1' and '2'?
>>> mylist = [list('121'),list('111'), list('222')]
>>> mylist
[['1', '2', '1'], ['1', '1', '1'], ['2', '2', '2']]
>>> all(item in ('1', '2') for sublist in mylist for item in sublist)
True
>>> mylist[0][0] = '0'
>>> mylist
[['0', '2', '1'], ['1', '1', '1'], ['2', '2', '2']]
>>> all(item in ('1', '2') for sublist in mylist for item in sublist)
False

How could i refresh a list once an item has been removed from a list within a list in python

This is quite complicated but i would like to be able to refresh a larger list once at item has been taken out of a mini list within the bigger list.
listA = ['1','2','3','4','5','6','6','8','9','5','3','7']
i used the code below to split it into lists of threes
split = [listA[i:(i+3)] for i in range(0, len(listA) - 1, 3)]
print(split)
# [['1','2','3'],['4','5','6'],['6','8','9'],['5','3','7']]
split = [['1','2','3'],['4','5','6'],['6','8','9'],['5','3','7']]
if i deleted #3 from the first list, split will now be
del split[0][-1]
split = [['1','2'],['4','5','6'],['6','8','9'],['5','3','7']]
after #3 has been deleted, i would like to be able to refresh the list so that it looks like;
split = [['1','2','4'],['5','6','6'],['8','9','5'],['3','7']]
thanks in advance
Not sure how big this list is getting, but you would need to flatten it and recalculate it:
>>> listA = ['1','2','3','4','5','6','6','8','9','5','3','7']
>>> split = [listA[i:(i+3)] for i in range(0, len(listA) - 1, 3)]
>>> split
[['1', '2', '3'], ['4', '5', '6'], ['6', '8', '9'], ['5', '3', '7']]
>>> del split[0][-1]
>>> split
[['1', '2'], ['4', '5', '6'], ['6', '8', '9'], ['5', '3', '7']]
>>> listA = sum(split, []) # <- flatten split list back to 1 level
>>> listA
['1', '2', '4', '5', '6', '6', '8', '9', '5', '3', '7']
>>> split = [listA[i:(i+3)] for i in range(0, len(listA) - 1, 3)]
>>> split
[['1', '2', '4'], ['5', '6', '6'], ['8', '9', '5'], ['3', '7']]
Just recreate the single list from your nested lists, then re-split.
You can join the lists, assuming they are only one level deep, with something like:
rejoined = [element for sublist in split for element in sublist]
There are no doubt fancier ways, or single-liners that use itertools or some other library, but don't overthink it. If you're only talking about a few hundred or even a few thousand items this solution is quite good enough.
I need this for turning of cards in the deck in a solitaire game.
You can deal your cards using itertools.groupby() with a good key function:
def group_key(x, n=3, flag=[0], counter=itertools.count(0)):
if next(counter) % n == 0:
flag[0] = flag[0] ^ 1
return flag[0]
^ is a bitwise operator, basically it change the value of the flag from 0 to 1 and viceversa. The flag value is an element of a list because we're doing some kind of memoization.
Example:
>>> deck = ['1', '2', '3', '4', '5', '6', '6', '8', '9', '5', '3', '7']
>>> for k,g in itertools.groupby(deck, key=group_key):
... print(list(g))
['1', '2', '3']
['4', '5', '6']
['6', '8', '9']
['5', '3', '7']
Now let's say you've used card '9' and '8', so your new deck looks like:
>>> deck = ['1', '2', '3', '4', '5', '6', '6', '5', '3', '7']
>>> for k,g in itertools.groupby(deck, key=group_key):
... print(list(g))
['1', '2', '3']
['4', '5', '6']
['6', '5', '3']
['7']
Build an object that contains a list and tracks when the list is altered (probably by controlling write to it), then have the object do it's own split every time the data is altered and save the split list to a member of the object.

Categories

Resources