How to interleave items from 2 lists backward - python

Word Problem:
Create a function to interleave the letters of two strings (starting with the first string from right to left) and return the resultant string.
def interleave(s1: str, s2: str) -> str:
I was able to solve this word problem but I need help putting it in a function
def interleave(s1: str, s2: str) -> str:
string1 = s1[::-1]
string2 = s2[::-1]
for i in range(len(string1)):
print(string2[i] + string1[i], end = "")
return print
print(interleave("1234", "5678"))

I can't tell from your question what should happen when the strings are unequal in length. My basic solution would be
def interleave(str1, str2):
return ''.join(c1 + c2 for c1, c2 in zip(str1, str2))
but this will stop with the shortest of the two input strings.

Right now, the function prints the results. Instead store the results in a variable, which you return when done.
Like so:
#!/usr/bin/python3
def interleave(s1: str, s2: str) -> str:
string1 = s1[::-1]
string2 = s2[::-1]
interleaved_string = ''
for i in range(len(string1)):
interleaved_string += string2[i] + string1[i]
return interleaved_string
print(interleave("1234", "5678"))

Your whole loop can be made into a one-liner using zip and map (or a generator comprehension):
def interleave(str1: str, str2: str) -> str:
assert len(str1) == len(str2), "Strings must be equal in length." # Alternatively, consider itertools.zip_longest
return ''.join(map(''.join, zip(str1[::-1], str2[::-1])))
Input:
joined_str = interleave("1234", "5678")
print(joined_str)
Output:
'48372615'

Related

Leetcode problem 14. Longest Common Prefix (Python)

