How to limit number of permutations - python-3.x

I have about 20 short strings that I want to permutate. I want only permutations that have len == 8.
I would like to avoid calculating every possible permutation, as seen below:
import itertools
p = itertools.permutations([s1, s2, s3, s4, s5, s6,...])
for i in p:
s = ''.join(j for j in i)
if len(s)==8:
print(s)
But that's too slow right? How can I decrease the number of calculations? (to not spend processing and RAM).

The first, obvious thing to do is filter out any strings with length > 8:
newList = [i for i in [s1, s2, s3, s4, s5, s6, ...] if len(i) <= 8]
Then, you can use the second argument of itertools.permutations to set the number of items you want. If you have no empty strings in your list, you'll never need more than 8 items, so we can use 8 as the second argument:
p = itertools.permutations(newList, 8)
However, if any of your strings are longer than one character, this won't get you what you want, since it will only return permutations of exactly 8 items. One way to resolve this is to iterate through the various lengths:
pList = [itertools.permutations(newList, length) for length in range(1, 9)]
Yet here you end up with an enormous amount of permutations to filter through: P(20, 8) + P(20, 7) + ... P(20, 1) = roughly 5.5 billion, which is impractical to work with.
A different direction
Instead of using permutations, let's use combinations, of which there are far fewer ("only" 263,949). Recall that in combinations, the order of the combined items doesn't matter, while in permutations it does. Thus we can use the smaller set of combinations to filter for the length 8 that we want:
cList = (combo for length in range(1, 9)
for combo in itertools.combinations(newList, length)
if len(''.join(combo)) == 8)
Using () instead of [] will make this a generator rather than a list, to delay evaluation until we really need it. And now we are close!
We can get our final result by taking the permutations of the items in cList:
result = [''.join(perm) for combo in cList
for perm in itertools.permutations(combo)]

Related

Summation two by two of the elements of an array

I have a an array of 40 000 elements and I would like to add the elements two by two so that I can reduce the elements to 20 000. How can I do that. Thanks
The easiest way is probably iterate over a range of "every other index" - in this case, that would be for i in range(0, len(my_list), 2), which would produce [0, 2, 4, ..., 39996, 39998]. From there, we just add the contents at each index to the contents of the index following it.
import random
my_list = random.choices(range(100), k=40000)
print(len(my_list))
# 40000
new_list = [my_list[i] + my_list[i + 1] for i in range(0, len(my_list), 2)]
print(len(new_list))
# 20000
Another way that's a bit less efficient but doesn't have a risk of IndexErroring if the list has an odd number of elements, is to zip() two copies of the collection, using the same pattern in our slices to select only the even [::2] or odd [1::2] elements:
new_list = [even + odd for (even, odd) in zip(my_list[::2], my_list[1::2])]

Looking for a specific combination algorithm to solve a problem

Let’s say I have a purchase total and I have a csv file full of purchases where some of them make up that total and some don’t. Is there a way to search the csv to find the combination or combinations of purchases that make up that total ? Let’s say the purchase total is 155$ and my csv file has the purchases [5.00$,40.00$,7.25$,$100.00,$10.00]. Is there an algorithm that will tell me the combinations of the purchases that make of the total ?
Edit: I am still having trouble with the solution you provided. When I feed this spreadsheet with pandas into the code snippet you provided it only shows one solution equal to 110.04$ when there are three. It is like it is stopping early without finding the final solutions.This is the output that I have from the terminal - [57.25, 15.87, 13.67, 23.25]. The output should be [10.24,37.49,58.21,4.1] and [64.8,45.24] and [57.25,15.87,13.67,23.25]
from collections import namedtuple
import pandas
df = pandas.read_csv('purchases.csv',parse_dates=["Date"])
from collections import namedtuple
values = df["Purchase"].to_list()
S = 110.04
Candidate = namedtuple('Candidate', ['sum', 'lastIndex', 'path'])
tuples = [Candidate(0, -1, [])]
while len(tuples):
next = []
for (sum, i, path) in tuples:
# you may range from i + 1 if you don't want repetitions of the same purchase
for j in range(i+1, len(values)):
v = values[j]
# you may check for strict equality if no purchase is free (0$)
if v + sum <= S:
next.append(Candidate(sum = v + sum, lastIndex = j, path = path + [v]))
if v + sum == S :
print(path + [v])
tuples = next
A dp solution:
Let S be your goal sum
Build all 1-combinations. Keep those which sums less or equal than S. Whenever one equals S, output it
Build all 2-combinations reusing the previous ones.
Repeat
from collections import namedtuple
values = [57.25,15.87,13.67,23.25,64.8,45.24,10.24,37.49,58.21,4.1]
S = 110.04
Candidate = namedtuple('Candidate', ['sum', 'lastIndex', 'path'])
tuples = [Candidate(0, -1, [])]
while len(tuples):
next = []
for (sum, i, path) in tuples:
# you may range from i + 1 if you don't want repetitions of the same purchase
for j in range(i + 1, len(values)):
v = values[j]
# you may check for strict equality if no purchase is free (0$)
if v + sum <= S:
next.append(Candidate(sum = v + sum, lastIndex = j, path = path + [v]))
if abs(v + sum - S) <= 1e-2 :
print(path + [v])
tuples = next
More detail about the tuple structure:
What we want to do is to augment a tuple with a new value.
Assume we start with some tuple with only one value, say the tuple associated to 40.
its sum is trivially 40
the last index added is 1 (it is the number 40 itself)
the used values is [40], since it is the sole value.
Now to generate the next tuples, we will iterate from the last index (1), to the end of the array.
So candidates are 7.25, 100.00, 10.00
The new tuple associated to 7.25 is:
sum: 40 + 7.25
last index: 2 (7.25 has index 2 in array)
used values: values of tuple union 7.25, so [40, 7.25]
The purpose of using the last index, is to avoid considering [7.25, 40] and [40, 7.25]. Indeed they would be the same combination
So to generate tuples from an old one, only consider values occurring 'after' the old one from the array
At every step, we thus have tuples of the same size, each of them aggregates the values taken, the sum it amounts to, and the next values to consider to augment it to a bigger size
edit: to handle floats, you may replace (v+sum)<=S by abs(v+sum - S)<=1e-2 to say a solution is reach when you are very close (here distance arbitrarily set to 0.01) to solution
edit2: same code here as in https://repl.it/repls/DrearyWindingHypertalk (which does give
[64.8, 45.24]
[57.25, 15.87, 13.67, 23.25]
[10.24, 37.49, 58.21, 4.1]

