Combinations with repetition of letters with weight from list - python

I have four letters with different weights as
letters = ['C', 'N', 'O', 'S']
weights_of_l = [1, 1, 2, 2]
I want to get the combinations of letters which weight = 2. The letter can be repeatedly chose and order is not important. The result can be list or array or any forms but with this combinations
comb_w2 = ['CC','NN','NC','O','S']
Here C and N has weight = 1 so combining two letters have weight = 2: The possible combinations are 'CC','NN','NC'
O and S has weight = 2 already so it cannot combine with other letters. Is there any libraries for calculating this? I saw itertools but it gives only the number of possibility, not the combinations.

Yours is a problem of partitioning (not easy stuff).
You can use this post to generate all possible combination outcomes for a given weight. Then you can delete the ones that contain keys which you don't have in weights_of_l. Finally you substitute the numbers by the letters and create permutations for they letters that have the same weight.

My answer ended up being very similar to Turksarama's. Namely, if you need the combination of results, you have to sort the letters and use a set to get rid of the duplicates. My approach is more succinct, but requires calling set() with the function call.
letters = ['C', 'N', 'O', 'S']
weights = [1, 1, 2, 2]
items = list(zip(weights, letters))
def combinations(items, max_weight, weight=0, word=''):
if weight == max_weight:
yield ''.join(sorted(word))
items_allowed = [(w, l) for w, l in items if max_weight - weight >= w]
for w, l in items_allowed:
for result in combinations(items_allowed, max_weight, weight+w, word+l):
yield result
print(set(combinations(items, 2)))

#Sneha has a nice and succinct answer, but if you're going to have a lot of combinations then it might be better to to not go too far in creating combinations. This solution is longer but will run faster for long lists of letters with large goal scores:
letters = ['C', 'N', 'O', 'S']
weights_of_l = [1, 1, 2, 2]
def get_combos(letters, weights, goal):
weighted_letters = list(zip(letters, weights))
combos = set()
def get_combos(letters, weight):
for letter, next_weight in weighted_letters:
total = weight + next_weight
if total == goal:
combos.add(''.join(sorted(letters + letter)))
elif total > goal:
pass
else:
get_combos(letters + letter, weight+next_weight)
get_combos('',0)
return combos
print(get_combos(letters, weights_of_l, 3))
EDIT: I think this one might be even faster:
letters = ['C', 'N', 'O', 'S']
weights_of_l = [1, 1, 2, 2]
def get_combos(letters, weights, goal):
weighted_letters = sorted(zip(weights, letters))
combos = []
def get_combos(letters, weight, weighted_letters):
for i, (next_weight, letter) in enumerate(weighted_letters):
total = weight + next_weight
if total == goal:
combos.append(letters + letter)
elif total > goal:
return
else:
get_combos(letters+letter, weight+next_weight, weighted_letters[i:])
get_combos('',0,weighted_letters)
return combos
print(get_combos(letters, weights_of_l, 3))

Create all combinations of the letters and use filter function to remove all combinations with combined weight not equal to 2.
from itertools import combinations_with_replacement
letters = ['C', 'N', 'O', 'S']
weights_of_l = [1, 1, 2, 2]
y=dict(zip(letters,weights_of_l)) #Creates a dict of the two list letters
#and weights_of_l
print(list(map(lambda x:''.join(x),filter(lambda
x:y[x[0]]+y[x[1]]==2,combinations_with_replacement(letters,2)))))
Or you can initially filter all letters in the letters list to include those which has weight less than 2 or the weight you require and then create all combinations.

Try the following code:
def find_combinations(letter_list, weight_list, weignt_sum):
output_list = []
letter_weight_dict = dict(zip(letter_list,weight_list))
for key, val in letter_weight_dict.items():
for key1, val1 in letter_weight_dict.items():
if val+val1 == weignt_sum:
if (key + key1)[::-1] not in output_list:
output_list.append(key+key1)
if val == weignt_sum:
output_list.append(key)
return set(output_list)
letters = ['C', 'N', 'O', 'S']
weights_of_l = [1, 1, 2, 2]
combinations = find_combinations(letters, weights_of_l, 2)
print combinations
I got the following output:
['CC', 'S', 'NN', 'CN', 'O']
This may not be the best way to do this.

Related

