Alternative to Nested for loop in python - python

I am processing the Cartesian product of a list of entities.
For e.g. a_list = ['a','b']
The expected output is:
"a";"a"&"a";"b"&"b";"a"&"b";"b"
Each entity in the entity pair is separated by semicolon ";" and each enitity pair is separated by "&".
I used following nested for loop to achieve the output.
entity_set = ['a','b']
domain_text = ''
count = 0
for entity1 in entity_set:
for entity2 in entity_set:
count += 1
domain_text += '"' + entity1 + '"' + ';' + '"' + entity2 + '"'
if count < (len(entity_set)*len(entity_set)):
domain_text += '&'
print domain_text
However, the process gets too slow as the size of a_list increases to thousands of entities.
Is there any elegant solutions that can be used alternatively?

Sure. itertools.product() can do the product for you, and then a string join operation can paste all the pieces together efficiently (which is more likely than not the real source of the sloth: incrementally building the result string one little piece at a time).
from itertools import product
entity_set = ['a', 'b']
result = "&".join('"%s";"%s"' % pair
for pair in product(entity_set, repeat=2))
Then result prints as
"a";"a"&"a";"b"&"b";"a"&"b";"b"

Related

is there a built in method to make all strings the same length by inserting spaces in python without using a bunch of for loops and max/len?

Is there a way I can evenly space the text?
I want this:
var_to_change: ¦[3][10]¦
james: ¦[7][A]¦
To look like this:
var_to_change: ¦[3][10]¦
james: ¦[7][A]¦
Below is my current code for printing (part of a bigger function):
# hand is formatted [card1, card2]
disp_hand = f'{player}: ¦'
for card in hand:
disp_hand += '[' + str(card) + ']'
disp_hand += '¦ \n'
print(disp_hand)
I was thinking of the len(max()) function to get the length of the biggest string and then inserting spaces for all names using a for spaces in range (len(max()) - len(name)): loop and adding spaces until they match the max string length but this is quite complex and I was wondering whether there is a better way.
Below is a method I came up with:
users = {'john': {'hand': [1,2]},'var_to_change': {'hand': [1,2]}}
max_name_len= len(max(users.keys(), key=len))
name_dif = {}
for names in users:
name_dif[names] = max_name_len-len(names)
for names in users.copy():
whitespace = ''
for spaces in range(name_dif[names]):
whitespace += ' '
users[names + whitespace] = users.pop(names)
print(users) # returns: {'john ': {'hand': [1, 2]}, 'var_to_change': {'hand': [1, 2]}}
Are there any built-in methods or better ways?
Try using str.ljust
print("var_to_change:".ljust(15), "¦[3][10]¦")
print("james:".ljust(15), "¦[7][A]¦")
# Output:
var_to_change: ¦[3][10]¦
james: ¦[7][A]¦

Create words by combining all elements in a set of lists with each other

I have 6 different lists, which I need to combine and create sequences/words for all possible combinations.
These are my lists:
product = ['SOC', 'SOCCAMP', 'ETI', 'CARDASS', 'VRS', 'PRS', 'INT', 'GRS', 'VeloVe', 'HOME']
fam = ['IND', 'FAM']
area = ['EUR', 'WOR']
type = ['STD', 'PLU']
assist = ['MOT', 'NMOT']
All of the elements in all of the lists need to be combined in words.
The result will be a list of all the elements possible.
I will have among all of the elements f.e. the following words:
('SOC', 'SOCIND', 'SOCFAM', 'SOCFAMMOT', 'SOCMOTFAM'...)
I thus combine each element of a precise list with all the elements of the other lists.
Up to now I managed to combine them with a series of loops:
combos = []
##1##
for i in range(len(product)):
combos.append(str(product[i]))
##2##
for a in range(len(product)):
for b in range(len(fam)):
combos.append(str(product[a]) + str(fam[b]))
##3##
for a in range(len(product)):
for b in range(len(fam)):
for c in range(len(area)):
combos.append(str(product[a]) + str(fam[b]) + str(area[c]))
##4##
for a in range(len(product)):
for b in range(len(fam)):
for c in range(len(area)):
for d in range(len(type)):
combos.append(str(product[a]) + str(fam[b]) + str(area[c]) + str(type[d]))
##5##
for a in range(len(product)):
for b in range(len(fam)):
for c in range(len(area)):
for d in range(len(type)):
for e in range(len(assist)):
combos.append(str(product[a]) + str(fam[b]) + str(area[c]) + str(type[d]) + str(assist[e]))
This code manages to combine the words in a list of combinations but solely in the precise order the lists are mentioned:
['SOC', 'SOCCAMP', 'ETI', 'CARDASS', 'VRS', 'PRS', 'INT', 'GRS', 'VeloVe', 'HOME', 'SOCIND', 'SOCFAM', 'SOCCAMPIND', 'SOCCAMPFAM', 'ETIIND', 'ETIFAM', 'CARDASSIND', 'CARDASSFAM', 'VRSIND', 'VRSFAM', 'PRSIND', 'PRSFAM', 'INTIND', 'INTFAM', 'GRSIND', 'GRSFAM', 'VeloVeIND', 'VeloVeFAM', 'HOMEIND', 'HOMEFAM', ...]
So, 'SOCINDEUR' is a combination in this list but 'SOCEURIND' is not.
Is there a smart way to avoid writing down another 100 loops to look for all the possible combinations?
You can make use of various tools provided by itertools.
Overall, one solution is:
source_lists = product, fam, area, type, assist
combos = [
''.join(prod)
for l in range(1, len(source_lists))
for subset in itertools.permutations(source_lists, l)
for prod in itertools.product(*subset)
]
This code is effectively equivalent of:
combos = []
source_lists = product, fam, area, type, assist
for l in range(1, len(source_lists)):
for subset in itertools.permutations(source_lists, l):
for prod in itertools.product(*subset):
combos.append(''.join(prod))
The first loop selects how many different source lists you want to select, so it will first produce the results from single list, i.e. the "original" input. Then it moves to combining two etc.
The second loop select which source lists and in which order we will combine (and goes over all possible permutations).
Finally the third and last loop takes the selected source list and produces all the possible combinations (or, better said "product") of these lists.
Finally this produces the tuples of results, since you want one single string per result, we need to join them.
The magic of itertools!
from itertools import product, permutations
def concat_combinations(sequence):
new_seq = []
for i,j in enumerate(sequence):
if i == 0:
new_str = j
new_seq.append(new_str)
else:
new_str += j
new_seq.append(new_str)
return new_seq
def final_list(*iterables)->list:
s = set()
for seq in list(product(*iterables)):
[s.update(concat_combinations(i)) for i in permutations(seq)]
return sorted(s,key=lambda x: len(x))

