Recursive function how to manage output - python-3.x

I'm working on a project for creating some word list. I have a word and some rules, for example, this char % is for digit, while this one ^ for special character, for example January%%^ should create things like:
January00!
January01!
January02!
January03!
January04!
January05!
January06!
etc.
For now I'm trying to do it with only digit and create a recursive function, because people can add as many digits and special characters as they want
January^%%%^% (for example)
This is the first function I have created:
month = "January"
nbDigit = "%%%"
def addNumber(month : list, position: int):
for i in range(position, len(month)):
for j in range(0,10):
month[position] = j
if(position == len(month)-1):
print (''.join(str(v) for v in month))
if position < len(month):
if month[position+1] == "%":
addNumber(month, position+1)
The problem is for each % that I have there is another output (three %, three times as output January000-January999/January000-January999/January000-January999).
When I tried to add the new function special character it's even worse, because I can't manage the output since every word can't end with a special character or digit. (AddSpecialChar is also a recursive function).

I believe what you are looking for is the following:
month = 'January'
nbDigit = "%%"
def addNumbers(root: str, mask: str)-> list:
# create a list of words using root followed By digits
rslt = []
mxNmb = 0
for i in range(len(mask)):
mxNmb += 9 * 10**i
mxNmb += 1
for i in range(mxNmb):
word = f"{root}{((str(i).rjust(len(mask), '0')))}"
rslt.append(word)
return rslt
this will produce:
['January00',
'January01',
'January02',
'January03',
'January04',
'January05',
'January06',
'January07',
'January08',
'January09',
'January10',
'January11',
'January12',
'January13',
'January14',
'January15',
'January16',
'January17',
'January18',
'January19',
'January20',
'January21',
'January22',
'January23',
'January24',
'January25',
'January26',
'January27',
'January28',
'January29',
'January30',
'January31',
'January32',
'January33',
'January34',
'January35',
'January36',
'January37',
'January38',
'January39',
'January40',
'January41',
'January42',
'January43',
'January44',
'January45',
'January46',
'January47',
'January48',
'January49',
'January50',
'January51',
'January52',
'January53',
'January54',
'January55',
'January56',
'January57',
'January58',
'January59',
'January60',
'January61',
'January62',
'January63',
'January64',
'January65',
'January66',
'January67',
'January68',
'January69',
'January70',
'January71',
'January72',
'January73',
'January74',
'January75',
'January76',
'January77',
'January78',
'January79',
'January80',
'January81',
'January82',
'January83',
'January84',
'January85',
'January86',
'January87',
'January88',
'January89',
'January90',
'January91',
'January92',
'January93',
'January94',
'January95',
'January96',
'January97',
'January98',
'January99']
Adding another position to the nbDigit variable will produce the numeric sequence from 000 to 999

Related

Replace string only if all characters match (Thai)

The problem is that มาก technically is in มาก็. Because มาก็ is มาก + ็.
So when I do
"แชมพูมาก็เยอะ".replace("มาก", " X ")
I end up with
แชมพู X ็เยอะ
And what I want
แชมพู X เยอะ
What I really want is to force the last character ก็ to count as a single character, so that มาก no longer matches มาก็.
While I haven't found a proper solution, I was able to find a solution. I split each string into separate (combined) characters via regex. Then I compare those lists to each other.
# Check is list is inside other list
def is_slice_in_list(s,l):
len_s = len(s) #so we don't recompute length of s on every iteration
return any(s == l[i:len_s+i] for i in range(len(l) - len_s+1))
def is_word_in_string(w, s):
a = regex.findall(u'\X', w)
b = regex.findall(u'\X', s)
return is_slice_in_list(a, b)
assert is_word_in_string("มาก็", "พูมาก็เยอะ") == True
assert is_word_in_string("มาก", "พูมาก็เยอะ") == False
The regex will split like this:
พู ม า ก็ เ ย อ ะ
ม า ก
And as it compares ก็ to ก the function figures the words are not the same.
I will mark as answered but if there is a nice or "proper" solution I will chose that one.

How to remove kth element in O(1) time complexity