Error "index out of range" when working with strings in a for loop in python

I'm very new to python and I'm practicing different exercises.
I need to write a program to decode a string. The original string has been modified by adding, after each vowel (letters ’a’, ’e’, ’i’, ’o’ and ’u’), the letter ’p’ and then that same vowel again.
For example, the word “kemija” becomes “kepemipijapa” and the word “paprika” becomes “papapripikapa”.
vowel = ['a', 'e', 'i', 'o', 'u']
input_word = list(input())
for i in range(len(input_word)):
if input_word[i] in vowel:
input_word.pop(i + 1)
input_word.pop(i + 2)
print(input_word)
The algorithm I had in mind was to detect the index for which the item is a vowel and then remove the following 2 items after this item ,so if input_word[0] == 'e' then the next 2 items (input_word[1], input_word[2]) must be removed from the list. For the sample input zepelepenapa, I get this error message : IndexError: pop index out of range even when I change the for loop to range(len(input_word) - 2) ,again I get this same error.
thanks in advance
The loop will run a number of times equal to the original length of input_word, due to range(len(input_word)). An IndexError will occur if input_word is shortened inside the loop, because the code inside the loop tries to access every element in the original list input_word with the expression input_word[i] (and, for some values of input_word, the if block could even attempt to pop items off the list beyond its original length, due to the (i + 1) and (i + 2)).
Hardcoding the loop definition with a specific number like 2, e.g. with range(len(input_word) - 2), to make it run fewer times to account for removed letters isn't a general solution, because the number of letters to be removed is initially unknown (it could be 0, 2, 4, ...).
Here are a couple of possible solutions:
Instead of removing items from input_word, create a new list output_word and add letters to it if they meet the criteria. Use a helper list skip_these_indices to keep track of indices that should be "removed" from input_word so they can be skipped when building up the new list output_word:
vowel = ['a', 'e', 'i', 'o', 'u']
input_word = list("zepelepenapa")
output_word = []
skip_these_indices = []
for i in range(len(input_word)):
# if letter 'i' shouldn't be skipped, add it to output_word
if i not in skip_these_indices:
output_word.append(input_word[i])
# check whether to skip the next two letters after 'i'
if input_word[i] in vowel:
skip_these_indices.append(i + 1)
skip_these_indices.append(i + 2)
print(skip_these_indices) # [2, 3, 6, 7, 10, 11]
print(output_word) # ['z', 'e', 'l', 'e', 'n', 'a']
print(''.join(output_word)) # zelena
Alternatively, use two loops. The first loop will keep track of which letters should be removed in a list called remove_these_indices. The second loop will remove them from input_word:
vowel = ['a', 'e', 'i', 'o', 'u']
input_word = list("zepelepenapa")
remove_these_indices = []
# loop 1 -- find letters to remove
for i in range(len(input_word)):
# if letter 'i' isn't already marked for removal,
# check whether we should remove the next two letters
if i not in remove_these_indices:
if input_word[i] in vowel:
remove_these_indices.append(i + 1)
remove_these_indices.append(i + 2)
# loop 2 -- remove the letters (pop in reverse to avoid IndexError)
for i in reversed(remove_these_indices):
# if input_word has a vowel in the last two positions,
# without a "p" and the same vowel after it,
# which it shouldn't based on the algorithm you
# described for generating the coded word,
# this 'if' statement will avoid popping
# elements that don't exist
if i < len(input_word):
input_word.pop(i)
print(remove_these_indices) # [2, 3, 6, 7, 10, 11]
print(input_word) # ['z', 'e', 'l', 'e', 'n', 'a']
print(''.join(input_word)) # zelena
pop() removes an item at the given position in the list and returns it. This alters the list in place.
For example if I have:
my_list = [1,2,3,4]
n = my_list.pop()
will return n = 4 in this instance. If I was to print my_list after this operation it would return [1,2,3]. So the length of the list will change every time pop() is used. That is why you are getting IndexError: pop index out of range.
So to solve this we should avoid using pop() since it's really not needed in this situation. The following will work:
word = 'kemija'
vowels = ['a', 'e', 'i', 'o', 'u']
new_word = []
for w in word:
if w in vowels:
new_word.extend([w,'p',w])
# alternatively you could use .append() over .extend() but would need more lines:
# new_word.append(w)
# new_word.append('p')
# new_word.append(w)
else:
new_word.append(w)
decoded_word = ''.join(new_word)
print(decoded_word)

