How to compress a string in-place - arrays

I have been working on this problem on leetcode https://leetcode.com/problems/string-compression/
Given an array of characters, compress it in-place.
The length after compression must always be smaller than or equal to the original array.
Every element of the array should be a character (not int) of length 1.
After you are done modifying the input array in-place, return the new length of the array.
I almost have a solution, but I can't seem to count the last character in the string and I also am not sure how to make it so if there is only an amount of one of a character that I do not show 1 in the array.
I feel like I'm pretty close and I'd like to try and keep the solution that I have without altering it too much if possible.
This is what I have so far. chars is a list of characters
def compress(chars):
char = 0
curr = 0
count = 0
while curr < len(chars):
if chars[char] == chars[curr]:
count += 1
else:
# if count == 1:
# break
# else:
chars[char-1] = count
char = curr
count = 0
curr += 1
chars[char-1] += 1
return chars
print(compress(["a", "a", "b", "b", "c", "c", "c"]))

I wasn't quite able to format your code to get the answer you were seeking. Based on your answer, I was able to put together code and explanation that could help you:
def compress(chars):
count = 1
current_position = 0
# if it's a single character, just return a
# a basic array with count
if len(chars) == 1:
chars.append("1")
return chars
# loop till the 2nd last character is analyzed
while current_position < len(chars) - 1:
# assume that we haven't reached the 2nd last character
# if next character is the same as the current one, delete
# the current one and increase our count
while current_position < len(chars) - 1 and \
chars[current_position] == chars[current_position + 1]:
del chars[current_position]
count += 1
# if next character isn't the same, time to add the count to
# the list. Split the number into
# character list (e.g. 12 will become ["1", "2"]
# insert those numbers behind the character and increment position
for x in str(count):
chars.insert(current_position + 1, str(x))
current_position += 1
# great, on to the next character
current_position += 1
# if we are now at the last character, it's a lonely character
# give it a counter of 1 and exit the looping
if current_position == len(chars) - 1:
chars.append("1")
break
count = 1
return chars
mylist = ["a","b","b","b","b","b","b","b","b","b","b","b","b"]
print(compress(mylist))
Results
mylist = ["a","b","b","b","b","b","b","b","b","b","b","b","b"]
['a', '1', 'b', '1', '2']
mylist = ["a","a","a","a","a","a","a","a","a","a","b","b","b","b","b","b","b","b","b","b","b","b"]
['a', '1', '0', 'b', '1', '2']
mylist = ["a"]
['a', '1']
mylist = ["a","b"]
['a', '1', 'b', '1']
mylist = ["a","a","b","b","c","c","c"]
['a', '2', 'b', '2', 'c', '3']

Related

Is it possible in Python 3 to update the value of the iterating variable during runtime?

I am trying to write a program that returns the length of longest substring within a string.
This is my code:
def lengthOfLongestSubstring():
dict = {}
s = 'dvdf'
max_substr_length = 0
max_substr = ''
if len(s) < 1:
return 0
else:
for letter in s:
print('String value: ', s)
if letter not in max_substr:
max_substr = max_substr + letter
max_substr_length = len(max_substr)
dict[max_substr] = dict.get(max_substr, max_substr_length)
print(letter, max_substr, max_substr_length, dict)
elif letter in max_substr:
dict[max_substr] = dict.get(max_substr, max_substr_length)
s = s[s.index(letter)+1:]
max_substr = ''
max_substr_length = 0
print(s, letter, max_substr, max_substr_length, dict)
print(dict)
print(max(dict.values(), default=0))
For the input string s = 'dvdf'
I am getting rid of the first instance of the letter that gets repeated in the input string s, in line 18 of my code s = s[s.index(letter)+1:].
So when the second 'd' is encountered, s should get updated to s = 'vdf'
Unfortunately, the for loop doesn't start iterating from the 0th index of this new s. Is there a way that doesn't involve iterating over integer indexes to get the for loop to begin iterating from the beginning when the string is updated?
Well, no not this way.
Python iterates whatever was s in the beginning of the loop.
You should try a different approach like using a cellar storage.
Push every letter to it in the correct order,
loop untils its empty,
pop a value,
do whatever you want with it,
push a value to it, if necessary.
in the end you should have a working example.

Ruby count integers in array

Is there a way I can count the number of integers in an array? I have an array whose members come from a-z and 0-9. I want to count the number of integers in said array. I tried:
myarray.count(/\d/)
...but the count method doesn't regex.
a = 'abcdefghijklmnopqrstuvwxyz'.split('')
a << [0,1,2,3,4,5,6,7,8,9]
t = a.sample(10)
p t.count(/\d/) # show me how many integers in here
The following should return the number of integers present within the array:
['a', 'b', 'c', 1, 2, 3].count { |e| e.is_a? Integer }
# => 3
Since #count can accept a block, we have it check if an element is an Integer, if so it will be counted towards our returned total.
Hope this helps!

How to get all different part in two array?

