Take every nth block from list - python

Given a list:
import string
a = list(string.ascii_lowercase)
What is the Pythonic way to return every nth block of m elements? Note that this is different from just returning every nth element.
Desired result of taking every 1st of 3 blocks of 3 elements (take 3, skip 6, take 3, skip 6...):
['a', 'b', 'c', 'j', 'k', 'l', 's', 't', 'u']
I can get to this as:
import itertools
s1 = a[::9]
s2 = a[1::9]
s3 = a[2::9]
res = list(itertools.chain.from_iterable(zip(s1,s2, s3)))
Is there a cleaner way?

For a fixed order of select and skip, you can wrap indices taking the modulo on the total length of the window (9 here) and select only those beneath the given threshold, 3:
lst = [x for i, x in enumerate(a) if i % 9 < 3]
print(lst)
# ['a', 'b', 'c', 'j', 'k', 'l', 's', 't', 'u']
You can make this into a function that makes it more intuitive to use:
def select_skip(iterable, select, skip):
return [x for i, x in enumerate(iterable) if i % (select+skip) < select]
print(select_skip(a, select=3, skip=6))
# ['a', 'b', 'c', 'j', 'k', 'l', 's', 't', 'u']

Perhaps just writing a simple generator is the most readable
def thinger(iterable, take=3, skip=6):
it = iter(iterable)
try:
while True:
for i in range(take):
yield next(it)
for i in range(skip):
next(it)
except StopIteration:
return
This has the advantage of working even if the input is infinite, or not slicable (e.g. data coming in from a socket).

more_itertools is a third-party library that implements itertools recipes and other helpful tools such as more_itertools.windowed.
> pip install more_itertools
Code
import string
from more_itertools import windowed, flatten
m, n = 3, 6
list(flatten(windowed(string.ascii_lowercase, m, step=m+n)))
# ['a', 'b', 'c', 'j', 'k', 'l', 's', 't', 'u']
windowed naturally steps one position per iteration. Given a new step by advancing beyond the overlaps (m), the windows are appropriately determined.

You can do it using some generic "chunks" recipe:
windows = chunks(original_iter, n=3)
Now that you've windowed you're data as you think of it, use islice's second variant for its' 'step' capabilities:
# flattens the list as well using chain
result = chain.from_iterable(islice(windows, 0, None, 2))

You can use a list comprehension and create a function that does this for any skip, take and list values:
import string
import itertools
a = list(string.ascii_lowercase)
def everyNthBlock(a, take, skip):
res = [a[i:i + take] for i in range(0, len(a) ,skip + take)]
return list(itertools.chain(*res))
print(everyNthBlock(a, 3, 6))
#^^^^ => ['a', 'b', 'c', 'j', 'k', 'l', 's', 't', 'u']
print(everyNthBlock(a, 4, 7))
#^^^^ => ['a', 'b', 'c', 'd', 'l', 'm', 'n', 'o', 'w', 'x', 'y', 'z']

Using incomprehensible list comprehension :D
m, n = 3, 3
[elem for blockstart in range(0, len(a), m*n) for elem in a[blockstart:blockstart+n]]
#> ['a', 'b', 'c', 'j', 'k', 'l', 's', 't', 'u']

Related

How can I reference a string (e.g. 'A') to the index of a larger list (e.g. ['A', 'B', 'C', 'D', ...])?

I have been racking my brain and scouring the internet for some hours now, please help.
Effectively I am trying to create a self-contained function (in python) for producing a caesar cipher. I have a list - 'cache' - of all letters A-Z.
def caesarcipher(text, s):
global rawmessage #imports a string input - the 'raw message' which is to be encrypted.
result = ''
cache = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
Is it possible to analyze the string input (the 'rawmessage') and attribute each letter to its subsequent index position in the list 'cache'? e.g. if the input was 'AAA' then the console would recognise it as [0,0,0] in the list 'cache'. Or if the input was 'ABC' then the console would recognise it as [0,1,2] in the list 'cache'.
Thank you to anyone who makes the effort to help me out here.
Use a list comprehension:
positions = [cache.index(letter) for letter in rawmessage if letter in cache]
You can with a list comprehension. Also you can get the letter from string.
import string
print([string.ascii_uppercase.index(c) for c in "AAA"])
# [0, 0, 0]
print([string.ascii_uppercase.index(c) for c in "ABC"])
# [0, 1, 2]
result = []
for i in list(rawmessage):
result.append(cache.index(i))

Python rearrange list based on another list

I want to rearrange a list based on another list which have common elements between them.
my list = ['q','s','b','f','l','c','x','a']
base_list = ['z','a','b','c']
Above lists have common 'a','b' and 'c' as common elements.the expected outcome for is as below
my_result = ['a','b','c','q','s','f','l','x']
Thanks in Advance
Sky
my_list = ['q','s','b','f','l','c','x','a']
base_list = ['z','a','b','c']
res1=[x for x in base_list if x in my_list] # common elements
res2=[x for x in my_list if x not in res1] #
res3=res1+res2
Output :
['a', 'b', 'c', 'q', 's', 'f', 'l', 'x']
Create a custom key for sorted as shown in this document. Set the value arbitrarily high for the letters that don't appear in the base_list so they end up in the back. Since sorted is considered stable those that aren't in the base_list will remain untouched in terms of original order.
l = ['q','s','b','f','l','c','x','a']
base_list = ['z','a','b','c']
def custom_key(letter):
try:
return base_list.index(letter)
except ValueError:
return 1_000
sorted(l, key=custom_key)
['a', 'b', 'c', 'q', 's', 'f', 'l', 'x']
A (probably non optimal) way:
>>> sorted(my_list, key=lambda x: base_list.index(x) if x in base_list
else len(base_list)+1)
['a', 'b', 'c', 'q', 's', 'f', 'l', 'x']