How to create a list which only adds letters that are unique to adjacent indices in python?

I have created a function which randomly generates a list of the letters "a", "b", "c", and "d". I would like to create a new list which is the same as the first list but with any letters/items which are the same as the previous letter/item removed. Where I am having problems is referring to the previous letter in the list.
For example, if :
letterlist = ['a','a','a','b','b','a,',b']
then the output should be,
nondupelist = ['a','b','a','b']
The problem is that nodupeletterlist is the same as letterlist - meaning it's not removing items which are the same as the last - because I am getting the function to refer to the previous item in letterlist wrong. I have tried using index and enumerate, but I am obviously using them wrong because I'm not getting the correct results. Below is my current attempt.
import random
def rdmlist(letterlist, nodupeletterlist):
for item in range(20):
rng = random.random()
if rng < 0.25:
letterlist.append("a")
elif 0.25 <= rng and rng < 0.5:
letterlist.append("b")
elif 0.5 <= rng and rng < 0.75:
letterlist.append("c")
else:
letterlist.append("d")
for letter in letterlist:
if letter != letterlist[letterlist.index(letter)-1]:
nodupeletterlist.append(letter)
else:
pass
return
letterlist1 = []
nodupeletterlist1 = []
rdmlist(letterlist1, nodupeletterlist1)
EDIT:
This is what I ended up using. I used this solution simply because I understand how it works. The answers below may provide more succinct or pythonic solutions.
for index, letter in enumerate(letterlist, start=0):
if 0 == index:
nodupeletterlist.append(letter)
else:
pass
for index, letter in enumerate(letterlist[1:], start = 1):
if letter != letterlist[index-1]:
nodupeletterlist.append(letter)
else:
pass
for i, letter in enumerate(([None]+letterlist)[1:], 1):
if letter != letterlist[i-1]:
nodupeletterlist.append(letter)
You can use itertools.groupby:
import itertools
nodupeletterlist = [k for k, _ in itertools.groupby(letterlist)]
Solution without using itertools, as requested in the comments:
def nodupe(letters):
if not letters:
return []
r = [letters[0]]
for ch in letters[1:]:
if ch != r[-1]:
r.append(ch)
return r
nodupeletterlist = nodupe(letterlist)
A fixed version of the proposed "working solution":
def nodupe(letters):
if not letters:
return []
r = [letters[0]]
r += [l for i, l in enumerate(letters[1:]) if l != letters[i]]
return r
nodupeletterlist = nodupe(letterlist)
You can also simplify your random generator a bit, by using random.choices:
import random
chars = 'abcd'
letterlist = random.choices(chars, k=20)
or by using random.randint:
import random
start, end = ord('a'), ord('d')
letterlist = [chr(random.randint(start, end)) for _ in range(20)]
Here's what I came up with. Using random.choices() would be better than what I have below, but same idea. doesn't involve itertools
>>> li_1 = [random.choice("abcdefg") for i in range(20)]
>>> li_1
['c', 'e', 'e', 'g', 'b', 'd', 'b', 'g', 'd', 'c', 'e', 'g', 'e', 'c', 'd',
'e', 'e', 'f', 'd', 'd']
>>>
>>> li_2 = [li_1[i] for i in range(len(li_1))
... if not i or i and li_1[i - 1] != li_1[i]]
>>> li_2
['c', 'e', 'g', 'b', 'd', 'b', 'g', 'd', 'c', 'e', 'g', 'e', 'c',
'd', 'e', 'f', 'd']
The problem with the way that you are using letterlist.index(letter)-1 is that list.index(arg) returns the the index of the first occurrence of arg in list, in this case the letter. This means that if you have list = ["a", "b", "a"] and you run list.index("a") it will always return 0.
A way to do what you intend to (removing consecutive repetitions of letters) would be:
nodupeletterlist.append(letterlist[0])
for idx in range(1, len(letterlist)):
if letterlist[idx] != letterlist[idx-1]:
nodupeletterlist.append(letterlist[idx])
Do This:
L1 = ['a','a','a','b','b','c','d']
L2 = []
L2.append(L1[0])
for i in range(1,len(L1)):
if L1[i] != L1[i-1]:
L2.append(L1[i])
set() will create a set with only unique values,then the list() will convert it back to a a list containing values without any repetition.
I hope this helps...

Python lists similitudes

I am looking to get the number of similar characters between two lists.
The first list is:
list1=['e', 'n', 'z', 'o', 'a']
The second list is going to be a word user inputted turned into a list:
word=input("Enter word")
word=list(word)
I'll run this function below to get the number of similitudes in the two lists:
def getSimilarItems(word,list1):
counter = 0
for i in list2:
for j in list1:
if i in j:
counter = counter + 1
return counter
What I don't know how to do is how to get the number of similitudes for each item of the list(which is going to be either 0 or 1 as the word is going to be split into a list where an item is a character).
Help would be VERY appreciated :)
For example:
If the word inputted by the user is afez:
I'd like the run the function:
wordcount= getSimilarItems(word,list1)
And get this as an output:
>>>1 (because a from afez is in list ['e', 'n', 'z', 'o', 'a'])
>>>0 (because f from afez isn't in list ['e', 'n', 'z', 'o', 'a'])
>>>1 (because e from afez is in list ['e', 'n', 'z', 'o', 'a'])
>>>1 (because z from afez is in list ['e', 'n', 'z', 'o', 'a'])
Sounds like you simply want:
def getSimilarItems(word,list1):
return [int(letter in list1) for letter in word]
What I don't know how to do is how to get the number of similitudes
for each item of the list(which is going to be either 0 or 1 as the
word is going to be split into a list where an item is a character).
I assume that instead of counting the number of items in the list, you want to get the individual match result for each element.
For that you can use a dictionary or a list, and return that from your function.
Going off the assumption that the input is going to be the same length as the list,
def getSimilarItems(list1,list2):
counter = 0
list = []
for i in list2:
for j in list1:
if i in j:
list.append(1)
else:
list.append(0)
return list
Based off your edit,
def getSimilarItems(list1,list2):
counter = 0
for i in list2:
if i in list1:
print('1 (because )'+i +' from temp_word is in list'+ str(list1))
else:
print("0 (because )"+i +" from temp_word isn't in list" + str(list1))
Look at Julien's answer if you want a more condensed version (I'm not very good with list comprehension)