I tried to solve the problem (you can read description here: https://leetcode.com/problems/longest-common-prefix/) And the following is code I came up with.
It gives prefix value of the first string in strs list and compares prefix with every string from the list, popping all characters that are not equal.
class Solution:
def longestCommonPrefix(self, strs: List[str]) -> str:
prefix = strs[0][0]
for i in range(len(strs)):
for j in range(len(prefix)):
if strs[i][j] != prefix[j]:
prefix.pop(prefix[j])
return prefix
But this code fails in the very first testcase where strs = ["flower","flow","flight"]
Expected output is "fl", while my code returns just "f"
I am struggling to find what is going wrong in my solution. Maybe you can help?
Iterate over the characters in parallel with zip:
strs = ["flower", "flow", "flight"]
n = 0
for chars in zip(*strs):
if len(set(chars)) > 1:
break
n += 1
# length
print(n) # 2
# prefix
print(strs[0][:n]) # fl
Similar approach as a one-liner using itertools.takewhile:
from itertools import takewhile
prefix = ''.join([x[0] for x in takewhile(lambda x: len(set(x)) == 1, zip(*strs))])
Alternatively you could try to use the lib in os - commonprefix:
(it's available since Python 3.5+)
def longestCommonPrefix(self, strs: List[str]) -> str:
return os.path.commonprefix(strs)
strs = ["flower","flow","flight"]
print(longestCommonPrefix(strs))

Function to remove more than 2 consecutive repetitions of a string not working

Here's my function:
def remove_more_than_two_reps(text):
result = list(text)
for idx,char in enumerate(text):
if(result[:idx].count(char) > 2):
result.remove(char)
return ''.join(result)
expected result:
text = 'teeeexxxxt'
result = remove_more_than_two_reps(text)
>'teexxt'
My function just returns the original string, what is the problem?
Try using append which is O(1) instead of remove which is O(n):
def remove_more_than_two_reps(text: str) -> str:
result = []
for ch in text:
if len(result) < 2 or result[-1] != ch or result[-2] != ch:
result.append(ch)
return ''.join(result)
text = 'teeeexxxxt'
result = remove_more_than_two_reps(text)
print(result)
Output:
teexxt
Another option could be using a pattern, matching 3 or more times the same character (.)\1{2,} and in the replacement use 2 times the captured group value:
import re
def remove_more_than_two_reps(text):
return re.sub(r'(.)\1{2,}', r'\1\1', text)
text = 'teeeexxxxt'
print(remove_more_than_two_reps(text))
Output
teexxt
See a regex demo and a Python demo.
Wanted to share an itertools solution, useful when you have particularly big strings (since it avoids allocating an enormous list):
import itertools as it
def remove_more_than_two_reps(text: str) -> str:
reps_of_at_most_two = (it.islice(reps, 2) for _, reps in it.groupby(text))
return ''.join(it.chain.from_iterable(reps_of_at_most_two))

Why is this sort string by chars frequency code not working?

So the question is: Given a string, sort it in decreasing order based on the frequency of characters.
Ex: "tree"
returns: "eert" or "eetr" both are valid answers
So my thought process is to create a counter to hold freq numbers, then sort by freq, in reverse order. However, it's not passing all testcases. For example, it works on "tree" but does not work on "longeststringhere", returns "eeengststrngrloih" which is not a valid answer because all the same characters must be grouped together. Thoughts on why my code doesn't work...?
Code:
class Solution:
def frequencySort(self, s: str) -> str:
freq = collections.Counter(s)
return "".join(sorted(list(s), key = lambda x: freq[x], reverse = True))
I expect dreamcrash's approach to be faster, but a quick fix for your approach is to explicitly sort by frequency, then by the value itself. (As a side note: sorted can be called on any iterable, exactly because it doesn't need to care about the internal structure of that iterable.)
class Solution:
def frequencySort(self, s: str) -> str:
freq = collections.Counter(s)
return "".join(sorted(s, key = lambda x: (freq[x], x), reverse = True))
The problem is that when the string had duplicate letters the lookup would happen more than once. To fix the problem, just remove all but the first instance of a character.
class Solution:
def frequencySort(self, s: str) -> str:
freq = collections.Counter(s)
return "".join(sorted(list(dict.fromkeys(list(s))) key = lambda x: freq[x], reverse = True))
Returns 'engstrloih'
A nicer way of writing that is:
class Solution:
def frequencySort(self, s: str) -> str:
freq = collections.Counter(s)
unique_letters = list(dict.fromkeys(list(s)))
return "".join(sorted(unique_letters, key = lambda x: freq[x], reverse = True))
Try the following:
def frequencySort(self, s: str) -> str:
freq = collections.Counter(s)
sorted_by_value = dict(sorted(freq.items(), key=lambda item: item[1], reverse=True))
return''.join([char * freq[char] for char in sorted_by_value])
First, we get the number of times that each characters shows up:
freq = collections.Counter(s)
then we sorted by value
sorted_by_value = dict(sorted(freq.items(), key=lambda item: item[1], reverse=True))
Finally, we iterate over each character (i.e., key of the map) and repeat that character "value" times, and join everything together into a string:
return''.join([char * freq[char] for char in sorted_by_value])
Output:
eeennggssttrrloih

recursively mix strings

I'm trying to link two string together recursively, but not getting the expected results:
For the two strings "abcd" and "xyz" - expected output should be "axbyczd":
def strr(str1, str2):
def recstr(str1, str2, prn):
if str1 == '':
return str2
if str2 == '':
return str1
else:
return prn + recstr(str1[:len(str1)-len(prn)],str2[:len(str2)-len(prn)],prn)
return recstr(str1, str2, '')
print strr("abcdef","12345")
When you ran out of characters in either string, you returned the other string without concatenating it to a running accumulator. Look at what I do when s1 or s2 is empty.
Also, in your recursive case, you have a very complex slicing of s1 and s2. You should really only need to slice s1[1:] and s2[1:]
This should do it
def recstr(s1, s2, answer=''):
if not s1:
return answer+s2
if not s2:
return answer+s1
return recstr(s1[1:], s2[1:], answer+s1[0]+s2[0])
In [15]: s1,s2 = 'abcd', 'xyz'
In [16]: print recstr(s1,s2)
axbyczd
Of course, a much cleaner way to do this would be to use itertools.izip_longest and itertools.chain.from_iterable:
In [23]: zips = itertools.izip_longest(s1,s2, fillvalue='')
In [24]: ''.join(itertools.chain.from_iterable(zips))
Out[24]: 'axbyczd'
[Thanks #AshwiniChaudhary for pointing out the fillvalue param in izip_longest]
Hope this helps

case sensitive string replacement in Python

I need to replace a string in case sensitive way. For example
abc -> def
Abc -> Def
aBc -> dEf
abC -> deF
What can I do this with Python?
from string import maketrans
"Abc".translate(maketrans("abcABC", "defDEF"))
Expanding on Mark Byers' answer, Here's a solution which works for replacement text of any length.
The trick is to send a function to re.sub().
import re
def case_sensitive_replace(string, old, new):
""" replace occurrences of old with new, within string
replacements will match the case of the text it replaces
"""
def repl(match):
current = match.group()
result = ''
all_upper=True
for i,c in enumerate(current):
if i >= len(new):
break
if c.isupper():
result += new[i].upper()
else:
result += new[i].lower()
all_upper=False
#append any remaining characters from new
if all_upper:
result += new[i+1:].upper()
else:
result += new[i+1:].lower()
return result
regex = re.compile(re.escape(old), re.I)
return regex.sub(repl, string)
print case_sensitive_replace("abc Abc aBc abC ABC",'abc','de')
print case_sensitive_replace("abc Abc aBc abC ABC",'abc','def')
print case_sensitive_replace("abc Abc aBc abC ABC",'abc','defg')
Result:
de De dE de DE
def Def dEf deF DEF
defg Defg dEfg deFg DEFG
Here's a method using regular expressions. The key point is that when it finds a match it first modifies the replacement string to match the casing of the matched string. This works because re.sub can take a function as a replacement instead of just a string.
import re
def case_sensitive_replace(s, before, after):
regex = re.compile(re.escape(before), re.I)
return regex.sub(lambda x: ''.join(d.upper() if c.isupper() else d.lower()
for c,d in zip(x.group(), after)), s)
test = '''
abc -> def
Abc -> Def
aBc -> dEf
abC -> deF
'''
result = case_sensitive_replace(a, 'abc', 'def')
print(result)
Result:
def -> def
Def -> Def
dEf -> dEf
deF -> deF
Long time lurker, thought I'd post a suggestion here as some of these seem fairly convoluted.
print map(lambda a, b: b.lower() if a.islower() else b.upper(), "aBc", "def")
It does assume both strings are the same length, however you could easily replace the lambda with a proper function and check for None on the first input.
Not the most efficient way, and it's very crude, but probably something like this could work:
def case_insensitive_replace(string, old, new):
upper_indices = [idx for idx, char in enumerate(string) if char.isupper()]
replaced = list(string.lower().replace(old.lower(), new.lower()))
for idx in upper_indices:
replaced[idx] = replaced[idx].upper()
return "".join(replaced)
I understand that You want to change the second string case according to the first string.
Am i right? So, my solution is the following. String s2 change its case according to the corresponding string s1. The result is storing in s3. One assumption here is the two strings has the same length.
s1 = "AaBb"
s2 = "cdef"
s3 = ""
index = 0
length = len(s1)
while(True):
if s1[index].isupper():
temp = s2[index].upper()
else:
temp = s2[index].lower()
s3 = s3 + temp
index +=1
if index == length:
break
print s3
This will work, although you probably want to add some checks that the string lengths are the same:
string1 = "AbcDEFghiJKLmnO"
string2 = "some other text"
string2 = "".join((string2[i].upper() if string1[i].isupper() else string2[i].lower()
for i in range(len(string1))))

Categories

Resources