Find all possible "real" word combinations from list in Python, up to x amount of words

I've been experimenting with itertools, combinations and enchant to find all possible (English) words from a list of characters, up to a set (x) amount of words, with no character limit for each word. Can't seem to find/create what I am looking for. Not looking for a handout or freebie, just genuinely stuck on a DnD cipher my friend passed along to me.
Basically, if I have:
char_list = ['i', 't', 'c', 'r', 'r', 's', 'f', 'o', 'k', 'p', 'a', 'e', 'u', 'a']
I'm trying to print:
possible_combos = [["xxx", "xxx", "xxx", "xxx"], ...]
Please don't laugh, but this is what I've been working with. I know it's not right, but I'm having a really hard time understanding exactly what I'm missing.
import itertools
lst = ['i', 't', 'c', 'r', 'r', 's', 'f', 'o', 'k', 'p', 'a', 'e', 'u', 'a']
combinatorics = itertools.product([True, False], repeat=len(lst) - 1)
solution = []
for combination in combinatorics:
i = 0
one_such_combination = [lst[i]]
for slab in combination:
i += 1
if not slab: # there is a join
one_such_combination[-1] += lst[i]
else:
one_such_combination += [lst[i]]
solution.append(one_such_combination)
print(solution)

Remove words from list containing certain characters

I have a long list of words that I'm trying to go through and if the word contains a specific character remove it. However, the solution I thought would work doesn't and doesn't remove any words
l3 = ['b', 'd', 'e', 'f', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y']
firstcheck = ['poach', 'omnificent', 'aminoxylol', 'teetotaller', 'kyathos', 'toxaemic', 'herohead', 'desole', 'nincompoophood', 'dinamode']
validwords = []
for i in l3:
for x in firstchect:
if i not in x:
validwords.append(x)
continue
else:
break
If a word from firstcheck has a character from l3 I want it removed or not added to this other list. I tried it both ways. Can anyone offer insight on what could be going wrong? I'm pretty sure I could use some list comprehension but I'm not very good at that.
The accepted answer makes use of np.sum which means importing a huge numerical library to perform a simple task that the Python kernel can easily do by itself:
validwords = [w for w in firstcheck if all(c not in w for c in l3)]
you can use a list comprehension:
import numpy as np
[w for w in firstcheck if np.sum([c in w for c in l3])==0]
It seems all the words contain at least 1 char from l3 and the output of above is an empty list.
If firstcheck is defined as below:
firstcheck = ['a', 'z', 'poach', 'omnificent']
The code should output:
['a', 'z']
If you want to avoid all loops etc, you can use re directly.
import re
l3 = ['b', 'd', 'e', 'f', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y']
firstcheck = ['azz', 'poach', 'omnificent', 'aminoxylol', 'teetotaller', 'kyathos', 'toxaemic', 'herohead', 'desole', 'nincompoophood', 'dinamode']
# Create a regex string to remove.
strings_to_remove = "[{}]".format("".join(l3))
validwords = [x for x in firstcheck if re.sub(strings_to_remove, '', x) == x]
print(validwords)
Output:
['azz']
Ah, there was some mistake in code, rest was fine:
l3 = ['b', 'd', 'e', 'f', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y']
firstcheck = ['aza', 'ca', 'poach', 'omnificent', 'aminoxylol', 'teetotaller', 'kyathos', 'toxaemic', 'herohead', 'desole', 'nincompoophood', 'dinamode']
validwords = []
flag=1
for x in firstcheck:
for i in l3:
if i not in x:
flag=1
else:
flag=0
break
if(flag==1):
validwords.append(x)
print(validwords)
So, here the first mistake was, the for loops, we need to iterate through words first then, through l3, to avoid the readdition of elements.
Next, firstcheck spelling was wrong in 'for x in firstcheck` due to which error was there.
Also, I added a flag, such that if flag value is 1 it will add the element in validwords.
To, check I added new elements as 'aza' and 'ca', due to which, now it shows correct o/p as 'aza' and 'ca'.
Hope this helps you.

List shuffling by range

I have a list full of strings. I want to take the first 10 values, shuffle them, then replace the first 10 values of the list, then with values 11-20, then 21-30, and so on.
For example:
input_list = ['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t']
and a function called:
shuffle10(input_list)
>>> ['d','b','c','f','j','i','h','a','e','g','m','n','s','r','k','p','l','q','o','t']
I thought it'd work if I defined an empty list and appended every 10 values randomized:
newlist=[]
for i in range(int(len(input_list) / 10)):
newlist.append(shuffle(input_list[(i*10):(i+1)*10]))
print(newlist)
but all this returns is:
[None]
[None, None]
Use random.sample instead of shuffle
>>> input_list = ['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t']
>>> sum((random.sample(input_list[n:n+10], 10) for n in range(0,len(input_list),10)), [])
['f', 'i', 'd', 'a', 'g', 'j', 'e', 'c', 'b', 'h', 'p', 'l', 'r', 'q', 'm', 't', 's', 'n', 'o', 'k']
You're creating a temp list in place and shuffling it but not capturing its results. You can pull out the relevant sublist, shuffle, then create a new list:
new_list=[]
for i in range(1, len(input_list), 10):
list_slice = input_list[i:i + 10]
shuffle(list_slice)
new_list.extend(list_slice)
print(new_list)

Categories