Algorithm for generating all string combinations

Say I have a list of strings, like so:
strings = ["abc", "def", "ghij"]
Note that the length of a string in the list can vary.
The way you generate a new string is to take one letter from each element of the list, in order. Examples: "adg" and "bfi", but not "dch" because the letters are not in the same order in which they appear in the list. So in this case where I know that there are only three elements in the list, I could fairly easily generate all possible combinations with a nested for loop structure, something like this:
for i in strings[0].length:
for ii in strings[1].length:
for iii in strings[2].length:
print(i+ii+iii)
The issue arises for me when I don't know how long the list of strings is going to be beforehand. If the list is n elements long, then my solution requires n for loops to succeed.
Can any one point me towards a relatively simple solution? I was thinking of a DFS based solution where I turn each letter into a node and creating a connection between all letters in adjacent strings, but this seems like too much effort.
In python, you would use itertools.product
eg.:
>>> for comb in itertools.product("abc", "def", "ghij"):
>>> print(''.join(comb))
adg
adh
adi
adj
aeg
aeh
...
Or, using an unpack:
>>> words = ["abc", "def", "ghij"]
>>> print('\n'.join(''.join(comb) for comb in itertools.product(*words)))
(same output)
The algorithm used by product is quite simple, as can be seen in its source code (Look particularly at function product_next). It basically enumerates all possible numbers in a mixed base system (where the multiplier for each digit position is the length of the corresponding word). A simple implementation which only works with strings and which does not implement the repeat keyword argument might be:
def product(words):
if words and all(len(w) for w in words):
indices = [0] * len(words)
while True:
# Change ''.join to tuple for a more accurate implementation
yield ''.join(w[indices[i]] for i, w in enumerate(words))
for i in range(len(indices), 0, -1):
if indices[i - 1] == len(words[i - 1]) - 1:
indices[i - 1] = 0
else:
indices[i - 1] += 1
break
else:
break
From your solution it seems that you need to have as many for loops as there are strings. For each character you generate in the final string, you need a for loop go through the list of possible characters. To do that you can make recursive solution. Every time you go one level deep in the recursion, you just run one for loop. You have as many level of recursion as there are strings.
Here is an example in python:
strings = ["abc", "def", "ghij"]
def rec(generated, k):
if k==len(strings):
print(generated)
return
for c in strings[k]:
rec(generated + c, k+1)
rec("", 0)
Here's how I would do it in Javascript (I assume that every string contains no duplicate characters):
function getPermutations(arr)
{
return getPermutationsHelper(arr, 0, "");
}
function getPermutationsHelper(arr, idx, prefix)
{
var foundInCurrent = [];
for(var i = 0; i < arr[idx].length; i++)
{
var str = prefix + arr[idx].charAt(i);
if(idx < arr.length - 1)
{
foundInCurrent = foundInCurrent.concat(getPermutationsHelper(arr, idx + 1, str));
}
else
{
foundInCurrent.push(str);
}
}
return foundInCurrent;
}
Basically, I'm using a recursive approach. My base case is when I have no more words left in my array, in which case I simply add prefix + c to my array for every c (character) in my last word.
Otherwise, I try each letter in the current word, and pass the prefix I've constructed on to the next word recursively.
For your example array, I got:
adg adh adi adj aeg aeh aei aej afg afh afi afj bdg bdh bdi
bdj beg beh bei bej bfg bfh bfi bfj cdg cdh cdi cdj ceg ceh
cei cej cfg cfh cfi cfj

make a function that take an integer and reduces it down to an odd number