What is the best way to generate all possible three letter strings?

I am generating all possible three letters keywords e.g. aaa, aab, aac.... zzy, zzz below is my code:
alphabets = ['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']
keywords = []
for alpha1 in alphabets:
for alpha2 in alphabets:
for alpha3 in alphabets:
keywords.append(alpha1+alpha2+alpha3)
Can this functionality be achieved in a more sleek and efficient way?
keywords = itertools.product(alphabets, repeat = 3)
See the documentation for itertools.product. If you need a list of strings, just use
keywords = [''.join(i) for i in itertools.product(alphabets, repeat = 3)]
alphabets also doesn't need to be a list, it can just be a string, for example:
from itertools import product
from string import ascii_lowercase
keywords = [''.join(i) for i in product(ascii_lowercase, repeat = 3)]
will work if you just want the lowercase ascii letters.
You could also use map instead of the list comprehension (this is one of the cases where map is still faster than the LC)
>>> from itertools import product
>>> from string import ascii_lowercase
>>> keywords = map(''.join, product(ascii_lowercase, repeat=3))
This variation of the list comprehension is also faster than using ''.join
>>> keywords = [a+b+c for a,b,c in product(ascii_lowercase, repeat=3)]
from itertools import combinations_with_replacement
alphabets = ['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']
for (a,b,c) in combinations_with_replacement(alphabets, 3):
print a+b+c
You can also do this without any external modules by doing simple calculation.
The PermutationIterator is what you are searching for.
def permutation_atindex(_int, _set, length):
"""
Return the permutation at index '_int' for itemgetter '_set'
with length 'length'.
"""
items = []
strLength = len(_set)
index = _int % strLength
items.append(_set[index])
for n in xrange(1,length, 1):
_int //= strLength
index = _int % strLength
items.append(_set[index])
return items
class PermutationIterator:
"""
A class that can iterate over possible permuations
of the given 'iterable' and 'length' argument.
"""
def __init__(self, iterable, length):
self.length = length
self.current = 0
self.max = len(iterable) ** length
self.iterable = iterable
def __iter__(self):
return self
def __next__(self):
if self.current >= self.max:
raise StopIteration
try:
return permutation_atindex(self.current, self.iterable, self.length)
finally:
self.current += 1
Give it an iterable object and an integer as the output-length.
from string import ascii_lowercase
for e in PermutationIterator(ascii_lowercase, 3):
print "".join(e)
This will start from 'aaa' and end with 'zzz'.
chars = range(ord('a'), ord('z')+1);
print [chr(a) + chr(b) +chr(c) for a in chars for b in chars for c in chars]
We could solve this without the itertools by utilizing two function definitions:
def combos(alphas, k):
l = len(alphas)
kRecur(alphas, "", l, k)
def KRecur(alphas, prfx, l, k):
if k==0:
print(prfx)
else:
for i in range(l):
newPrfx = prfx + alphas[i]
KRecur(alphas, newPrfx, l, k-1)
It's done using two functions to avoid resetting the length of the alphas, and the second function self-iterates itself until it reaches a k of 0 to return the k-mer for that i loop.
Adopted from a solution by Abhinav Ramana on Geeks4Geeks
Well, i came up with that solution while thinking about how to cover that topic:
import random
s = "aei"
b = []
lenght=len(s)
for _ in range(10):
for _ in range(length):
password = ("".join(random.sample(s,length)))
if password not in b:
b.append("".join(password))
print(b)
print(len(b))
Please let me describe what is going on inside:
Importing Random,
creating a string with letters that we want to use
creating an empty list that we will use to put our combinations in
and now we are using range (I put 10 but for 3 digits it can be less)
next using random.sample with a list and list length we are creating letter combinations and joining it.
in next steps we are checking if in our b list we have that combination - if so, it is not added to the b list. If current combination is not on the list, we are adding it to it. (we are comparing final joined combination).
the last step is to print list b with all combinations and print number of possible combinations.
Maybe it is not clear and most efficient code but i think it works...
print([a+b+c for a in alphabets for b in alphabets for c in alphabets if a !=b and b!=c and c!= a])
This removes the repetition of characters in one string