Given a string I need to remove the smallest character and return the sum of indices of removed charecter.
Suppose the string is 'abcab' I need to remove first a at index 1.
We are left with 'bcab'. Now remove again a which is smallest in remaining string and is at index 3
We are left with 'bcb'.
In the same way remove b at index 1,then remove again b from 'cb' at index 2 and finally remove c
Total of all indices is 1+3+1+2+1=8
Question is simple but we need to do it in O(n). for that I need to remove kth element in O(1). In python del list[index] has time complexity O(n).
How can I delete in constant time using python
Edit
This is the exact question
You are given a string S of size N. Assume that count is equal to 0.
Your task is the remove all the N elements of string S by performing the following operation N times
• In a single operation, select an alphabetically smallest character in S, for example, Remove from S and add its index to count. If multiple characters such as c exist, then select that has the smallest index.
Print the value of count.
Note Consider 1-based indexing
Solve the problem for T test cases
Input format
The first line of the input contains an integer T denoting the number of test cases • The first line of each test case contains an integer N denoting the size of string S
• The second line of each test case contains a string S
Output format
For each test case print a single line containing one integer denoting the value of count
1<T, N < 10^5
• S contains only lowercase English alphabets
Sum of N over all test cases does not exceed 10
Sample input 1
5
abcab
Sample Output1
8
Explanation
The operations occur in the following order
Current string S= abcab', The alphabetically smallest character of s is 'a As there are 2 occurrences of a, we choose the first occurrence. Its Index 1 will be added to the count and a will be removed. Therefore, S becomes bcab
a will.be removed from 5 (bcab) and 3 will.be added to count
The first occurrence of b will be removed from (bcb) and 1 will be added to count.
b will be removed from s (cb) and 2 will be added to count
c will be removed from 5 (c) and 1 will be added to count
If you follow your procedure of repeatedly removing the first occurrence of the smallest character, then each character's index -- when you remove it -- is the number of preceding larger characters in the original string plus one.
So what you really need to do is find, for each character, the number of preceding larger characters, and then add up all those counts.
There are only 26 characters, so you can do this as you go with 26 counters.
Please link to the original problem statement, or copy/paste exactly what it says, without trying to explain it. As is, what you're asking for is impossible.
Forget deleting: if what you're asking for was possible, sorting would be worse-case O(n) (remove the minimum remaining n times, at O(1) cost for each), but it's well known that comparison-based sorting cannot do better than worst case O(n log n).
One bet: the original problem statement doesn't require that you delete anything - but instead that you return the result as if you had deleted.
With one pass over the input
Putting together various ideas, the final index of a character is one more than the number of larger characters seen before it. So it's possible to do this in one left-to-right pass over the input, using O(1) storage and O(n) time, while deleting nothing:
def crunch(s):
neq = [0] * 26
result = 0
orda = ord('a')
for ch in map(ord, s):
ch -= orda
result += sum(neq[i] for i in range(ch + 1, 26)) + 1
neq[ch] += 1
return result
For your original:
>>> crunch('abcab')
8
But it's also possible to process arbitary iterables one character at a time:
>>> from itertools import repeat, chain
>>> crunch(chain(repeat('y', 1000000), 'xz'))
2000002
x is originally at (1-based) index 1000001, which accounts for half the result. Then each of a million 'y's is conceptually deleted, each at index 1. Finally 'z' is at index 1, for a grand total of 2000002.
Looks like you're only interested in the resulting sum of indices and don't need to simulate this algorithm step by step.
In which case you could compute the result in the following way:
For each letter from a to z:
Have a counter of already removed letters set to 0
Iterate over the string and if you encounter the current letter add current_index - already_removed_counter to the result.
2a. If you encounter current or earlier (smaller) letter increase the counter as it already has been removed
The time complexity is 26 * O{n} which is O{n}.
Since there are only 26 distinct chatacters in the string, we can take each character separately and linearly traverse the string to find all its occurences. Keep a counter of how many chacters were found. Each time an occurence of a given character is found display its index decreased by the counter. Before switching to a new character, remove all the occurences of the previous one - this can be done in linear time.
res = 0
for c in 'a' .. 'z'
cnt = 0
for idx = 1 .. len(s)
if s[idx] = c
print idx - cnt
res += idx - cnt
cnt++
removeAll(s, c)
return res
where
removeAll(s,c):
i = 1
cnt = 0
n = len(s)
while (i < n)
if s[i + cnt] = c
cnt++
n--
else
s[i] = s[i + cnt]
i++
len(s) = n
It prints the elements of the sum to better illustrate what's going on.
Edit:
An updated version based on Igor's answer, that does not require actually removing elements. The complexity is the same i.e. O(n).
res = 0
for c in 'a' .. 'z'
cnt = 0
for idx = 1 .. len(s)
if s[idx] <= c
if s[idx] = c
print idx - cnt
res += idx - cnt
cnt++
return res

How to count the number of substrings in a string?

