Identifying the recursion conditions for Python String - python

I have this string 0123456789 and I want to use recursion to create a method that returns
'09182736455463728190'
So basically the above says that first I get the first num from the left and then the first from the right, and add them to the string, then I get the second from the left and the second from the right, etc.
When I reach the middle, I start adding to the final string the values of the initial string, but now int the opposite order. So 546372, etc. So Starting from the edges I add first the most left and then the most right element.
Starting from the middle and moving to the edges, I favour the right side element first.
I cannot come up with the recursion relationship

Here it is. The base case is when the final string length is twice the original string length. This signals the end of recursion and returns the final string. The recursive step consists of advancing the start index forward and the end index backward on the original string:
def transform_sequence(original, final = '', start_index = 0, end_index = -1):
#base case:
if len(final) == 2* len(original):
return final
#recursion
final += original[start_index] + original[end_index]
return transform_sequence(original, final, start_index + 1, end_index - 1)
print(transform_sequence('0123456789'))
#output: 09182736455463728190

If you want to use recursion to handle this, you'd better try to solve it from top-down like this
def rec(nums):
if len(nums) == 0:
return ''
elif len(nums) == 1:
return nums * 2
else:
first = nums[0]
last = nums[-1]
return ''.join([first, last, rec(nums[1:-1]), last, first])
if __name__ == '__main__':
nums = '0123456789'
print(rec(nums)) # 09182736455463728190
nums = '012'
print(rec(nums)) # 021120
nums = '0'
print(rec(nums)) # 00

Fun problem and great way to learn about recursion. We can use inductive reasoning to ensure our program is valid for all input strings -
if the input s is empty, return the empty result
(inductive) s is not empty. get the first and last characters, a and b, and prepend/append them to the result of the sub-problem, s[1:-1]
We can easily encode this in python -
def first_and_last(s):
return (s[0], s[-1])
def puzzle(s):
if not s:
return "" # 1
else:
(a,b) = first_and_last(s) # 2
return a + b + puzzle(s[1:-1]) + b + a
We can see it working below -
print(puzzle("0123456789"))
print(puzzle("12345"))
09182736455463728190
152433334251
When then puzzle input is an odd length, notice how the middle character is repeated four times. When we reach the sub-problem puzzle("3"), a=3 and b=3, and the result of a + b + puzzle("") + b + a is 3333.
If you wish to handle this situation differently, we could modify puzzle
def puzzle(s):
if len(s) < 2: # <- if input is empty or singleton string
return s # <- return s
else:
(a,b) = first_and_last(s)
return a + b + puzzle(s[1:-1]) + b + a
print(puzzle("0123456789"))
print(puzzle("12345"))
09182736455463728190
152434251 # <-
The nice answer from Menglong Li presents another option for dealing with puzzle inputs of an odd length. I encourage you to see it :D

I'd take a different approach to this problem and assume the argument is a sequence rather than a str. This simplifies our logic, allowing us to pass str to the initial call, but the recursive calls can pass list:
def puzzle(sequence):
if len(sequence) > 1:
first, *middle, last = sequence
return first + last + puzzle(middle) + last + first
return sequence[0] if sequence else ""
if __name__ == '__main__':
print(puzzle("0123456789"))
print(puzzle("12345"))
print(puzzle("012"))
print(puzzle("0"))
OUTPUT
> python3 test.py
09182736455463728190
152434251
02120
0
>

If you approach this as processing the first character (placing it at the beginning and end) and recursing with the inverted remainder of the string then your exit condition will be an empty string:
def mirror(S):
return "" if not S else S[0]+mirror(S[:0:-1])+S[0]
print(mirror("0123456789")) # 09182736455463728190
If you want to avoid generating new (inverted) strings on every recursion, you could also implement it using an index that you carry along and map to alternating start/end relative positions:
def mirror(S,i=0):
j = (i+1)//2 * (-1)**i
return S[j]+mirror(S,i+1)+S[j] if i<len(S) else ""

Related

How to increment and decrement with a string Python?

