How simplify list processing in Python? - python

Here's my first Python program, a little utility that converts from a Unix octal code for file permissions to the symbolic form:
s=raw_input("Octal? ");
digits=[int(s[0]),int(s[1]),int(s[2])];
lookup=['','x','w','wx','r','rx','rw','rwx'];
uout='u='+lookup[digits[0]];
gout='g='+lookup[digits[1]];
oout='o='+lookup[digits[2]];
print(uout+','+gout+','+oout);
Are there ways to shorten this code that take advantage of some kind of "list processing"? For example, to apply the int function all at once to all three characters of s without having to do explicit indexing. And to index into lookup using the whole list digits at once?

digits=[int(s[0]),int(s[1]),int(s[2])];
can be written as:
digits = map(int,s)
or:
digits = [ int(x) for x in s ] #list comprehension
As it looks like you might be using python3.x (or planning on using it in the future based on your function-like print usage), you may want to opt for the list-comprehension unless you want to dig in further and use zip as demonstrated by one of the later answers.

Here is a slightly optimized version of your code:
s = raw_input("Octal? ")
digits = map(int, s)
lookup = ['','x','w','wx','r','rx','rw','rwx']
perms = [lookup[d] for d in digits]
rights = ['{}={}'.format(*x) for x in zip('ugo', perms)]
print ','.join(rights)

You can also do it with bitmasks:
masks = {
0b100: 'r', # 4
0b010: 'x', # 2
0b001: 'w' # 1
}
octal = raw_input('Octal? ')
result = '-'
for digit in octal[1:]:
for mask, letter in sorted(masks.items(), reverse=True):
if int(digit, 8) & mask:
result += letter
else:
result += '-'
print result

Here's my version, inspired by Blender's solution:
bits = zip([4, 2, 1], "rwx")
groups = "ugo"
s = raw_input("Octal? ");
digits = map(int, s)
parts = []
for group, digit in zip(groups, digits):
letters = [letter for bit, letter in bits if digit & bit]
parts.append("{0}={1}".format(group, "".join(letters)))
print ",".join(parts)
I think it's better not to have to explicitly enter the lookup list.

Here's my crack at it (including '-' for missing permissions):
lookup = {
0b000 : '---',
0b001 : '--x',
0b010 : '-w-',
0b011 : '-wx',
0b100 : 'r--',
0b101 : 'r-x',
0b110 : 'rw-',
0b111 : 'rwx'
}
s = raw_input('octal?: ')
print(','.join( # using ',' as the delimiter
r + '=' + lookup[int(n, 8)] # the letter followed by the permissions
for n, r in zip(tuple(s), 'ugo'))) # for each number/ letter pair

Related

How to get an array of all possible binary numbers given a binary number of the same length

