Solve iteration algorithm in ruby - ruby

So this is hurting my head, I am not very good with programming obviously. I have,
LetterArray = [a,b,c,d,e,f,g]
NumArray = [1,2,3,4,5,6,7,8,9,10]
ListOfLetters = []
and I want to take an element from NumArray and, starting on LetterArray[0], go up var x amount of times in LetterArray, and add that element (say var y to the array. Then starting on y go up the next number in NumArray, and so on. Then print the ListOfLetters to console.
My goal is for the output to be like this: [a, c, f, c, a, f, e, e, f, a].
I am drawing a blank on how to go about this in code.

Something like this (if I get your requirements right of course)?
letter_array = %w[a b c d e f g]
number_array = [1,2,3,4,5,6,7,8,9,10]
list_of_letters = []
number_array.inject(0) do |offset, delta|
list_of_letters << letter_array[offset]
(offset + delta) % letter_array.size
end
p list_of_letters #=> ["a", "b", "d", "g", "d", "b", "a", "a", "b", "d"]

Either I don't understand your problem description, or the example output you showed is wrong from a certain point onwards. Anyway, maybe this gets you started:
letter_array = [*?a..?g]
number_array = *1..10
list_of_letters = []
number_array.inject(0) do |s, n|
i = s + n
list_of_letters << letter_array[i % letter_array.size - 1]
i
end
This produces the output ["a", "c", "f", "c", "a", "g", "g", "a", "c", "f"].
Alternatively you can also first create the indices and then use them (this doesn't require a pre-initialized list_of_letters):
indices = number_array.inject([]) { |a, n| a << (a.last || 0) + n ; a})
list_of_letters = indices.map { |i| letter_array[i%letter_array.size-1] }

ar = ('a'..'g').to_a.cycle #keeps on cycling
res = []
p 10.times.map do |n|
n.times{ar.next} #cycle one time too short (the first time n is 0)
res << ar.next #cycle once more and store
end
p res #=>["a", "c", "f", "c", "a", "g", "g", "a", "c", "f"]

Related

Compressing a given String problem in Ruby

I have been trying to solve this problem for a while now, im supposed to take a given string like "aaabbc" and compress it into a new string that states multiples of a letter in a row in place. So it would output "3a2bc"
So far i managed to print it out except it counts all instances of a letter and im not sure how to get rid of the current repeats:
def compress_str(str)
new_str = []
word = str.split("")
word.each do |char|
count = 0
word.each do |ele|
if ele == char
count += 1
end
end
if count > 1
new_str << count
new_str << char
else
new_str << char
end
end
return new_str.join("")
Example output:
Any suggestions on how I'm supposed to get rid of them?
Using Enumerable#chunk might be a good fit for your needs.
uncompressed = %w(aaabbcddaaaaa aaabb 111ddttttabaaacc)
uncompressed.each do |str|
puts str.chars.chunk{|e| e}.map {|e| "#{e[1].length}#{e[0]}"}.join
end
>>> 3a2b1c2d5a
>>> 3a2b
>>> 312d4t1a1b3a2c
Sure, you can add another check inside map block, so omit 1 before a single element and print as is.
You could use String#chars (1), so Enumerable#chunk_while (2), then Enumerable#flat_map (3) into the desired format and finally Array#join:
str = "aaabbcaa"
str.chars.chunk_while { |x, y| x == y }.flat_map { |e| [(e.size unless e.size == 1), e.first] }.join
#=> "3a2bc2a"
Step by step
# (1)
str.chars#.to_a
#=> ["a", "a", "a", "b", "b", "c", "a", "a"]
so
# (2)
str.chars.chunk_while { |x, y| x == y }#.to_a
#=> [["a", "a", "a"], ["b", "b"], ["c"], ["a", "a"]]
then
# (3)
str.chars.chunk_while { |x, y| x == y }.flat_map { |e| [(e.size unless e.size == 1),e.first] }
#=> [3, "a", 2, "b", nil, "c", 2, "a"]
String#scan can also be handy here.
uncompressed = %w(aaabbcddaaaaa aaabb 111ddttttabaaacc)
uncompressed.map { |w| w.scan(/(.)(\1*)/).map(&:join) }
#⇒ [["aaa", "bb", "c", "dd", "aaaaa"],
# ["aaa", "bb"],
# ["111", "dd", "tttt", "a", "b", "aaa", "cc"]]
And to get the desired outcome.
uncompressed.map do |w|
w.scan(/(.)(\1*)/).map(&:join).map do |l|
"#{l.length}#{l[0]}"
end.join
end
#⇒ ["3a2b1c2d5a", "3a2b", "312d4t1a1b3a2c"]

function that returns the number of letters that repeat in a string

Trying to make a function that counts the number of letters that appear more than once anywhere in a string (not necessarily together, and not the the number of times they repeat). This is what I have:
def num_repeats(string)
repeat = []
i1 = 0
i2 = 1
while i1 < string.length
while i2 < string.length
if (string[i1] == string[i2]) && (!repeat.include? string[i1])
repeat << string[i1]
end
i2 +=1
end
i1+=1
end
return repeat.length
end
puts(num_repeats('sldhelanlaskjkajksda'))
For some reason, it only pushes the first letter of the string if that first letter has been used in the rest of the string, but after that, it seems like the method stops looping through the rest of the string.
I'd like to know first why the current code is not working and if there is a way to fix it, and I also welcome other better solutions.
Here is an orthodox way to do it:
'sldhelanlaskjkajksda'.each_char.group_by(&:itself).count{|_, v| v.length > 1}
# => 6
The reason your code does not work is because, (i) once the i2 loop terminates, you increment i1, and try for another i2 loop in the next i1 iteration, but since i2 hasn't been touched after failed to satisfy the loop condition, it will not satisfy the condition again, and the i2 loop will never run again, and (ii) you are initializing i2 to a constant.
To fix it, initialize i2 within i1 loop at the beginning, and initialize it to i2 = i1 + 1, not 1.
Another way:
s = 'sldhelanlaskjkajksda'
a = s.chars
#=> ["s", "l", "d", "h", "e", "l", "a", "n", "l", "a",
# "s", "k", "j", "k", "a", "j", "k", "s", "d", "a"]
a.difference(a.uniq).uniq.size
#=> 6
where Array#difference is defined in my answer here.
We have:
b = a.uniq
#=> ["s", "l", "d", "h", "e", "a", "n", "k", "j"]
c = a.difference(b)
#=> ["l", "l", "a", "s", "k", "a", "j", "k", "s", "d", "a"]
d = c.uniq
#=> ["l", "a", "s", "k", "j", "d"]
d.size
#=> 6
None of these answers consider that OP asked for repeating letters
But this does:
'sldhe-lanlas-kjkajksda'.scan(/([a-z])(?=.*\1)/i).uniq.size
#=> 6
This is the solution for your problem
def num_repeats(string)
repeat = []
i1 = 0
i2 = 1
while i1 < string.length
while i2 < string.length
if (string[i1] == string[i2]) && !(repeat.include? string[i1])
repeat << string[i1]
end
i2 +=1
end
i1+=1
i2 = i1 + 1
end
return repeat.length
end
puts(num_repeats('sldhelanlaskjkajksda'))
Here is bit simpler (hopefully) and little Ruby-ish, solution:
def num_repeats(string)
# chars in string
chars = string.split('')
# initialize map - for each char, count is initialized to 0
hash = chars.uniq.inject({}) { |h, c| h[c] = 0; h}
# for each char in string, lets count its occurrences
chars.each do |c|
hash[c] += 1
end
# now lets pick those entries from the map where the count is > 1
hash_with_repeated_chars = hash.select {|k, v| v > 1 }
# now lets pick the chars that are repeated by picking keys of hash
repeated_chars = hash_with_repeated_chars.select { |k, v| k}
# return the count of repeated chars
return repeated_chars.count
end
p num_repeats('abc') # Prints 0
p num_repeats('abbc') # Prints 1
p num_repeats('abbcc') # Prints 2
p num_repeats('aabbcc') # Prints 3
I also have a Ruby version that is different from all other answers (and hence, bit inefficient due to multiple iterations it does internally)
s = 'sldhelanlaskjkajksda'
p s.chars.combination(2).to_a.uniq.map(&:sort).map(&:uniq).select{|a| a.size.eql?(1)}.count
Create a hash and set the default value to 0. Use the gsub method to remove all white space from the string. Convert the string into an array of string characters using the split method. Iterate over each letter in the array and store the key as each letter and the number of times each letter occurs as the key's value. Finally Remove any key from the hash with a value that is less than 2. Return the hash's length since this corresponds to the number of letters in the hash that have occurred more than once in your original string. Hope this explanation helps and that it answers your question. The code below could be more compact but it is in its current form in the hopes of being more explanatory and informative.
def counter(string)
counts = Hash.new(0)
result = string.gsub(" ","")
result = result.split('')
result.each do |letter|
counts[letter] += 1
end
counts.delete_if { |key,value| value < 2}
return counts.length
end

Ruby: How to create a letter counting function

I wouldn't have asked for help without first spending a few hours trying to figure out my error but I'm at a wall. So there is my attempt but I'm getting false when I try to pass the argument though and I'm not sure why. I know there are other ways to solve this problem that are a little shorter but I'm more interested in trying to get my code to work. Any help is much appreciated.
Write a method that takes in a string. Your method should return the most common letter in the array, and a count of how many times it appears.
def most_common_letter(string)
idx1 = 0
idx2 = 0
counter1 = 0
counter2 = 0
while idx1 < string.length
idx2 = 0
while idx2 < string.length
if string[idx1] == string[idx2]
counter1 += 1
end
idx2 += 1
end
if counter1 > counter2
counter2 = counter1
counter1 = 0
var = [string[idx1], counter2]
end
idx1 += 1
end
return var
end
puts("most_common_letter(\"abca\") == [\"a\", 2]: #{most_common_letter("abca") == ["a", 2]}")
puts("most_common_letter(\"abbab\") == [\"b\", 3]: #{most_common_letter("abbab") == ["b", 3]}")
I didn't rewrite your code because I think it's important to point out what is wrong with the existing code that you wrote (especially since you're familiar with it). That said, there are much more 'ruby-like' ways to go about this.
The issue
counter1 is only being reset if you've found a 'new highest'. You need to reset it regardless of whether or not a new highest number has been found:
def most_common_letter(string)
idx1 = 0
idx2 = 0
counter1 = 0
counter2 = 0
while idx1 < string.length
idx2 = 0
while idx2 < string.length
if string[idx1] == string[idx2]
counter1 += 1
end
idx2 += 1
end
if counter1 > counter2
counter2 = counter1
# counter1 = 0 THIS IS THE ISSUE
var = [string[idx1], counter2]
end
counter1 = 0 # this is what needs to be reset each time
idx1 += 1
end
return var
end
Here's what the output is:
stackoverflow master % ruby letter-count.rb
most_common_letter("abca") == ["a", 2]: true
most_common_letter("abbab") == ["b", 3]: true
I think you're aware there are way better ways to do this but frankly the best way to debug this is with a piece of paper. "Ok counter1 is now 1, indx2 is back to zero", etc. That will help you keep track.
Another bit of advice, counter1 and counter2 are not very good variable names. I didn't realize what you were using them for initially and that should never be the case, it should be named something like current_count highest_known_count or something like that.
Your question has been answered and #theTinMan has suggested a more Ruby-like way of doing what you want to do. There are many other ways of doing this and you might find it useful to consider a couple more.
Let's use the string:
string = "Three blind mice. Oh! See how they run."
First, you need to answer a couple of questions:
do you want the frequency of letters or characters?
do you want the frequency of lowercase and uppercase letters combined?
I assume you want the frequency of letters only, independent of case.
#1 Count each unique letter
We can deal with the case issue by converting all the letters to lower or upper case, using the method String#upcase or String#downcase:
s1 = string.downcase
#=> "three blind mice. oh! see how they run."
Next we need to get rid of all the characters that are not letters. For that, we can use String#delete1:
s2 = s1.delete('^a-z')
#=> "threeblindmiceohseehowtheyrun"
Now we are ready to convert the string s2 to an an array of individual characters2:
arr = s2.chars
#=> ["t", "h", "r", "e", "e", "b", "l", "i", "n", "d",
# "m", "i", "c", "e", "o", "h", "s", "e", "e", "h",
# "o", "w", "t", "h", "e", "y", "r", "u", "n"]
We can combine these first three steps as follows:
arr = string.downcase.gsub(/[^a-z]/, '').chars
First obtain all the distinct letters present, using Array.uniq.
arr1 = arr.uniq
#=> ["t", "h", "r", "e", "b", "l", "i", "n",
# "d", "m", "c", "o", "s", "w", "y", "u"]
Now convert each of these characters to a two-character array consisting of the letter and its count in arr. Whenever you need convert elements of a collection to something else, think Enumerable#map (a.k.a. collect). The counting is done with Array#count. We have:
arr2 = arr1.map { |c| [c, arr.count(c)] }
#=> [["t", 2], ["h", 4], ["r", 2], ["e", 6], ["b", 1], ["l", 1],
# ["i", 2], ["n", 2], ["d", 1], ["m", 1], ["c", 1], ["o", 2],
# ["s", 1], ["w", 1], ["y", 1], ["u", 1]]
Lastly, we use Enumerable#max_by to extract the element of arr2 with the largest count3:
arr2.max_by(&:last)
#=> ["e", 6]
We can combine the calculation of arr1 and arr2:
arr.uniq.map { |c| [c, arr.count(c)] }.max_by(&:last)
and further replace arr with that obtained earlier:
string.downcase.gsub(/[^a-z]/, '').chars.uniq.map { |c|
[c, arr.count(c)] }.max_by(&:last)
#=> ["e", 6]
String#chars returns a temporary array, upon which the method Array#uniq is invoked. As alternative, which avoids the creation of the temporary array, is to use String#each_char in place of String#chars, which returns an enumerator, upon which Enumerable#uniq is invoked.
The use of Array#count is quite an inefficient way to do the counting because a full pass through arr is made for each unique letter. The methods below are much more efficient.
#2 Use a hash
With this approach we wish to create a hash whose keys are the distinct elements of arr and each value is the count of the associated key. Begin by using the class method Hash::new to create hash whose values have a default value of zero:
h = Hash.new(0)
#=> {}
We now do the following:
string.each_char { |c| h[c.downcase] += 1 if c =~ /[a-z]/i }
h #=> {"t"=>2, "h"=>4, "r"=>2, "e"=>6, "b"=>1, "l"=>1, "i"=>2, "n"=>2,
# "d"=>1, "m"=>1, "c"=>1, "o"=>2, "s"=>1, "w"=>1, "y"=>1, "u"=>1}
Recall h[c] += 1 is shorthand for:
h[c] = h[c] + 1
If the hash does not already have a key c when the above expression is evaluated, h[c] on the right side is replaced by the default value of zero.
Since the Enumerable module is included in the class Hash we can invoke max_by on h just as we did on the array:
h.max_by(&:last)
#=> ["e", 6]
There is just one more step. Using Enumerable#each_with_object, we can shorten this as follows:
string.each_char.with_object(Hash.new(0)) do |c,h|
h[c.downcase] += 1 if c =~ /[a-z]/i
end.max_by(&:last)
#=> ["e", 6]
The argument of each_with_object is an object we provide (the empty hash with default zero). This is represented by the additional block variable h. The expression
string.each_char.with_object(Hash.new(0)) do |c,h|
h[c.downcase] += 1 if c =~ /[a-z]/i
end
returns h, to which max_by(&:last) is sent.
#3 Use group_by
I will give a slightly modified version of the Tin Man's answer and show how it works with the value of string I have used. It uses the method Enumerable#group_by:
letters = string.downcase.delete('^a-z').each_char.group_by { |c| c }
#=> {"t"=>["t", "t"], "h"=>["h", "h", "h", "h"], "r"=>["r", "r"],
# "e"=>["e", "e", "e", "e", "e", "e"], "b"=>["b"], "l"=>["l"],
# "i"=>["i", "i"], "n"=>["n", "n"], "d"=>["d"], "m"=>["m"],
# "c"=>["c"], "o"=>["o", "o"], "s"=>["s"], "w"=>["w"],
# "y"=>["y"], "u"=>["u"]}
used_most = letters.max_by { |k,v| v.size }
#=> ["e", ["e", "e", "e", "e", "e", "e"]]
used_most[1] = used_most[1].size
used_most
#=> ["e", 6]
In later versions of Ruby you could simplify as follows:
string.downcase.delete('^a-z').each_char.group_by(&:itself).
transform_values(&:size).max_by(&:last)
#=> ["e", 6]
See Enumerable#max_by, Object#itself and Hash#transform_values.
1. Alternatively, use String#gsub: s1.gsub(/[^a-z]/, '').
2. s2.split('') could also be used.
3. More or less equivalent to arr2.max_by { |c, count| count }.
It's a problem you'll find asked all over Stack Overflow, a quick search should have returned a number of hits.
Here's how I'd do it:
foo = 'abacab'
letters = foo.chars.group_by{ |c| c }
used_most = letters.sort_by{ |k, v| [v.size, k] }.last
used_most # => ["a", ["a", "a", "a"]]
puts '"%s" was used %d times' % [used_most.first, used_most.last.size]
# >> "a" was used 3 times
Of course, now that this is here, and it's easily found, you can't use it because any teacher worth listening to will also know how to search Stack Overflow and will find this answer.

Hash with array of characters as key

I need to have a Hash in which keys are represented by arrays with chars.
But when i have arrays like these:
a = %w(a b c), b = %w(d e f), c = %w(g h i)
and i create a new Hash and try to give it values, my results are strange, i expect something similar to this:
H = { ["a", "b", "c"] => 1, ["d", "e", "f"] => 2 }
but i get something like this:
{"[\"a\", \"b\", \"c\"]"=>1}
The way i create this hash is simple:
H = {}
H["#{array_name}"]
Is this normal behaviour? If so how can i make these keys normal arrays of chars?
a,b,c are local variables. They are not array names. They are holding the references of 3 different Array instances. So do as below :
a = %w(a b c)
b = %w(d e f)
c = %w(g h i)
H = {}
H[a] = 1
H[b] = 2
H[c] = 3
H # => {["a", "b", "c"]=>1, ["d", "e", "f"]=>2, ["g", "h", "i"]=>3}
One Rubyish way :
a = %w(a b c), %w(d e f), %w(g h i)
Hash[a.zip([1,2,3])]
# => {["a", "b", "c"]=>1, ["d", "e", "f"]=>2, ["g", "h", "i"]=>3}

slice an array by specific way

There is a good slice method in Python like
my_array[3:]
I'm aware there are slice methods in Ruby as well, but there is no method which does exactly the same as Python's my_array[3:] (in case if don't know the size of the array). Is not it?
class Array
def sub_array(pos, len = -1)
if len == -1
then # the rest of the array starting at pos
len = self.size - pos
end
self.slice(pos, len)
end
end
my_array = %w[a b c d e f]
p my_array.sub_array(3) #=> ["d", "e", "f"]
p my_array.sub_array(5) #=> ["f"]
p my_array.sub_array(9) #=> nil
p my_array.sub_array(3, 2) #=> ["d", "e"]
p my_array.sub_array(3, 9) #=> ["d", "e", "f"]
Actually this was originally a substring method for String.
Please have a look at the ruby slice methods here. and as #Blender suggested you can pass a range like:
my_array[3..-1]
EDIT:
range example
array = ["a", "b", "c", "d", "e"]
array[3..-1]
will result in ["d", "e"] as d's index is 3 and e is the last element.
more examples
a = [ "a", "b", "c", "d", "e" ]
a[2] + a[0] + a[1] #=> "cab"
a[6] #=> nil
a[1, 2] #=> [ "b", "c" ]
a[1..3] #=> [ "b", "c", "d" ]
a[4..7] #=> [ "e" ]
a[6..10] #=> nil
a[-3, 3] #=> [ "c", "d", "e" ]
# special cases
a[5] #=> nil
a[5, 1] #=> []
a[5..10] #=> []

Resources