Suppose I have a sentence:
When Grazia Deledda submitted a short story to a fashion magazine at the age of 13
this sentence is split into two list:
# list 1
[When] [Grazia Deledda] [submitted a short story] [to] [a] [fashion magazine] [at] [the age of] [13]
# list 2
[When] [Grazia Deledda] [submitted] [a short story] [to] [a fashion] [magazine at] [the age of] [13]
Now I want to get the different parts in this two array, this example's result should be:
[
([[submitted a short story]],[[submitted] [a short story]]),
([[a] [fashion magazine] [at]], [[a fashion] [magazine at]])
]
so it should meet these requirements:
every pair should have the same content, for example: [[submitted a short story]] can be joined into 'submitted a short story', and [[submitted] [a short story]] can also be joined into 'submitted a short story'
every pair should have the same start position and end position, for example: [[submitted a short story]] the starts at 3, and ends with 6. [[submitted] [a short story] are the same.
the most important is that every one should be the shortest, for example [[submitted a short story] [to]] and [[submitted] [a short story] [to]] also meets the first two requirements, but it is not the shortest.
Any way to avoid O(n^2) complexity?
I may got wrong direction at beginning, this question can be easy, I have think a good idea:
#!/usr/bin/env python
# encoding: utf-8
# list 1
llist = [["When"], ["Grazia", "Deledda"], ["submitted", "a", "short", "story"], ["to"], ["a"], ["fashion", "magazine"], ["at"], ["the", "age", "of",], ["13"],]
# list 2
rlist = [["When"], ["Grazia", "Deledda"], ["submitted"], ["a", "short", "story"], ["to"], ["a", "fashion"], ["magazine", "at"], ["the", "age", "of",], ["13"],]
loffset = -1
roffset = 0
rindex = 0
lstart = -1
rstart = -1
for lindex, litem in enumerate(llist):
if loffset == roffset and litem != rlist[rindex]:
lstart = lindex
rstart = rindex
loffset += len(litem)
while roffset < loffset:
roffset += len(rlist[rindex])
rindex += 1
if loffset == roffset and lstart >= 0:
print(llist[lstart:lindex+1], rlist[rstart:rindex])
lstart = -1
I tokenize all the words and pad them as a sequence as a list of list. Then I compare the first list against the second building string buffers and match when the index length counts differ. I then remove the duplicate indexe values for out1 and out2 at the end
from keras.preprocessing.text import Tokenizer
tokenizer=Tokenizer()
# list 1
list1 = [["When"], ["Grazia Deledda"], ["submitted a short story"], ["to"],
["a"], ["fashion magazine"], ["at"], ["the age of"], ["13"],["EOS"]]
# list 2
list2 = [["When"], ["Grazia Deledda"], ["submitted"], ["a short story"], ["to"],
["a fashion"], ["magazine at"], ["the age of"], ["13"],["EOS"]]
tokenizer.fit_on_texts([" ".join(item) for item in list1])
tokenizer.fit_on_texts([" ".join(item) for item in list2])
seq1=[]
seq2=[]
for item1,item2 in zip(list1,list2):
seq1.append(tokenizer.texts_to_sequences(item1))
seq2.append(tokenizer.texts_to_sequences(item2))
out1=[]
out2=[]
out1_buffer=[]
out2_buffer=[]
current_index=0
string1=""
for seq1_index in range(len(seq1)-1):
string1=""
index=0
out1_buffer=[]
found=False
#check each seq1 string accumulation until a match is found or the end of queue is detect 16 - maps to eos
while seq1[seq1_index+index][0] != [16] and found==False:
out1_buffer.append(seq1_index+index)
seq_string=" ".join([str(token) for token in seq1[seq1_index+index][0]])
if string1=="":
string1=seq_string
else:
string1+=" "+seq_string
string2=""
out2_buffer=[]
for seq2_index in range(current_index,len(seq2)-1):
seq_string=" ".join([str(token) for token in seq2[seq2_index][0]])
if string2=="":
string2=seq_string
else:
string2+=" "+seq_string
out2_buffer.append(seq2_index)
count_seq1=len(out1_buffer)
count_seq2=len(out2_buffer)
if string1==string2 and count_seq1!=count_seq2:
print("string_a", [list1[int(index)] for index in out1_buffer])
print("string_b",[list2[int(index)] for index in out2_buffer])
current_index=seq2_index+1
print("match",count_seq1,count_seq2)
for index1 in out1_buffer:
out1.append(index1)
for index2 in out2_buffer:
out2.append(index2)
out1_buffer=[]
out2_buffer=[]
found=True
break
index+=1
tuple1=[]
tuple2=[]
result1=[]
for item1 in out1:
found=False
for item2 in out2:
if list1[item1]==list2[item2]:
found=True
break
if found==True:
out2 = list(filter(lambda item2: list1[item1]!=list2[item2],out2))
if found==False:
result1.append(item1)
for item1 in result1:
tuple1.append(list1[item1])
for item2 in out2:
tuple2.append(list2[item2])
tuple1=tuple(tuple1)
tuple2=tuple(tuple2)
print("{}\n{}\n".format(tuple1,tuple2))
output
(['submitted a short story'], ['a'], ['fashion magazine'], ['at'])
(['submitted'], ['a short story'], ['a fashion'], ['magazine at'])

Failing to use numeric characters within Ruby array as indices for a string

I am trying to use the numeric charecters from the array held within the positions argument as indices to access the characters of the string inside the string argument to subsequently print a new string. I have an idea of what I need to do to get it to work, but I am hung up.
Total code thus far:
def scramble_string(string, positions)
str = string
pos = positions.join
newstr = []
i = 0
while i < pos.length do
return newstr.push(str[pos[i]])
i += 1
end
end
scramble_string("hello", [2, 3, 4, 5])
I suspect my problem lies within this part of the code...
return newstr.push(str[pos[i]])
If I understand you, you can use the following to get a given substring of a string, using a range:
'this is a string'[5..8]
=> "is a"
A simple way would be:
str = 'this is a string'
positions = [2,3,6,9,10]
new_str = positions.map {|p| str[p]}.join
=> "iss s"
str = 'this is a string'
positions = [2,3,6,9,1]
str.split('').values_at(*positions).join
#=> "iss h"
Another way, one that does not use join:
positions.each_with_object('') { |i,s| s << str[i] }

Coderbytes Letter Changes (Ruby)

Here is the assigned problem:
Using the Ruby language, have the function LetterChanges(str) take the str parameter being passed and modify it using the following algorithm. Replace every letter in the string with the letter following it in the alphabet (ie. c becomes d, z becomes a). Then capitalize every vowel in this new string (a, e, i, o, u) and finally return this modified string.*
I figured I'd attack this in two parts, however I cannot seem to figure out what I am doing wrong with the first half of the problem!
Here is my code as it currently stands.
def LetterChanges(str)
str.downcase!
str = str.split(//)
alphabet_lower = ["a".."z"]
i = 0
letter = 0
while i < str.length - 1
if alphabet_lower[letter] == str[i]
str[i] = alphabet_lower[letter + 1]
i += 1
letter = 0 ## Added this for the instance when letter > 0 after an 'else'
else
letter += 1
end
end
str = str.join("")
return str
end
This code is having infinite loop. I did try a few other things such as
......
i = 0
alphabet.each do |letter|
if str[i] == letter
str[i] = letter.next ##I also tried letter + 1
i += 1
end
end
......
alphabet_lower = ["a".."z"]
This creates an Array of a single Range element, not what you expected. This is the reason alphabet_lower[letter] == str[i] is never true, causing infinite loop.
Change it to:
alphabet_lower = ("a".."z").to_a
Since there is also whitespace character in your string, it's better be:
alphabet_lower = ("a".."z").to_a + [' ']
Most probably, your if alphabet_lower[letter] == str[i] doesn't get a match, thus your letter increments to infinity, and you get an infinite loop. You can verify this by adding a puts on your else. It would help if you do as Yu Hao said and place your input and it's output. Also, kindly post the calling script for your LetterChanges.
As for the Socratic Way:
The question left, is why does your if alphabet_lower[letter] == str[i] doesn't get a match? And how can you get around with it? :)
#Yu has explained your problem. Here's another way that uses the form of String#gsub that uses a hash to substitute each character in the string.
All the work will be done by the following hash:
H = ('a'..'z').to_a.each_with_object(Hash.new { |h,k| k }) do |c,h|
h[c] = case c
when 'z' then 'A'
when 'd', 'h', 'n', 't' then c.next.upcase
else c.next
end
end
#=> {"a"=>"b", "b"=>"c", "c"=>"d", "d"=>"E", "e"=>"f", "f"=>"g", "g"=>"h",
# "h"=>"I", "i"=>"j", "j"=>"k", "k"=>"l", "l"=>"m", "m"=>"n", "n"=>"O",
# "o"=>"p", "p"=>"q", "q"=>"r", "r"=>"s", "s"=>"t", "t"=>"U", "u"=>"v",
# "v"=>"w", "w"=>"x", "x"=>"y", "y"=>"z", "z"=>"A"}
The construction of the hash is straightforward, except possibly the line that creates it, which is an argument of Enumerable#each_with_object:
Hash.new { |h,k| k }
This creates an empty hash with a default value. Specifically, if h does not have a key k, h[k] returns the default value k. We can see how that works:
H['a'] #=> "b"
H['d'] #=> "E"
H['%'] #=> "%"
H['3'] #=> "3"
H['zombie'] #=> "zombie"
This allows us to write:
def convert(str)
str.gsub(/./, H)
end
which produces the desired results:
convert 'bcd tuv' #=> "cdE Uvw"
convert 'bcd3?tuv' #=> "cdE3?Uvw"
convert '123D ghi$' #=> "123D hIj$"
Note that if we created H without the default:
H = ('a'..'z').to_a.each_with_object({}) do |c,h|
h[c] = case c
...
end
end
the hash mapping would be the same, but we would obtain:
convert 'bcd tuv' #=> "cdEUvw"
convert 'bcd3?tuv' #=> "cdEUvw"
convert '123D ghi$' #=> "hIj"
which is not what we want.

Resources