goal: I have a string which usually looks like this "010" and I need to replace the zeros by 1 in all the possible ways like this ["010", "110", "111", "011"]
problem when I replace the zeros with 1s I iterate through the letters of the string from left to right then from right to left. As you can see in the code where I did number = number[::-1]. Now, this method does not actually cover all the possibilities.
I also need to maybe start from the middle or maybe use the permutation method But not sure how to apply in python.
mathematically there is something like factorial of the number of places/(2)!
A = '0111011110000'
B = '010101'
C = '10000010000001101'
my_list = [A,B,C]
for number in [A,B,C]:
number = number[::-1]
for i , n in enumerate(number):
number = list(number)
number[i] = '1'
number = ''.join(number)
if number not in my_list: my_list.append(number)
for number in [A,B,C]:
for i , n in enumerate(number):
number = list(number)
number[i] = '1'
number = ''.join(number)
if number not in my_list: my_list.append(number)
print(len(my_list))
print(my_list)
You can use separate out the zeros and then use itertools.product -
from itertools import product
x = '0011'
perm_elements = [('0', '1') if digit == '0' else ('1', ) for digit in x]
print([''.join(x) for x in product(*perm_elements)])
['0011', '0111', '1011', '1111']
If you only need the number of such combinations, and not the list itself - that should just be 2 ** x.count('0')
Well, you will definitely get other answers with a traditional implementations of combinations with fixed indexes, but as we're working with just "0" and "1", you can use next hack:
source = "010100100001100011"
pattern = source.replace("0", "{}")
count = source.count("0")
combinations = [pattern.format(*f"{i:0{count}b}") for i in range(1 << count)]
Basically, we count amount of zeros in source, then iteration over range where limit is number with this amount of set bits and unpack every number in binary form into a pattern.
It should be slightly faster if we predefine pattern for binary transformation too:
source = "010100100001100011"
pattern = source.replace("0", "{}")
count = source.count("0")
fmt = f"{{:0{count}b}}"
result = [pattern.format(*fmt.format(i)) for i in range(1 << count)]
Upd. It's not clear do you need to generate all possible combinations or just get number, so originally I provided code to generate them, but if you will look closely in my method I'm getting number of all possible combinations using 1 << count, where count is amount of '0' chars in source string. So if you need just number, code is next:
source = "010100100001100011"
number_of_combinations = 1 << source.count("0")
Alternatively, you can also use 2 ** source.count("0"), but generally power is much more slower than binary shift, so I'd recommend to use option I originally advised.
We also can use recursive solution for this problem, we iterate over string and if saw a "0" change it to "1" and begin another branch on this new string:
s = "010100100001100011"
def perm(s, i=0, result=[]):
if i < len(s):
if s[i] == "0":
t = s[:i]+"1"+s[i+1:]
result.append(t)
perm(t, i+1, result)
perm(s, i+1, result)
res = [s]
perm(s, 0, res)
print(res)
For each position in the string that has a zero, you can either replace it with a 1 or not. This creates the combinations. So you can progressively build the resulting list of strings by adding the replacements of each '0' position with a '1' based on the previous replacement results:
def zeroTo1(S):
result = [S] # start with no replacement
for i,b in enumerate(S):
if b != '0': continue # only for '0' positions
result += [r[:i]+'1'+r[i+1:] for r in result] # add replacements
return result
print(zeroTo1('010'))
['010', '110', '011', '111']
If you're allowed to use libraries, the product function from itertools can be used to combine the zero replacements directly for you:
from itertools import product
def zeroTo1(S):
return [*map("".join,product(*("01"[int(b):] for b in S)))]
The tuples of 1s and 0s generated by the product function are assembled into individual strings by mapping the string join function onto its output.
Based on your objective you can do this to obtain the expected results.
A = '0111011110000'
B = '010'
C = '10000010000001101'
my_list = [A, B, C]
new_list = []
for key, number in enumerate(my_list):
for key_item, num in enumerate(number):
item_list = [i for i in number]
item_list[key_item] = "1"
new_list.append(''.join(item_list))
print(len(new_list))
print(new_list)

Find all possible combinations with replacement in a string