specific characters printing with Python

given a string as shown below,
"[xyx],[abc].[cfd],[abc].[dgr],[abc]"
how to print it like shown below ?
1.[xyz]
2.[cfd]
3.[dgr]
The original string will always maintain the above-mentioned format.
I did not realize you had periods and commas... that adds a bit of trickery. You have to split on the periods too
I would use something like this...
list_to_parse = "[xyx],[abc].[cfd],[abc].[dgr],[abc]"
count = 0
for i in list_to_parse.split('.'):
for j in i.split(','):
string = str(count + 1) + "." + j
if string:
count += 1
print(string)
string = None
Another option is split on the left bracket, and then just re-add it with enumerate - then strip commas and periods - this method is also probably a tiny bit faster, as it's not a loop inside a loop
list_to_parse = "[xyx],[abc].[cfd],[abc].[dgr],[abc]"
for index, i in enumerate(list.split('[')):
if i:
print(str(index) + ".[" + i.rstrip(',.'))
also strip is really "what characters to remove" not a specific pattern. so you can add any characters you want removed from the right, and it will work through the list until it hits a character it can't remove. there is also lstrip() and strip()
string manipulation can always get tricky, so pay attention. as this will output a blank first object, so index zero isn't printed etc... always practice and learn your needs :D
You can use split() function:
a = "[xyx],[abc].[cfd],[abc].[dgr],[abc]"
desired_strings = [i.split(',')[0] for i in a.split('.')]
for i,string in enumerate(desired_strings):
print(f"{i+1}.{string}")
This is just a fun way to solve it:
lst = "[xyx],[abc].[cfd],[abc].[dgr],[abc]"
count = 1
var = 1
for char in range(0, len(lst), 6):
if var % 2:
print(f"{count}.{lst[char:char + 5]}")
count += 1
var += 1
output:
1.[xyx]
2.[cfd]
3.[dgr]
explanation : "[" appears in these indexes: 0, 6, 12, etc. var is for skipping the next pair. count is the counting variable.
Here we can squeeze the above code using list comprehension and slicing instead of those flag variables. It's now more Pythonic:
lst = "[xyx],[abc].[cfd],[abc].[dgr],[abc]"
lst = [lst[i:i+5] for i in range(0, len(lst), 6)][::2]
res = (f"{i}.{item}" for i, item in enumerate(lst, 1))
print("\n".join(res))
You can use RegEx:
import regex as re
pattern=r"(\[[a-zA-Z]*\])\,\[[a-zA-Z]*\]\.?"
results=re.findall(pattern, '[xyx],[abc].[cfd],[abc].[dgr],[abc]')
print(results)
Using re.findall:
import re
s = "[xyx],[abc].[cfd],[abc].[dgr],[abc]"
print('\n'.join(f'{i+1}.{x}' for i,x in
enumerate(re.findall(r'(\[[^]]+\])(?=,)', s))))
Output:
1.[xyx]
2.[cfd]
3.[dgr]

Better way for taking multiple inputs with a for loop?