How to randomly partition a list into n nearly equal parts?

I have read the answers to the Slicing a list into n nearly-equal-length partitions [duplicate] question.
This is the accepted answer:
def partition(lst, n):
division = len(lst) / float(n)
return [ lst[int(round(division * i)): int(round(division * (i + 1)))] for i in xrange(n) ]
I am wondering, how does one modify these solutions in order to randomly assign items to a partition as opposed to incremental assignment.
Call random.shuffle() on the list before partitioning it.
Complete 2018 solution (python 3.6):
import random
def partition (list_in, n):
random.shuffle(list_in)
return [list_in[i::n] for i in range(n)]
Beware! this may mutate your original list
shuffle input list.
First you randomize the list and then you split it in n nearly equal parts.
Shuffling the list doesn't preserve order. You could do something like this instead (pretty easy to adapt to more than two parts). Completely untested.
from __future__ import annotations
from typing import TypeVar
import random
T = TypeVar("T")
def partition_list(s: list[T]) -> tuple[list[T], list[T]]:
"""
Randomly partition a list into two lists, preserving order. The number to
take is drawn from a uniform distribution.
"""
len_a = random.randint(0, len(s))
len_b = len(s) - len_a
put_in_a = [True] * len_a + [False] * len_b
random.shuffle(put_in_a)
a: list[T] = []
b: list[T] = []
for val, in_a in zip(s, put_in_a):
if in_a:
a.append(val)
else:
b.append(val)
return a, b
The random partition that also preserves the order:
def partition_preserve_order(list_in, n):
indices = list(range(len(list_in)))
shuffle(indices)
index_partitions = [sorted(indices[i::n]) for i in range(n)]
return [[list_in[i] for i in index_partition]
for index_partition in index_partitions]
(that is we shuffle the indices then sort them within the partitions)
example:
random_partition_preserve_order(list('abcdefghijklmnopqrstuvxyz'), 3)
# [
# ['c', 'd', 'g', 'm', 'p', 'r', 'v', 'x', 'y'],
# ['b', 'e', 'h', 'k', 'o', 'q', 't', 'u'],
# ['a', 'f', 'i', 'j', 'l', 'n', 's', 'z']
# ]

Categories