Hope you all are doing well in these times.
here's my code:
def ab(n):
first = 0
last = -1
endprod = n[first] + n[last]
endprod2 = n[first+1] + n[last-1]
endprod3 = n[first+2] + n[last-2]
endprod4 = n[first+3] + n[last-3]
endprod5 = n[first+4] + n[last-4]
endprod100 = endprod[::-1] + endprod2[::-1] + endprod3[::-1]+ endprod4[::-1]+ endprod5[::-1]
return endprod100
I was able do to it, however mine isn't a loop. Is there a way to convert my code into a for loop. So, increment by 1 and decrement by 1.
Thanks,
Try this:
def ab(n):
result = ''
for j in range(len(n) // 2):
result += n[-j-1] + n[j]
if len(n) % 2 == 1:
result += n[len(n) // 2]
return result
You also need the part
if len(n) % 2 == 1:
result += n[len(n) // 2]
because your input string might have an odd number of characters
Examples:
>>> ab('0123456789')
'9081726354'
>>> ab('01234956789')
'90817263549'
If you want to reuse your original logic:
def ab(n):
result = ''
first = 0
last = -1
for j in range(len(n) // 2):
result += n[last-j] + n[first+j]
if len(n) % 2 == 1:
result += n[len(n) // 2]
return result
You could also recurse it:
def ab(s):
if len(s)>2:
return s[-1]+s[0]+ab(s[1:-1])
else:
return s
But the last part of Riccardo's answer fits your question more closely.
you need to split your string for your loop, means first you broke your string to half then build your string, you could use zip to iterate over multiple iteratable. something like this:
def ab(s):
out = ""
for v0,v1 in zip(s[:len(s)//2 -1 :-1], s[:len(s)//2 ]):
out += v0 + v1
return out
the better version you should write without loop.
like this:
out = "".join(map(lambda x: "".join(x), zip(s[:len(s)//2 -1 :-1], s[:len(s)//2 ])))
There are already a lot of good answers that are probably clearer, easier to read and much better suited for learning purposes than what I'm about to write. BUT... something I wanted to bring up (maybe just telling myself, because I tend to forget it) is that sometimes destroying the original argument can facilitate this sort of things.
In this case, you could keep "poping" left and right, shrinking your string until you exhaust it
def swap_destr(my_str):
result = ""
while len(my_str) > 1:
result += my_str[-1] # Last char
result += my_str[0] # First char
my_str = my_str[1:-1] # Shrink it!
return result + my_str # + my_str just in case there 1 char left
print(swap_destr("0123456789"))
print(swap_destr("123456789"))
# Outputs:
# 9081726354
# 918273645
This is also a good problem to see (and play with) recursion:
def swap_recur(my_str):
if len(my_str) <= 1:
return my_str
return my_str[-1] + my_str[0] + swap_recur(my_str[1:-1])
print(swap_recur("0123456789"))
print(swap_recur("123456789"))
# Outputs:
# 9081726354
# 918273645

Converting an iterative solution to a recursive solution

I'm currently working on a problem that requires I design a function that takes a string of '0's, '1's, and 'X's as an argument and returns a generator which yields the different combinations of the X's turned to 1's and 0's
ie: passing '0XX1', would return a generator that yields->
0001,
0101,
0011,
0111,
I have solved the problem iteratively, but need to be able to able to solve it recursively. What is the best way to approach this type of problem? In a complex problem like this (well, complex to me!), how do I identify the base case and the recursive case?
Below is my iterative solution:
from typing import Generator
def binary_strings(string: str) -> Generator[str, None, None]:
listOfIndices = []
starterString = ''
for index, char in enumerate(string):
if char == 'X':
starterString = starterString + '0'
listOfIndices.append(index)
else:
starterString = starterString + char
def stringGenerator(): #generates the different combos
baseString = starterString
moddedString = ''
n = len(listOfIndices)
counter = 1
for i, character in enumerate(
starterString):
if i == 0:
yield starterString
else:
break
while counter <= n:
for i, chara in enumerate(baseString):
if i in listOfIndices:
moddedString = baseString[:i] + '1' + baseString[i + 1:]
yield moddedString
counter += 1
if counter > n and n >= 1:
counter = 1
n -= 1
baseString = moddedString
break
else:
continue
return stringGenerator()
It's often the case that recursive functions are easier to reason about and shorter. Typically you'll start with a base case. Here you can imagine what your function should yield with an empty string. Probably ''.
Next if your first character is not an X you just yield that first character plus the result of recursively calling the rest. If it is and X then you yield both 1+recursive call and 0+recursive call. Something like:
def combos(s):
if len(s) == 0:
yield ''
return
head, *tail = s
for combo in combos(tail):
if head == 'X':
yield '1'+ combo
yield '0'+ combo
else:
yield head + combo
s = '0XX1'
list(combos(s))
#['0111', '0011', '0101', '0001']
Ignoring the (trivial) base case (that is, where there are no X's to replace), binary_strings(s) = binary_strings(s') + binary_strings(s'') where s' is s with the first X replaced with a 0, and s'' is s with the first X replaced with a 1.

What is a faster version of this code instead of the double for-loop (python)?

When I hand this code in on a site (from my university) that corrects it, it is too long for its standards.
Here is the code:
def pangram(String):
import string
alfabet = list(string.ascii_lowercase)
interpunctie = string.punctuation + "’" + "123456789"
String = String.lower()
string_1 = ""
for char in String:
if not char in interpunctie:
string_1 += char
string_1 = string_1.replace(" ", "")
List = list(string_1)
List.sort()
list_2 = []
for index, char in enumerate(List):
if not List[index] == 0:
if not (char == List[index - 1]):
list_2.append(char)
return list_2 == alfabet
def venster(tekst):
pangram_gevonden = False
if pangram(tekst) == False:
return None
for lengte in range(26, len(tekst)):
if pangram_gevonden == True:
break
for n in range(0, len(tekst) - lengte):
if pangram(tekst[n:lengte+n]):
kortste_pangram = tekst[n:lengte+n]
pangram_gevonden = True
break
return kortste_pangram
So the first function (pangram) is fine and it determines whether or not a given string is a pangram: it contains all the letters of the alphabet at least once.
The second function checks whether or not the string(usually a longer tekst) is a pangram or not and if it is, it returns the shortest possible pangram within that tekst (even if that's not correct English). If there are two pangrams with the same length: the most left one is returned.
For this second function I used a double for loop: The first one determines the length of the string that's being checked (26 - len(string)) and the second one uses this length to go through the string at each possible point to check if it is a pangram. Once the shortest (and most left) pangram is found, it breaks out of both of the for loops.
However this (apparantly) still takes too long. So i wonder if anyone knew a faster way of tackling this second function. It doesn't necessarily have to be with a for loop.
Thanks in advance
Lucas
Create a map {letter; int} and activecount counter.
Make two indexes left and right, set them in 0.
Move right index.
If l=s[right] is letter, increment value for map key l.
If value becomes non-zero - increment activecount.
Continue until activecount reaches 26
Now move left index.
If l=s[left] is letter, decrement value for map key l.
If value becomes zero - decrement activecount and stop.
Start moving right index again and so on.
Minimal difference between left and right while
activecount==26 corresponds to the shortest pangram.
Algorithm is linear.
Example code for string containing only lower letters from alphabet 'abcd'. Returns length of the shortest substring that contains all letters from abcd. Does not check for valid chars, is not thoroughly tested.
import string
def findpangram(s):
alfabet = list(string.ascii_lowercase)
map = dict(zip(alfabet, [0]*len(alfabet)))
left = 0
right = 0
ac = 0
minlen = 100000
while left < len(s):
while right < len(s):
l = s[right]
c = map[l]
map[l] = c + 1
right += 1
if c==0:
ac+=1
if ac == 4:
break
if ac < 4:
break
if right - left < minlen:
minlen = right - left
while left < right:
l = s[left]
c = map[l]
map[l] = c - 1
left += 1
if c==1:
ac-=1
break
if right - left + 2 < minlen:
minlen = right - left + 1
return minlen
print(findpangram("acacdbcca"))

Finding if string is concatenation of others

I am working on a problem where one must determine if a string is a concatenation of other string (these strings can be repeated in the concatenated strings). I am using backtracking to be as efficient as possible. If the string is a concatenation, it will print the strings it is a concatenation of. If not, it will print NOT POSSIBLE. Here is my python code:
# note: strList has to have been sorted
def findFirstSubstr(strList, substr, start = 0):
index = start
if (index >= len(strList)):
return -1
while (strList[index][:len(substr)] != substr):
index += 1
if (index >= len(strList)):
return -1
return index
def findPossibilities(stringConcat, stringList):
stringList.sort()
i = 0
index = 0
substr = ''
resultDeque = []
indexStack = []
while (i < len(stringConcat)):
substr += stringConcat[i]
index = findFirstSubstr(stringList, substr, index)
if (index < 0):
if (len(resultDeque) == 0):
return 'NOT POSSIBLE'
else:
i -= len(resultDeque.pop())
index = indexStack.pop() + 1
substr = ''
continue
elif (stringList[index] == substr):
resultDeque.append(stringList[index])
indexStack.append(index)
index = 0
substr = ''
i += 1
return ' '.join(resultDeque)
I keep failing the last half of the test cases and can't figure out why. Could someone prompt me in the right direction for any cases that this would fail? Thanks!
First, of all, this code is unnecessary complicated. For example, here is an equivalent but shorter solution:
def findPossibilities(stringConcat, stringList):
if not stringConcat: # if you want exact match, add `and not stringList`
return True
return any(findPossibilities(stringConcat[len(s):],
stringList[:i] + stringList[i+1:]) # assuming non-repeatable match. Otherwise, simply replace with `stringList`
for i, s in enumerate(stringList)
if stringConcat.startswith(s))
Actual answer:
Border condition: remaining part of stringConcat matches some of stringList, search is stopped:
>>> findPossibilities('aaaccbbbccc', ['aaa', 'bb', 'ccb', 'cccc'])
'aaa ccb bb'

Why can't I initialize a variable to the indices of two other variables working on a string?

The wording of my question was not polite to the search feature on the site, so I apologize should someone feel this is a duplicate question, but I must ask anyway.
Working in Python 3.6.1, my goal is to find a substring of letters in a string that are in alphabetical order and if that substring of letters is the longest substring of letters in alphabetical order (aa would be considered alphabetical order), then print out the string. I have not gotten entirely close to the solution but I'm making progress; however, this came up and I'm confounded by it being completely new to Python. My question is, why is this valid:
s = 'hijkkpdgijklmnopqqrs'
n = len(s)
i = 0
a = 0
for i in range(n-2):
if s[i] <= s[i+1]:
a = s[i+1]
i = s[i+2]
a = i + a
print(a)
And yet this is not:
s = 'hijkkpdgijklmnopqqrs'
n = len(s)
i = 0
a = 0
b = ''
for i in range(n-2):
if s[i] <= s[i+1]:
b = a + i
a = s[i+1]
i = s[i+2]
a = a + i
print(b)
When the latter code is run, I receive the error:
Traceback (most recent call last):
File "C:\Users\spect\Desktop\newjackcity.py", line 14, in <module>
b = a + i
TypeError: must be str, not int
What I am ultimately trying to do is to 'index in' to the string s, compare the zeroth element to the zeroth+1 element and if s[I] < s[I+1], I want to concatenate the two into my variable a for later printing. Because when I do this, a only prints out two letters in the string. I thought, well initialize the variable first so that a and i can be incremented, then added into a for comparison purposes, and b for printing.
I see now that I'm only going through n-2 iterations (in order to compare the second to last letter to n-1 so the logic is flawed, but I still don't understand the error of why all of a sudden binding a+i to a variable b will produce a str/int error? In my view saying s[i]; etc. is pulling out the elements as a string and this to me is proven in the fact if I run the first set of code, I get the output:
sr
>>>
In both for loops, you use i as the loop variable, so it starts as an int.
In the first version, you reassign i to a string, then add.
for i in range(n-2):
# here i is an int, something between 0 and n-2
if s[i] <= s[i+1]:
a = s[i+1] # a is a string...
i = s[i+2] # now you change i to a string
a = i + a # string + string: OK!
In the second version you try to add i first:
for i in range(n-2):
# here i is an int, something between 0 and n-2
if s[i] <= s[i+1]:
b = a + i # string + int, can't do it...
a = s[i+1]
i = s[i+2]
a = a + i
You will have an easier time debugging your code if you pick more meaningful names.
edit: here is my cleaned up version of your code:
s = 'hijkkpdgijklmnopqqrs'
# i = 0 isn't needed, range starts at 0
# the first character is always 'alphabetical'
alph_substr = s[0]
# range(1,n) is [1,2, ..., n-1]
for i in range(1, len(s)):
if s[i-1] <= s[i]:
alph_substr = alph_substr + s[i]
else:
# we have to start over, since we're not alphabetical anymore
print(alph_substr)
alph_substr = s[i]
print(alph_substr)

Categories

Resources