I would like to use a for loop to get inputs for many questions I have to receive.
I managed to make some code, but it seems there should be
a better way.
Maybe I can reduce the number of variables I'm using?
## <Desired Result>
## onset : 3mo
## location : earth
## character : red
checks = ['onset','location','character']
l1 = ['onset','location','character']
l2 = ['onset','location','character']
for i in range(len(checks)):
l2[i] = input(checks[i])
for i in range(len(checks)):
print(l1[i]+" : "+l2[i])
A few observations on your code:
Note that you never actually change l1 so basically it is unnecessary and wherever you use l1 replace with checks.
It is not necessary to define l2 this way as you are overriding all its values anyway, so you could just define l2 = [] and then use append in your loop:
for i in range(len(checks)):
l2.append(input(checks[i]))
Both your loops have exactly the same range, so you could combine them to 1:
for i in range(len(checks)):
l2[i] = input(checks[i])
print(l1[i]+" : "+l2[i])
Now, using list-comprehension and the join method of strings, you could actually reduce this code to 3 lines (and get rid of l1):
checks = ['onset', 'location', 'character']
l2 = [input(x) for x in checks]
print("\n".join(checks[i]+" : "+l2[i] for i in range(len(checks))))
Or more neatly using zip:
print("\n".join(check + " : " + ans for check, ans in zip(checks, l2)))
Lastly, to reduce even one more line (and get rid of l2):
checks = ['onset', 'location', 'character']
print("\n".join(check + " : " + input(check) for check in checks))
We could also avoid using join and use the chance to further reduce to one line (getting rid of checks) using print's extra arguments and list-unpacking:
print(*(check + " : " + input(check) for check in ['onset', 'location', 'character']), sep='\n')
What you are trying to achieve for is done using List comprehensions.
In your case you can do that in a single line.
l2 = [input(x) for x in checks]
You should not initialize the list of desired length and take input for each element. You can use append method to that.
The following code will help you:
checks = ['onset','location','character']
arr = []
for i in checks:
arr.append(input(i + ' : '))
If you want to reduce the number of lines, you can try the following:
arr = [ input(i + ' : ') for i in ['onset','location','character']]
For a truly 1-line solution to your for-loops, you could do your list comprehension like this:
l2 = [(n, print(l1[i]+" : "+n))[0] for i, n in enumerate([input(x + ": ") for x in checks])]
Ouput:
onseta
locationb
characterc
onset : a
location : b
character : c
EDIT
As others mentioned, this is not best practice, so use something like this:
checks = ['onset','location','character']
l2 = [input(f"Check {n}:\n > ") for n in checks]
print(*(f"{j}: {l2[i]}\n" for i, j in enumerate(checks)), sep="")
Output:
Check onset:
> ok
Check location:
> ok
Check character:
> ok
onset: ok
location: ok
character: ok

ArcGIS:Python - Adding Commas to a String

In ArcGIS I have intersected a large number of zonal polygons with another set and recorded the original zone IDs and the data they are connected with. However the strings that are created are one long list of numbers ranging from 11 to 77 (each ID is 11 characters long). I am looking to add a "," between each one making, it easier to read and export later as a .csv file. To do this I wrote this code:
def StringSplit(StrO,X):
StrN = StrO #Recording original string
StrLen = len(StrN)
BStr = StrLen/X #How many segments are inside of one string
StrC = BStr - 1 #How many times it should loop
if StrC > 0:
while StrC > 1:
StrN = StrN[ :((X * StrC) + 1)] + "," + StrN[(X * StrC): ]
StrC = StrC - 1
while StrC == 1:
StrN = StrN[:X+1] + "," + StrN[(X*StrC):]
StrC = 0
while StrC == 0:
return StrN
else:
return StrN
The main issue is how it has to step through multiple rows (76) with various lengths (11 -> 77). I got the last parts to work, just not the internal loop as it returns an error or incorrect outputs for strings longer than 22 characters.
Thus right now:
1. 01234567890 returns 01234567890
2. 0123456789001234567890 returns 01234567890,01234567890
3. 012345678900123456789001234567890 returns either: Error or ,, or even ,,01234567890
I know it is probably something pretty simple I am missing, but I can't seem remember what it is...
It can be easily done by regex.
those ........... are 11 dots for give split for every 11th char.
you can use pandas to create csv from the array output
Code:
import re
x = re.findall('...........', '01234567890012345678900123456789001234567890')
print(x)
myString = ",".join(x)
print(myString)
output:
['01234567890', '01234567890', '01234567890', '01234567890']
01234567890,01234567890,01234567890,01234567890
for the sake of simplicity you can do this
code:
x = ",".join(re.findall('...........', '01234567890012345678900123456789001234567890'))
print(x)
Don't make the loops by yourself, use python libraries or builtins, it will be easier. For example :
def StringSplit(StrO,X):
substring_starts = range(0, len(StrO), X)
substrings = (StrO[start:start + X] for start in substring_starts)
return ','.join(substrings)
string = '1234567890ABCDE'
print(StringSplit(string, 5))
# '12345,67890,ABCDE'

Categories

Resources