Here's the code I have so far:
from itertools import combinations, product
string = "abcd012345"
char = "01268abc"
for i, j in combinations(tuple(range(len(string))), 2):
for char1, char2 in product(char, char):
print(string[:i] + char1 + string[i+1:j] + char2 + string[j+1:])
So, the string is abcd012345, and I change two characters in order to find all possible combinations. The characters are 01268abc. In this example, we got 2880 combinations.
The goal is to set what character(s) will be in a specified place of the string. Example below:
from itertools import combinations, product
string = "abcd012345"
# place 0123456789
char_to change_for_place0 = "ab02"
char_to change_for_place1 = "14ah"
char_to change_for_place2 = "94nf"
char_to change_for_place3 = "a"
char_to change_for_place4 = "9347592"
char_to change_for_place5 = "93478nvg"
char_to change_for_place6 = "b"
char_to change_for_place7 = ""
char_to change_for_place8 = ""
char_to change_for_place9 = "84n"
for i, j in combinations(tuple(range(len(string))), 2):
for char1, char2 in product(char, char):
print(string[:i] + char1 + string[i+1:j] + char2 + string[j+1:])
Note:
some places can be empty and stays the same as in the place 7 and 8.
the number of places will be 64.
the number of characters to change will be 4, not 2 as in the example.
I will enjoy to learn from your solutions and ideas, thank you.
This boils down to adding the current letter of each position inside string to your current replacements for that position and then creating all possible combinations of those options:
from itertools import combinations, product
string = "abcd012345"
# must be of same lenght as (string), each entry correspond to the same index in string
p = ["ab02", "14ah", "94nf", "a", "9347592", "93478nvg", "b", "", "", "84n"]
errmsg = f"Keep them equal lenghts: '{string}' ({len(string)}) vs {p} ({len(p)})"
assert len(p)==len(string), errmsg
# eliminates duplicates from letter in string + replacments due to frozenset()
d = {idx: frozenset(v + string[idx]) for idx, v in enumerate(p)}
# creating this list take memory
all_of_em = [''.join(whatever) for whatever in product(*d.values())]
# if you hit a MemoryError creating the list, write to a file instead
# this uses a generator with limits memory usage but the file is going
# to get BIG
# with open("words.txt","w") as f:
# for w in (''.join(whatever) for whatever in product(*d.values())):
# f.write(w+"\n")
print(*all_of_em, f"\n{len(all_of_em)}", sep="\t")
Output:
2and2g234n 2and2g2348 2and2g2344 2and2g2345 2and27b34n
[...snipp...]
249d99234n 249d992348 249d992344 249d992345
100800
If you value the order of letters in your replacements, use
d = {idx: (v if string[idx] in v else string[idx]+v) for idx, v in enumerate(p)}
instead:
abcd012345 abcd012348 [...] 2hfa2gb344 2hfa2gb34n 115200
The difference in amounts is due to duplicate 9 in "9347592" wich is removed using frozensets.
To get only those that changed fewer then 5 things:
# use a generator comprehension to reduce memory usage
all_of_em = (''.join(whatever) for whatever in product(*d.values()))
# create the list with less then 5 changes from the generator above
fewer = [w for w in all_of_em if sum(a != b for a, b in zip(w, string)) < 5]

find substring between "a" and "(" + 2 unknown characters +")"

I have some strings which are built this way:
string = "blabla y the_blabla_I_want (two_digits_number) blabla"
I would like to get the_blabla_I_want.
I know the re.search can help but my problem is about how to represent (two_digits_number).
To represent (two_digits_number), you may use "\([0-9]{2}\)".
Here is a regex tutorial in python.
To get the_blabla_I_want, you may try the following code:
import re
x = re.search("y (.*) \([0-9]{2}\)", str)
x[1]
Depends on how you define the two digits number, yo may want to change "\([0-9]{2}" to "\([1-9][0-9])" to avoid numbers have leading zero
string = "blabla y the_blabla_I_want (99) blabla"
if string.count("(") == 1 and string.count(")") == 1:
start_idx = string.find("(")
end_idx = string.find(")")
two_digits_number = string[start_idx+1:end_idx]
print(two_digits_number) # output: 99
two_digits_number = string[start_idx:end_idx+1]
print(two_digits_number) # output: (99)

How can I optimize this function which is related to reversal of string?