I'm working on my final for a class I'm taking(Python 3) im stuck at this part.
he gave us a file with numbers inside of it. we opened it and add those numbers to a list.
"Create a function called makeOdd() that returns an integer value. This function should take in any integer and reduce it down to an odd number by dividing it in half until it becomes an odd number.
o For example 10 would be cut in half to 5.
o 9 is already odd, so it would stay 9.
o But 12 would be cut in half to 6, and then cut in half again to 3.
o While 16 would be cut to 8 which gets cut to 4 which gets cut to 2 which gets cut to 1.
 Apply this function to every number in the array. "
I have tried to search the internet but i have not clue where to even begin with this one. any help would be nice.
Here my whole final so far:
#imports needed to run this code.
from Final_Functions import *
#Defines empty list
myList = []
sumthing = 0
sortList = []
oddList = []
count = 0
#Starts the Final Project with my name,class, and quarter
intro()
print("***************************************************************",'\n')
#Opens the data file and reads it then places the intrager into a list we can use later.
with open('FinalData.Data', 'r') as f:
myList = [line.strip() for line in f]
print("File Read Complete",'\n')
#Finds the Sum and Adverage of this list from FinalData.Data
print("*******************sum and avg*********************************")
for oneLine in myList:
tempNum = int(oneLine)
sumthing = sumthing + tempNum
avg = sumthing /1111
print("The Sum of the List is:",sumthing)
print("The Adverage of the List is:",avg,'\n')
print("***************************************************************",'\n')
#finds and prints off the first Ten and the last ten numbers in the list
firstTen(myList)
lastTen(myList)
print("***************************************************************",'\n')
#Lest sort the list then find the first and last ten numbers in this list
sortList = myList
sortList.sort()
firstTen(sortList)
lastTen(sortList)
print("****************************************************************",'\n')
Language:Python 3
I don't want to give you the answer outright, so I'm going to talk you through the process and let you generate your own code.
You can't solve this problem in a single step. You need to divide repeatedly and check the value every time to see if it's odd.
Broadly speaking, when you need to repeat a process there are two ways to proceed; looping and recursion. (Ok, there are lots, but those are the most common)
When looping, you'd check if the current number x is odd. If not, halve it and check again. Once the loop has completed, x will be your result.
If using recursion, have a function that takes x. If it's odd, simply return x, otherwise call the function again, passing in x/2.
Either of those methods will solve your problem and both are fundamental concepts.
adding to what #Basic said, never do import * is a bad practice and is a potential source of problem later on...
looks like you are still confuse in this simple matter, you want to given a number X reduce it to a odd number by dividing it by 2, right? then ask yourself how I do this by hand? the answer is what #Basic said you first ask "X is a even number?" if the answer is No then I and done reducing this number, but if the answer is Yes then the next step dividing it by 2 and save the result in X, then repeat this process until you get to the desire result. Hint: use a while
to answer your question about
for num in myList:
if num != 0:
num = float(num)
num / 2
the problem here is that you don't save the result of the division, to do that is as simple as this
for num in myList:
if num != 0:
num = float(num)
num = num / 2

Check string in list for multiple occurrence

I'm trying to find out if the following poker hand is a flush:
In the first case I analyse 5 cards. A flush is true if there all the 5 suits are identical (C H D S), i.e. there is only one suit present. That works fine.
a=['AC', '3H', 'TD', '9C', 'KD']
flush = len({suit for _, suit in a}) ==1 #false
In reality however there are usually 7 cards. 2 are held by the player and 5 are on the table. Here it gets a bit more complicated. How can I check if any suit occurs exactly 5 times?
b=['AC', '3H', 'TD', '9C', 'KD', '7H', '5S']
flush = ?
Speed is very important as this is part of an inner loop of a montecarlo simulation, so this should probably be a one-liner if possible.
You could use count and max to find... well, the max count of any of the suits and see whether it's at least 5.
>>> b = ['AC', '3H', 'TD', '9C', 'KD', '7H', '5S']
>>> suits = [s for _, s in b]
>>> max(suits.count(s) for s in suits) >= 5
False
But this will loop the list of suits for each element of that list, giving it O(n^2) complexity. Probably not too bad, considering that n is just 7, but still. Or use collections.Counter. This should be much faster (O(n)), as it uses a dictionary to keep track of the counts.
>>> max(collections.Counter((s for _, s in b)).values())
2
>>> collections.Counter((s for _, s in b)).most_common(1)
[('H', 2)]
list = ['AC', '3H', 'TD', '9C', 'KD']
substring='C'
print( len([s for s in list if substring in s]))
If speed is the key, well, there are only 7 cards, 4 suits. That makes 16384 combinations. I would convert each combination to a number and make a lookup in a precomputed table using this number as an index.
CONV = dict(C=0, H=1, D=2, S=3)
def flush_table_index(cards):
n = 0
for _, s in cards:
n = n * 4 + CONV[s]
return n
Edit: if-elif-elif instead of CONV would be quicker. Not a pretty or interesting code though.

Resources