I want to find the number of occurrences of a particular sub-string in a string.
string="abcbcbcb"
sub_str="cbc"
c=string.count(sub_str)
print(c)
This gives the output as
1
which is the number of non-overlapping occurrences of substring in the string.
But I want to calculate the overlapping strings as well. Thus, the desired output is:
2
You can use a regular expression, use module "re"
print len(re.findall('(?=cbc)','abcbcbcb'))
No standard function available for overlapping count. You could write custom function tho.
def count_occ(string, substr):
cnt = 0
pos = 0
while(True):
pos = string.find(substr , pos)
if pos > -1:
cnt += 1
pos += 1
else:
break
return cnt
string="abcbcbcb"
sub_str="cbc"
print(count_occ(string,sub_str))

Return number of alphabetical substrings within input string

I'm trying to generate code to return the number of substrings within an input that are in sequential alphabetical order.
i.e. Input: 'abccbaabccba'
Output: 2
alphabet = 'abcdefghijklmnopqrstuvwxyz'
def cake(x):
for i in range(len(x)):
for j in range (len(x)+1):
s = x[i:j+1]
l = 0
if s in alphabet:
l += 1
return l
print (cake('abccbaabccba'))
So far my code will only return 1. Based on tests I've done on it, it seems it just returns a 1 if there are letters in the input. Does anyone see where I'm going wrong?
You are getting the output 1 every time because your code resets the count to l = 0 on every pass through the loop.
If you fix this, you will get the answer 96, because you are including a lot of redundant checks on empty strings ('' in alphabet returns True).
If you fix that, you will get 17, because your test string contains substrings of length 1 and 2, as well as 3+, that are also substrings of the alphabet. So, your code needs to take into account the minimum substring length you would like to consider—which I assume is 3:
alphabet = 'abcdefghijklmnopqrstuvwxyz'
def cake(x, minLength=3):
l = 0
for i in range(len(x)):
for j in range(i+minLength, len(x)): # carefully specify both the start and end values of the loop that determines where your substring will end
s = x[i:j]
if s in alphabet:
print(repr(s))
l += 1
return l
print (cake('abccbaabccba'))

Change Letters in A String One at a Time (Pandas,Python3)