I have a string: "String"
The first thing you do is reverse it: "gnirtS"
Then you will take the string from the 1st position and reverse it again: "gStrin"
Then you will take the string from the 2nd position and reverse it again: "gSnirt"
Then you will take the string from the 3rd position and reverse it again: "gSntri"
Continue this pattern until you have done every single position, and then you will return the string you have created. For this particular string, you would return: "gSntir"
And I have to repeat this entire procedure for x times where the string and x can be very big . (million or billion)
My code is working fine for small strings but it's giving timeout error for very long strings.
def string_func(s,x):
def reversal(st):
n1=len(st)
for i in range(0,n1):
st=st[0:i]+st[i:n1][::-1]
return st
for i in range(0,x):
s=reversal(s)
return s
This linear implementation could point you in the right direction:
from collections import deque
from itertools import cycle
def special_reverse(s):
d, res = deque(s), []
ops = cycle((d.pop, d.popleft))
while d:
res.append(next(ops)())
return ''.join(res)
You can recognize the slice patterns in the following examples:
>>> special_reverse('123456')
'615243'
>>> special_reverse('1234567')
'7162534'
This works too:
my_string = "String"
my_string_len = len(my_string)
result = ""
for i in range(my_string_len):
my_string = my_string[::-1]
result += my_string[0]
my_string = my_string[1:]
print(result)
And this, though it looks spaghetti :D
s = "String"
lenn = len(s)
resultStringList = []
first_half = list(s[0:int(len(s) / 2)])
second_half = None
middle = None
if lenn % 2 == 0:
second_half = list(s[int(len(s) / 2) : len(s)][::-1])
else:
second_half = list(s[int(len(s) / 2) + 1 : len(s)][::-1])
middle = s[int(len(s) / 2)]
lenn -= 1
for k in range(int(lenn / 2)):
print(k)
resultStringList.append(second_half.pop(0))
resultStringList.append(first_half.pop(0))
if middle != None:
resultStringList.append(middle)
print(''.join(resultStringList))
From the pattern of the original string and the result I constructed this algorithm. It has minimal number of operations.
str = 'Strings'
lens = len(str)
lensh = int(lens/2)
nstr = ''
for i in range(lensh):
nstr = nstr + str[lens - i - 1] + str[i]
if ((lens % 2) == 1):
nstr = nstr + str[lensh]
print(nstr)
or a short version using iterator magic:
def string_func(s):
ops = (iter(reversed(s)), iter(s))
return ''.join(next(ops[i % 2]) for i in range(len(s)))
which does the right thing for me, while if you're happy using some library code, you can golf it down to:
from itertools import cycle, islice
def string_func(s):
ops = (iter(reversed(s)), iter(s))
return ''.join(map(next, islice(cycle(ops), len(s))))
my original version takes 80microseconds for a 512 character string, this updated version takes 32µs, while your version took 290µs and schwobaseggl's solution is about 75µs.
I've had a play in Cython and I can get runtime down to ~0.5µs. Measuring this under perf_event_open I can see my CPU is retiring ~8 instructions per character, which seems pretty good, while a hard-coded loop in C gets this down to ~4.5 instructions per ASCII char. These don't seem to be very "Pythonic" solutions so I'll leave them out of this answer. But included this paragraph to show that the OP has options to make things faster, and that running this a billion times on a string consisting of ~500 characters will still take hundreds of seconds even with relatively careful C code.

Replacing a character in a string with a set of two possible characters

a = ["0$%","0%%%","0$%$%","0$$"]
The above is a corrupted communication code where the first element of each sequence has been disguised as 0. I want to recover the original and correct code by computing a list of all possible sequences by replacing 0 with either $ or % and then checking which of the sequences is valid. Think of each sequence as corresponding to an alphabet if correct. For instance, "$$$" could correspond to the alphabet "B".
This is what I've done so far
raw_decoded = []
word = []
for i in a:
for j in i:
if j == "0":
x = list(itertools.product(["$", "%"], *i[1:]))
y = ("".join(i) for i in x)
for i in y:
raw_decoded.append(i)
for i in raw_decoded:
letter = code_dict[i] #access dictionary for converting to alphabet
word.append(letter)
return word
Try that:
output = []
for elem in a:
replaced_dollar = elem.replace('0', '$', 1)
replaced_percent = elem.replace('0', '%', 1)
# check replaced_dollar and replaced_percent
# and then write to output
output.append(replaced_...)
Not sure what you mean, perhaps you could add a desired output. What I got from your question could be solved in the following way:
b = []
for el in a:
if el[0] == '0':
b.append(el.replace('0', '%', 1))
b.append(el.replace('0', '$', 1))
else:
b.append(el)

Categories