Get all possible ordered sublists of a list - python

Let's say I have a list with the following letters:
lst=['A','B','C','D']
And I need to get all the possible sublists of that list that maintain the order. Thus, the result must be:
res=['A'
'AB'
'ABC'
'ABCD'
'B'
'BC'
'BCD'
'C'
'CD'
'D']
I had implemebted the following for loop, but an error occurs, saying that "TypeError:Can only concatenate str (not "list) to str"
res=[]
for x in range(len(lst)):
for y in range(len(lst)):
if x==y:
res.appebd(x)
if y>x:
res.append(lst[x]+lst[y:len(lst)-1]
Is there a better and more efficient way to do this?

lst=['A','B','C','D']
out = []
for i in range(len(lst)):
for j in range(i, len(lst)):
out.append( ''.join(lst[i:j+1]) )
print(out)
Prints:
['A', 'AB', 'ABC', 'ABCD', 'B', 'BC', 'BCD', 'C', 'CD', 'D']

Rather than nested loops with redefined inner loop bounds on each go, you can use itertools to generate the bounds for you:
from itertools import combinations
lst = ['A','B','C','D']
out = []
for s, e in combinations(range(len(lst) + 1), 2):
out.append(''.join(lst[s:e]))
combinations conveniently produces all possible start and end indices from a single range, producing each set one at a time in your desired order. It also simplifies the code enough that the equivalent listcomp isn't too unreadable, allowing you to condense three lines of code down to one:
out = [''.join(lst[s:e]) for s, e in combinations(range(len(lst) + 1), 2)]
Either way, out ends up with the value:
['A', 'AB', 'ABC', 'ABCD', 'B', 'BC', 'BCD', 'C', 'CD', 'D']

This is probably the closest to what you have got and will produce the desired result:
res=[]
for x in range(len(lst)):
for y in range(len(lst)):
if x==y:
res.append(lst[x])
if y>x:
res.append(''.join(lst[x:y+1]))
The error you are describing mean that you are trying to add a character to a list:
lst[x]+lst[y:len(lst)-1]
lst[x] is a character and lst[y:len(lst)-1] is a list of characters and python does not know how to add it together. It can add a character and a string though using a join function.

Related

counting a permutation 4 characters long with 2 characters on each result

from itertools import permutations
perm=permutations(['A','B','C','C','D','D','D','D'],4)
for i in perm:
print (i)
how could I print a permutation of which the value in perm only prints a series of letters with 2 characters (pardon my English)
example : ADDD,DADD,BDDD,CCDD,CDDD etc (only 2 characters for every permutation)
I think for this you will have to generate all combinations, and filter down to the condition you want.
Keep this bit the same:
from itertools import permutations
perm = permutations(['A', 'B', 'C', 'C', 'D', 'D', 'D', 'D'], 4)
But then keep only the elements which satisfy your condition, by using list comprehension. Convert the element to a set, and count the length of the set. An element like ('A', 'B', 'B') gets converted to {'A', 'B'}.
perm = [x for x in perm if len(set(x))==2]
for i in perm:
if len((set(list(i))) == 2:
print (i)
lets say i is ABAA
list(i) will result in [A,B,A,A]
set() of that will result in {A,B}
then len() of that will be 2

Basic Sorting / Order Algorithm

Trying to implement and form a very simple algorithm. This algorithm takes in a sequence of letters or numbers. It first creates an array (list) out of each character or digit. Then it checks each individual character compared with the following character in the sequence. If the two are equal, it removes the character from the array.
For example the input: 12223344112233 or AAAABBBCCCDDAAABB
And the output should be: 1234123 or ABCDAB
I believe the issue stems from the fact I created a counter and increment each loop. I use this counter for my comparison using the counter as an index marker in the array. Although, each time I remove an item from the array it changes the index while the counter increases.
Here is the code I have:
def sort(i):
iter = list(i)
counter = 0
for item in iter:
if item == iter[counter + 1]:
del iter[counter]
counter = counter + 1
return iter
You're iterating over the same list that you are deleting from. That usually causes behaviour that you would not expect. Make a copy of the list & iterate over that.
However, there is a simpler solution: Use itertools.groupby
import itertools
def sort(i):
return [x for x, _ in itertools.groupby(list(i))]
print(sort('12223344112233'))
Output:
['1', '2', '3', '4', '1', '2', '3']
A few alternatives, all using s = 'AAAABBBCCCDDAAABB' as setup:
>>> import re
>>> re.sub(r'(.)\1+', r'\1', s)
'ABCDAB'
>>> p = None
>>> [c for c in s if p != (p := c)]
['A', 'B', 'C', 'D', 'A', 'B']
>>> [c for c, p in zip(s, [None] + list(s)) if c != p]
['A', 'B', 'C', 'D', 'A', 'B']
>>> [c for i, c in enumerate(s) if not s.endswith(c, None, i)]
['A', 'B', 'C', 'D', 'A', 'B']
The other answers a good. This one iterates over the list in reverse to prevent skipping items, and uses the look ahead type algorithm OP described. Quick note OP this really isn't a sorting algorithm.
def sort(input_str: str) -> str:
as_list = list(input_str)
for idx in range(len(as_list), 0, -1)):
if item == as_list[idx-1]:
del as_list[idx]
return ''.join(as_list)

Getting all combinations of a string and its substrings

I've seen many questions on getting all the possible substrings (i.e., adjacent sets of characters), but none on generating all possible strings including the combinations of its substrings.
For example, let:
x = 'abc'
I would like the output to be something like:
['abc', 'ab', 'ac', 'bc', 'a', 'b', 'c']
The main point is that we can remove multiple characters that are not adjacent in the original string (as well as the adjacent ones).
Here is what I have tried so far:
def return_substrings(input_string):
length = len(input_string)
return [input_string[i:j + 1] for i in range(length) for j in range(i, length)]
print(return_substrings('abc'))
However, this only removes sets of adjacent strings from the original string, and will not return the element 'ac' from the example above.
Another example is if we use the string 'abcde', the output list should contain the elements 'ace', 'bd' etc.
You can do this easily using itertools.combinations
>>> from itertools import combinations
>>> x = 'abc'
>>> [''.join(l) for i in range(len(x)) for l in combinations(x, i+1)]
['a', 'b', 'c', 'ab', 'ac', 'bc', 'abc']
If you want it in the reversed order, you can make the range function return its sequence in reversed order
>>> [''.join(l) for i in range(len(x),0,-1) for l in combinations(x, i)]
['abc', 'ab', 'ac', 'bc', 'a', 'b', 'c']
This is a fun exercise. I think other answers may use itertools.product or itertools.combinations. But just for fun, you can also do this recursively with something like
def subs(string, ret=['']):
if len(string) == 0:
return ret
head, tail = string[0], string[1:]
ret = ret + list(map(lambda x: x+head, ret))
return subs(tail, ret)
subs('abc')
# returns ['', 'a', 'b', 'ab', 'c', 'ac', 'bc', 'abc']
#Sunitha answer provided the right tool to use. I will just go and suggest an improved way while using your return_substrings method. Basically, my solution will take care of duplicates.
I will use "ABCA" in order to prove validity of my solution. Note that it would include a duplicate 'A' in the returned list of the accepted answer.
Python 3.7+ solution,
x= "ABCA"
def return_substrings(x):
all_combnations = [''.join(l) for i in range(len(x)) for l in combinations(x, i+1)]
return list(reversed(list(dict.fromkeys(all_combnations))))
# return list(dict.fromkeys(all_combnations)) for none-reversed ordering
print(return_substrings(x))
>>>>['ABCA', 'BCA', 'ACA', 'ABA', 'ABC', 'CA', 'BA', 'BC', 'AA', 'AC', 'AB', 'C', 'B', 'A']
Python 2.7 solution,
You'll have to use OrderedDict instead of a normal dict. Therefore,
return list(reversed(list(dict.fromkeys(all_combnations))))
becomes
return list(reversed(list(OrderedDict.fromkeys(all_combnations))))
Order is irrelevant for you ?
You can reduce code complexity if order is not relevant,
x= "ABCA"
def return_substrings(x):
all_combnations = [''.join(l) for i in range(len(x)) for l in combinations(x, i+1)]
return list(set(all_combnations))
def return_substrings(s):
all_sub = set()
recent = {s}
while recent:
tmp = set()
for word in recent:
for i in range(len(word)):
tmp.add(word[:i] + word[i + 1:])
all_sub.update(recent)
recent = tmp
return all_sub
For an overkill / different version of the accepted answer (expressing combinations using https://docs.python.org/3/library/itertools.html#itertools.product ):
["".join(["abc"[y[0]] for y in x if y[1]]) for x in map(enumerate, itertools.product((False, True), repeat=3))]
For a more visual interpretation, consider all substrings as a mapping of all bitstrings of length n.

Access list elements that are not equal to a specific value

I am searching through a list like this:
my_list = [['a','b'],['b','c'],['a','x'],['f','r']]
and I want to see which elements come with 'a'. So first I have to find lists in which 'a' occurs. Then get access to the other element of the list. I do this by abs(pair.index('a')-1)
for pair in my_list:
if 'a' in pair:
print( pair[abs(pair.index('a')-1)] )
Is there any better pythonic way to do that?
Something like: pair.index(not 'a') maybe?
UPDATE:
Maybe it is good to point out that 'a' is not necessarily the first element.
in my case, ['a','a'] doesn't happen, but generally maybe it's good to choose a solution which handles this situation too
Are you looking for elements that accompany a? If so, a simple list comprehension will do:
In [110]: [x for x in my_list if 'a' in x]
Out[110]: [['a', 'b'], ['a', 'x']]
If you just want the elements and not the pairs, how about getting rid of a before printing:
In [112]: [(set(x) - {'a'}).pop() for x in my_list if 'a' in x]
Out[112]: ['b', 'x']
I use a set because a could either be the first or second element in the pair.
If I understand your question correctly, the following should work:
my_list = filter(
lambda e: 'a' not in e,
my_list
)
Note that in python 3, this returns a filter object instance. You may want to wrap the code in a list() command to get a list instance instead.
That technique works ok here, but it may be more efficient, and slightly more readable, to do it using sets. Here's one way to do that.
def paired_with(seq, ch):
chset = set(ch)
return [(set(pair) - chset).pop() for pair in seq if ch in pair]
my_list = [['a','b'], ['b','c'], ['x','a'], ['f','r']]
print(paired_with(my_list, 'a'))
output
['b', 'x']
If you want to do lots of tests on the same list, it would be more efficient to build a list of sets.
def paired_with(seq, ch):
chset = set(ch)
return [(pair - chset).pop() for pair in seq if ch in pair]
my_list = [['a','b'], ['b','c'], ['x','a'], ['f','r']]
my_sets = [set(u) for u in my_list]
print(my_sets)
print(paired_with(my_sets, 'a'))
output
[{'b', 'a'}, {'c', 'b'}, {'x', 'a'}, {'r', 'f'}]
['b', 'x']
This will fail if there's a pair like ['a', 'a'], but we can easily fix that:
def paired_with(seq, ch):
chset = set(ch)
return [(pair - chset or chset).pop() for pair in seq if ch in pair]
my_list = [['a','b'], ['b','c'], ['x','a'], ['f','r'], ['a', 'a']]
my_sets = [set(u) for u in my_list]
print(paired_with(my_sets, 'a'))
output
['b', 'x', 'a']

how to correctly modify the iterator of a loop in python from within the loop

what I basically need is to check every element of a list and if some criteria fit I want to remove it from the list.
So for example let's say that
list=['a','b','c','d','e']
I basically want to write (in principle and not the actual code I try to implement)
If an element of the list is 'b' or 'c' remove it from the list and take the next.
But
for s in list:
if s=='b' or s=='c':
list.remove(s)
fails because when 'b' is removed the loop takes 'd' and not 'c' as the next element. So is there a way to do that faster than storing the elements in a separate list and removing them afterwards?
Thanks.
The easier way is to use a copy of the list - it can be done with a slice that extends "from the beginning" to the "end" of the list, like this:
for s in list[:]:
if s=='b' or s=='c':
list.remove(s)
You have considered this, and this is simple enough to be in your code, unless this list is really big, and in a critical part of the code (like, in the main loop of an action game). In that case, I sometimes use the following idiom:
to_remove = []
for index, s in enumerate(list):
if s == "b" or s == "c":
to_remove.append(index)
for index in reversed(to_remove):
del list[index]
Of course you can resort to a while loop instead:
index = 0
while index < len(list):
if s == "b" or s == "c":
del list[index]
continue
index += 1
Its better not to reinvent things which are already available. Use filter functions and lambda in these cases. Its more pythonic and looks cleaner.
filter(lambda x:x not in ['b','c'],['a','b','c','d','e'])
alternatively you can use list comprehension
[x for x in ['a','b','c','d','e'] if x not in ['b','c']]
This is exactly what itertools.ifilter is designed for.
from itertools import ifilter
ifilter(lambda x: x not in ['b', 'c'], ['a', 'b', 'c', 'd', 'e'])
will give you back a generator for your list. If you actually need a list, you can create it using one of the standard techniques for converting a generator to a list:
list(ifilter(lambda x: x not in ['b', 'c'], ['a', 'b', 'c', 'd', 'e']))
or
[x for x in ifilter(lambda x: x not in ['b', 'c'], ['a', 'b', 'c', 'd', 'e'])]
If you are ok with creating a copy of the list you can do it like this (list comprehension):
[s for s in list if s != 'b' and s != 'c']

Categories