I have a list of words in Pandas (DF)
Words
Shirt
Blouse
Sweater
What I'm trying to do is swap out certain letters in those words with letters from my dictionary one letter at a time.
so for example:
mydict = {"e":"q,w",
"a":"z"}
would create a new list that first replaces all the "e" in a list one at a time, and then iterates through again replacing all the "a" one at a time:
Words
Shirt
Blouse
Sweater
Blousq
Blousw
Swqater
Swwater
Sweatqr
Sweatwr
Swezter
I've been looking around at solutions here: Mass string replace in python?
and have tried the following code but it changes all instances "e" instead of doing so one at a time -- any help?:
mydict = {"e":"q,w"}
s = DF
for k, v in mydict.items():
for j in v:
s['Words'] = s["Words"].str.replace(k, j)
DF["Words"] = s
this doesn't seem to work either:
s = DF.replace({"Words": {"e": "q","w"}})
This answer is very similar to Brian's answer, but a little bit sanitized and the output has no duplicates:
words = ["Words", "Shirt", "Blouse", "Sweater"]
md = {"e": "q,w", "a": "z"}
md = {k: v.split(',') for k, v in md.items()}
newwords = []
for word in words:
newwords.append(word)
for c in md:
occ = word.count(c)
pos = 0
for _ in range(occ):
pos = word.find(c, pos)
for r in md[c]:
tmp = word[:pos] + r + word[pos+1:]
newwords.append(tmp)
pos += 1
Content of newwords:
['Words', 'Shirt', 'Blouse', 'Blousq', 'Blousw', 'Sweater', 'Swqater', 'Swwater', 'Sweatqr', 'Sweatwr', 'Swezter']
Prettyprint:
Words
Shirt
Blouse
Blousq
Blousw
Sweater
Swqater
Swwater
Sweatqr
Sweatwr
Swezter
Any errors are a result of the current time. ;)
Update (explanation)
tl;dr
The main idea is to find the occurences of the character in the word one after another. For each occurence we are then replacing it with the replacing-char (again one after another). The replaced word get's added to the output-list.
I will try to explain everything step by step:
words = ["Words", "Shirt", "Blouse", "Sweater"]
md = {"e": "q,w", "a": "z"}
Well. Your basic input. :)
md = {k: v.split(',') for k, v in md.items()}
A simpler way to deal with replacing-dictionary. md now looks like {"e": ["q", "w"], "a": ["z"]}. Now we don't have to handle "q,w" and "z" differently but the step for replacing is just the same and ignores the fact, that "a" only got one replace-char.
newwords = []
The new list to store the output in.
for word in words:
newwords.append(word)
We have to do those actions for each word (I assume, the reason is clear). We also append the world directly to our just created output-list (newwords).
for c in md:
c as short for character. So for each character we want to replace (all keys of md), we do the following stuff.
occ = word.count(c)
occ for occurrences (yeah. count would fit as well :P). word.count(c) returns the number of occurences of the character/string c in word. So "Sweater".count("o") => 0 and "Sweater".count("e") => 2.
We use this here to know, how often we have to take a look at word to get all those occurences of c.
pos = 0
Our startposition to look for c in word. Comes into use in the next loop.
for _ in range(occ):
For each occurence. As a continual number has no value for us here, we "discard" it by naming it _. At this point where c is in word. Yet.
pos = word.find(c, pos)
Oh. Look. We found c. :) word.find(c, pos) returns the index of the first occurence of c in word, starting at pos. At the beginning, this means from the start of the string => the first occurence of c. But with this call we already update pos. This plus the last line (pos += 1) moves our search-window for the next round to start just behind the previous occurence of c.
for r in md[c]:
Now you see, why we updated mc previously: we can easily iterate over it now (a md[c].split(',') on the old md would do the job as well). So we are doing the replacement now for each of the replacement-characters.
tmp = word[:pos] + r + word[pos+1:]
The actual replacement. We store it in tmp (for debug-reasons). word[:pos] gives us word up to the (current) occurence of c (exclusive c). r is the replacement. word[pos+1:] adds the remaining word (again without c).
newwords.append(tmp)
Our so created new word tmp now goes into our output-list (newwords).
pos += 1
The already mentioned adjustment of pos to "jump over c".
Additional question from OP: Is there an easy way to dictate how many letters in the string I want to replace [(meaning e.g. multiple at a time)]?
Surely. But I have currently only a vague idea on how to achieve this. I am going to look at it, when I got my sleep. ;)
words = ["Words", "Shirt", "Blouse", "Sweater", "multipleeee"]
md = {"e": "q,w", "a": "z"}
md = {k: v.split(',') for k, v in md.items()}
num = 2 # this is the number of replaces at a time.
newwords = []
for word in words:
newwords.append(word)
for char in md:
for r in md[char]:
pos = multiples = 0
current_word = word
while current_word.find(char, pos) != -1:
pos = current_word.find(char, pos)
current_word = current_word[:pos] + r + current_word[pos+1:]
pos += 1
multiples += 1
if multiples == num:
newwords.append(current_word)
multiples = 0
current_word = word
Content of newwords:
['Words', 'Shirt', 'Blouse', 'Sweater', 'Swqatqr', 'Swwatwr', 'multipleeee', 'multiplqqee', 'multipleeqq', 'multiplwwee', 'multipleeww']
Prettyprint:
Words
Shirt
Blouse
Sweater
Swqatqr
Swwatwr
multipleeee
multiplqqee
multipleeqq
multiplwwee
multipleeww
I added multipleeee to demonstrate, how the replacement works: For num = 2 it means the first two occurences are replaced, after them, the next two. So there is no intersection of the replaced parts. If you would want to have something like ['multiplqqee', 'multipleqqe', 'multipleeqq'], you would have to store the position of the "first" occurence of char. You can then restore pos to that position in the if multiples == num:-block.
If you got further questions, feel free to ask. :)
Because you need to replace letters one at a time, this doesn't sound like a good problem to solve with pandas, since pandas is about doing everything at once (vectorized operations). I would dump out your DataFrame into a plain old list and use list operations:
words = DF.to_dict()["Words"].values()
for find, replace in reversed(sorted(mydict.items())):
for word in words:
occurences = word.count(find)
if not occurences:
print word
continue
start_index = 0
for i in range(occurences):
for replace_char in replace.split(","):
modified_word = list(word)
index = modified_word.index(find, start_index)
modified_word[index] = replace_char
modified_word = "".join(modified_word)
print modified_word
start_index = index + 1
Which gives:
Words
Shirt
Blousq
Blousw
Swqater
Swwater
Sweatqr
Sweatwr
Words
Shirt
Blouse
Swezter
Instead of printing the words, you can append them to a list and re-create a DataFrame if that's what you want to end up with.
If you are looping, you need to update s at each cycle of the loop. You also need to loop over v.
mydict = {"e":"q,w"}
s=deduped
for k, v in mydict.items():
for j in v:
s = s.replace(k, j)
Then reassign it to your dataframe:
df["Words"] = s
If you can write this as a function that takes in a 1d array (list, numpy array etc...), you can use df.apply to apply it to any column, using df.apply().

Resources