Ruby noobster does not understand arrays - ruby

I'm not a programmer, but I want to become one. So, I read books, I do tutorials and I ask questions. Here is the question:
I'm trying to do a Ruby quiz – the one with the Solitaire cypher (http://rubyquiz.com/quiz1.html). I wrote some code that works pretty well, except that at one point it alters the key_deck array which should be a reference to the end of the program. I do not know where this happens or why.
Here is my noobish code:
$characters = Array ('A' .. 'Z')
def encode to_encode, input_deck
trekljdfg = input_deck
edeck = input_deck
work_string = ''
to_encode.upcase.split("").each do |char|
if $characters.include?(char)
work_string.concat(char)
end
end
work_string = work_string + ('X' * ((5 - (work_string.length % 5)) % 5))
keystream_string = ''
while keystream_string.length < work_string.length do # <-- generate keystream
edeck = permutation(edeck)
keystream_string.concat(get_letter(edeck))
end
encoded = combine_with_keystream(work_string, keystream_string)
encoded = split_string_in_groups(encoded)
return encoded
end
def decode to_decode, input_deck
ddeck = input_deck
to_decode = to_decode.delete(' ')
keystream_string = ''
while keystream_string.length < to_decode.length do # <-- generate keystream
ddeck = permutation(ddeck)
keystream_string.concat(get_letter(ddeck))
end
array_to_decode = text_to_numbers to_decode
array_keystream_string = text_to_numbers keystream_string
decoded = ''
for i in 0..(array_to_decode.length-1)
if array_to_decode[i] >= array_keystream_string[i]
decoded.concat($characters[(array_to_decode[i] - array_keystream_string[i])-1])
else
decoded.concat($characters[(array_to_decode[i] + 26 - array_keystream_string[i])-1])
end
end
decoded = split_string_in_groups decoded
return decoded
end
def permutation deck_to_change
deck = deck_to_change
def swap array, joker
work_array = array
joker_position = work_array.index(joker)
if joker_position == (work_array.length-1)
temp_array = work_array.slice!(1..(work_array.length-2))
work_array = work_array + temp_array
else
work_array[joker_position], work_array[joker_position+1] = work_array[joker_position+1], work_array[joker_position]
end
return work_array
end
deck = swap(deck, 'A') # <-- swap first joker
2.times do # <-- swap second joker
deck = swap(deck, 'B')
end
if deck.index('A') < deck.index('B') # <-- triple cut
joker_position1 = deck.index('A')
joker_position2 = deck.index('B') - joker_position1
else
joker_position1 = deck.index('B')
joker_position2 = deck.index('A') - joker_position1
end
if joker_position1 == 0
temp_array1 = []
else
temp_array1 = deck.slice!(0..joker_position1-1)
end
if joker_position2 == deck.length-1
temp_array2 = []
else
temp_array2 = deck.slice!(joker_position2+1..deck.length-1)
end
deck = temp_array2 + deck + temp_array1
if (deck.last != 'A') | (deck.last != 'B') # <-- count cut
temp_array1 = deck.slice!(0..deck.last.to_i-1)
temp_array2 = deck.pop(1)
deck = deck + temp_array1 + temp_array2
end
return deck
end
def get_letter deck
first = deck.first
case first
when 'A'
first = '53'
when 'B'
first = '53'
end
if (deck[first.to_i] == 'A') | (deck[first.to_i] == 'B')
return ''
else
return $characters[((deck[first.to_i]).to_i-1) % 26]
end
end
def text_to_numbers text
array = []
text.upcase.split("").each do |char|
array.push($characters.index(char)+1)
end
return array
end
def combine_with_keystream string1, string2
temp_array1 = text_to_numbers string1
temp_array2 = text_to_numbers string2
string = ''
for i in 0..(temp_array1.length-1)
tmp = temp_array1[i] + temp_array2[i]
if tmp > 26
tmp = tmp - 26
end
string.concat($characters[tmp-1])
end
return string
end
def split_string_in_groups string
return string.scan(/.{1,5}/).join(" ")
end
#-begin-------------------------
key_deck = ('1' .. '52').to_a + ['A', 'B'] # <-- this is the key deck ^^
string_to_encode = 'Code in Ruby live longer!' # <-- this is the string to be encoded
string_to_decode = 'GLNCQ MJAFF FVOMB JIYCB' # <-- this is the string to be decoded
puts "Your encoded text is: #{encode(string_to_encode, key_deck)}"
puts "Your decoded text is: #{decode(string_to_decode, key_deck)}"

You are using slice! to find your deck's permutations, which changes the input array.
The easiest solution is to dup the array before working on it:
def encode to_encode, input_deck
trekljdfg = input_deck.dup
edeck = input_deck.dup
# ..
end
def decode to_decode, input_deck
ddeck = input_deck.dup
# ..
end
dup creates a copy of the array, which you can safely mutilate.

Your permutation function alters the deck. You pass it a reference to the original deck, so any changes you do on the reference will actually change the original. Try something like this instead:
key_deck = [....]
...
puts "#{encode(string_to_encode, key_dec.clone}"
The clone method will make a new copy of the array for you so all changes will only apply to the copy.
Or you can just avoit the entire problem by using the Array#shuffle method:
puts "#{encode(string_to_encode, key_dec.shuffle}"
That will give an already shuffled deck to the encode function.

Related

Is there any different way to write below code by initializing hash's key with a default value?

Write a method that returns the no of various lowercase, uppercase, digits and special characters used in the string. Make use of Ranges.
Input = "heLLo Every1"
I am making using of ranges and case method in solution provided.
Solution:
class String
def character_count
uppercase_count = 0
lowercase_count = 0
digit_count = 0
uppercase_range = Range.new('A', 'Z')
lowercase_range = Range.new('a', 'z')
digit_range = Range.new('0', '9')
special_character_count = 0
each_char do |item|
case item
when uppercase_range
uppercase_count += 1
when lowercase_range
lowercase_count += 1
when digit_range
digit_count += 1
else
special_character_count += 1
end
end
[lowercase_count, uppercase_count, digit_count, special_character_count]
end
end
if ARGV.empty?
puts 'Please provide an input'
else
string = ARGV[0]
count_array = string.character_count
puts "Lowercase characters = #{count_array[0]}"
puts "Uppercase characters = #{count_array[1]}"
puts "Numeric characters = #{count_array[2]}"
puts "Special characters = #{count_array[3]}"
end
Code is working.
Yes
class String
def character_count
counters = Hash.new(0)
each_char do |item|
case item
when 'A'..'Z'
counters[:uppercase] += 1
when 'a'..'z'
counters[:lowercase] += 1
when '0'..'9'
counters[:digit] += 1
else
counters[:special] += 1
end
end
counters.values_at(:uppercase, :lowercase, :digit, :special)
end
end
if ARGV.empty?
puts 'Please provide an input'
else
string = ARGV[0]
uppercase, lowercase, digit, special = string.character_count
puts "Lowercase characters = #{lowercase}"
puts "Uppercase characters = #{uppercase}"
puts "Numeric characters = #{digit}"
puts "Special characters = #{special}"
end
You can instead use regex in better way as following,
type = { special: /[^0-9A-Za-z]/, numeric: /[0-9]/, uppercase: /[A-Z]/, lowercase: /[a-z]/ }
'Hello World'.scan(type[:special]).count
# => 1
'Hello World'.scan(type[:numeric]).count
# => 0
'Hello World'.scan(type[:uppercase]).count
# => 2
'Hello World'.scan(type[:lowercase]).count
# => 8
Other option.
First, map your ranges into an Hash:
mapping = { upper: ('A'..'Z'), lower: ('a'..'z'), digits: ('0'..'9'), specials: nil }
Then initialize the recipient Hash to default 0:
res = Hash.new(0)
Finally, map the chars of the input:
input = "heLLo Every1"
input.chars.each { |e| res[(mapping.find { |k, v| v.to_a.include? e } || [:specials]).first ] += 1 }
res
#=> {:upper=>3, :lower=>7, :digits=>1, :specials=>1}
str = "Agent 007 was on the trail of a member of SPECTRE"
str.each_char.with_object(Hash.new(0)) do |c,h|
h[ case c
when /\d/ then :digit
when /\p{Lu}/ then :uppercase
when /\p{Ll}/ then :downcase
else :special
end
] += 1
end
end
#=> {:uppercase=>8, :downcase=>28, :special=>10, :digit=>3}

Simplify similar loops into one

I am writing a braille converter. I have this method to handle the top line of a braille character:
def top(input)
braille = ""
#output_first = ""
#top.each do |k, v|
input.chars.map do |val|
if k.include?(val)
braille = val
braille = braille.gsub(val, v)
#output_first = #output_first + braille
end
end
end
#output_first
end
I'm repeating the same each loop for the middle and bottom lines of a character. The only thing that is different from the method above is that the #top is replaced with #mid and #bottom to correspond to the respective lines.
Trying to figure a way to simplify the each loop so I can call it on top, mid and bottom lines.
You can put the loop in a separate method.
def top(input)
#output_first = handle_line(#top)
end
def handle_line(line)
result = ''
line.each do |k, v|
input.chars.map do |val|
if k.include?(val)
braille = val
braille = braille.gsub(val, v)
result = result + braille
end
end
end
result
end
You can then call handle_line in your #mid and #bottom processing
I'm not sure whats in the #top var but I believe braille has limited number of characters and therefore I would consider some map structure
BRAILLE_MAP = {
'a' => ['..',' .','. '], # just an example top,mid,bot line for character
'b' => ['..','..',' '],
# ... whole map
}
def lines(input)
top = '' # representation of each line
mid = ''
bot = ''
input.each_char do |c|
representation = BRAILLE_MAP[c]
next unless representation # handle invalid char
top << representation[0] # add representation to each line
mid << representation[1]
bot << representation[2]
end
[top,mid,bot] # return the lines
end
There may be better way to handle those 3 variables, but I cant think of one right now

I am creating a caesar cipher in Ruby but the caesar function never finishes

Here is my code:
def caesar(string, shift_factor)
alphabet = Array("a".."z")
new_alph = alphabet.rotate(shift_factor)
new_str = []
new_str = string.downcase.split("")
new_str.each do |i|
print i
if !alphabet.include?(i)
new_str.push(i)
else
equals = alphabet.index(i)
new_str.push(new_alph[equals])
end
end
end
caesar("What a string!", 0)
print new_str.join.capitalize!
The code just keeps on looping and I am not sure how to go about stopping it.
You need a different variable for storing the result string. How about this:
def caesar(string, shift_factor)
alphabet = Array("a".."z")
new_alph = alphabet.rotate(shift_factor)
new_str = string.downcase.split("")
caesar_string = []
new_str.each do |i|
if !alphabet.include?(i)
caesar_string.push(i)
else
equals = alphabet.index(i)
caesar_string.push(new_alph[equals])
end
end
caesar_string
end
caesar_string = caesar("What a string!", 0)
print caesar_string.join.capitalize!
You're iterating over new_str and in each iteration you're pushing another object onto the array so the loop will never end.
In your loop, if you instead replace the character at the index, then you should get the result you're looking for.
def caesar(string, shift_factor)
alphabet = Array("a".."z")
new_alph = alphabet.rotate(shift_factor)
new_str = string.downcase.split("")
new_str.each_with_index do |letter, i|
if !alphabet.include?(letter)
new_str[i] = letter
else
equals = alphabet.index(letter)
new_str[i] = new_alph[equals]
end
end
end
Just to add an example that in Ruby there is always more than one way of doing things:
def caesar(string, shift_factor)
alphabet = ('a'..'z').to_a
string.downcase.tr(alphabet.join, alphabet.rotate(shift_factor).join)
end

Can't convert Fixnum into array (TypeError) Ruby

I'm getting the error:
minesweeper.rb:32:in '|': can't convert Fixnum into Array (TypeError)
from minesweeper.rb:32:in 'block in create_hint_board'
from minesweeper.rb:31:in 'each_index'
from minesweeper.rb:31:in 'create_hint_board'
from minesweeper.rb:68:in '(main)'
when attempting to check a 2D array for a value, and adding 1 to all cells adjacent to that index location. The error occurs at subarray2 = board|i|. I'm trying to iterate over the entire 2D array
The entire code is
#def load_board(file)
# gameboard = File.readlines(file)[1..-1]
# gameboard.map! do |line|
# line.split.map(&:to_s)
# end
# $globalarray = gameboard
#end
$globalarray = [['*','.','.','.'],['.','.','*','.'],['.','.','.','.']]
def pp_board(board)
puts Array.new(board[0].size*2+1, '-').join('')
board.each do |row|
puts "|" + row.join("|") + "|"
puts Array.new(row.size*2+1, '-').join('')
end
end
def create_hint_board(board)
board = $globalarray
$globalarray.each_index do |i|
subarray = $globalarray[i]
subarray.each_index do |j|
if $globalarray[i][j] != '*'
board[i][j].to_i
board[i][j] = 0
end
puts "#{String(i)},#{String(j)} is #{board[i][j]}"
end
end
board.each_index do |i|
subarray2 = board|i|
subarray2.each_index do |j|
if board[i][j] == '*'
board[i+1][j] = board[i+1][j]+1
board[i+1][j+1] = board[i+1][j+1]+1
board[i+1][j-1] = board[i+1][j-1]+1
board[i][j-1] = board[i][j-1]+1
board[i][j+1] = board[i][j+1]+1
board[i-1][j] = board[i-1][j]+1
board[i-1][j+1] = board[i-1][j+1]+1
board[i-1][j-1] = board[i-1][j-1]+1
end
end
end
puts "new array is "
puts board
end
=begin
#def copy_to_blank(board)
# $newarrayblank = $newarray
# $newarrayblank.each_index do |i|
# subarray = $newarrayblank[i]
# subarray.each_index do |j|
# $newarrayblank[i][j] = '.'
# puts "#{String(i)},#{String(j)} is #{$newarrayblank[i][j]}"
# end
# end
#end
#load_board("mines.txt")
blank = [[]]
=end
puts "Original array is"
puts $globalarray
create_hint_board($globalarray)
#pp_board($globalarray)
#create_hint_board($globalarray)
#puts "new array is"
#pp_board($newarray)
#puts "new blank board is"
#copy_to_blank(blank)
#puts $newarrayblank
#pp_board($newarrayblank)
=begin
puts "Input Guess"
value1 = gets.split(" ")
row_guess = value1[0].to_i
col_guess = value1[1].to_i
puts $newarray[row_guess][col_guess]
while $newarray[row_guess][col_guess] != '*'
if $newarray[row_guess][col_guess] != '*'
puts "You guessed row #{row_guess} and column #{col_guess}."
puts $newarray[row_guess][col_guess]
#$newarrayblank[row_guess][col_guess] = $newarray[row_guess][col_guess]
#pp_board($newarrayblank)
puts "Input your guess in coordinates, separated by a blank space, or press q to quit."
value1 = gets.split(" ")
row_guess = value1[0].to_i
col_guess = value1[1].to_i
elsif $newarray[row_guess][col_guess] == '*'
puts "You guessed row #{row_guess} and column #{col_guess}."
puts "You hit a mine!"
puts "Game Over"
end
end
=end
The area giving me trouble is
board.each_index do |i|
subarray2 = board|i|
subarray2.each_index do |j|
if board[i][j] == '*'
board[i+1][j] = board[i+1][j]+1
board[i+1][j+1] = board[i+1][j+1]+1
board[i+1][j-1] = board[i+1][j-1]+1
board[i][j-1] = board[i][j-1]+1
board[i][j+1] = board[i][j+1]+1
board[i-1][j] = board[i-1][j]+1
board[i-1][j+1] = board[i-1][j+1]+1
board[i-1][j-1] = board[i-1][j-1]+1
end
end
end
I've also tried moving the addition section above, as an elsif statement below the if, like so
def create_hint_board(board)
board = $globalarray
$globalarray.each_index do |i|
subarray = $globalarray[i]
subarray.each_index do |j|
if $globalarray[i][j] != '*'
board[i][j].to_i
board[i][j] = 0
elsif board[i][j] == '*'
board[i+1][j] = board[i+1][j]+1
board[i+1][j+1] = board[i+1][j+1]+1
board[i+1][j-1] = board[i+1][j-1]+1
board[i][j-1] = board[i][j-1]+1
board[i][j+1] = board[i][j+1]+1
board[i-1][j] = board[i-1][j]+1
board[i-1][j+1] = board[i-1][j+1]+1
board[i-1][j-1] = board[i-1][j-1]+1
end
end
puts "#{String(i)},#{String(j)} is #{board[i][j]}"
end
end
This results in the error message:
minesweeper.rb:28:in '+': can't convert Fixnum into String (TypeError)
from minesweeper.rb:28:in 'block (2 levels) in create_hint_board'
from minesweeper.rb:28:in 'each_index'
from minesweeper.rb:28:in 'block in create_hint_board'
from minesweeper.rb:28:in 'each_index'
from minesweeper.rb:28:in 'create_hint_board'
from minesweeper.rb:28:in '(main')
The issue is at following line
subarray2 = board|i|
You are doing:
board.each_index do |i|
And in following line you are trying to get the value of board at that index. To achive this you should do:
subarray2 = board[i]
At last, there is a better way to achieve this by using each_with_index.
Eg:
board.each_with_index do |v, i|
subarray2 = v
...
end

Transposing a string

Given a (multiline) string, where each line is separated by "\n" and may not be necessarily of the same length, what is the best way to transpose it into another string as follows? Lines shorter than the longest one should be padded with space (right padding in terms of the original, or bottom padding in terms of the output). Applying the operation on a string twice should be idempotent modulo padding.
Input string
abc
def ghi
jk lm no
Output string
adj
bek
cf
l
gm
h
in
o
Here are five approaches. (Yes, I got a bit carried away, but I find that trying to think of different ways to accomplish the same task is good exercise for the grey cells.)
#1
An uninteresting, brute-force method:
a = str.split("\n")
l = a.max_by(&:size).size
puts a.map { |b| b.ljust(l).chars }
.transpose
.map { |c| c.join.rstrip }.join("\n")
adj
bek
cf
l
gm
h
in
o
#2
This method and all that follow avoid the use of ljust and transpose, and make use of the fact that if e is an empty array, e.shift returns nil and leaves e an empty array. (Aside: I am often reaching for the non-existent method String#shift. Here it would have avoided the need to convert each line to an array of characters.)
a = str.split("\n").map(&:chars)
a.max_by(&:size).size.times.map { a.map { |e| e.shift || ' ' }.join.rstrip }
#3
This and the remaining methods avoid the need to compute the length of the longest string:
a = str.split("\n").map(&:chars)
a_empty = Array(a.size, [])
[].tap { |b| b << a.map { |e| e.shift || ' ' }.join.rstrip while a != a_empty }
#4
This method makes use of Enumerator#lazy, which has been available since v2.0.
a = str.split("\n").map(&:chars)
(0..Float::INFINITY).lazy.map do |i|
a.each { |e| e.shift } if i > 0
a.map { |e| e.first || ' ' }.join.rstrip
end.take_while { c = a.any? { |e| !e.empty? } }.to_a
(I initially had a problem getting this to work, as I was not getting the element of the output (" o"). The fix was adding the third line and changing the line that follows from a.map { |e| e.shift || ' ' }.join.rstrip to what I have now. I mention this because it seems like it may be common problem when using lazy.)
#5
Lastly, use recursion:
def recurse(a, b=[])
return b[0..-2] if a.last.empty?
b << a.map { |e| e.shift || ' ' }.join.rstrip
recurse(a, b)
end
a = str.split("\n").map(&:chars)
recurse(a)
I would write it like this:
def transpose s
lines = s.split(?\n)
longest = lines.map { |l| l.length }.max
(0..longest).map do |index|
lines.map { |l| l[index] || ' ' }.join
end * ?\n
end
This one works
s = "abc\ndef ghi\njk lm no\n"
s = s.split("\n")
s2 = ''
i = 0
while true
line = ''
s.each do |row|
line += (row[i] or ' ')
end
if line.strip == ''
break
end
s2 += line + "\n"
i += 1
end
puts s2
This one also works
s = "abc\ndef ghi\njk lm no\n"
s = s.split("\n")
maxlen = s.inject(0) {|m,r| m=[m, r.length].max}
s.map! {|r| r.ljust(maxlen).split(//)}
s = s.transpose.map {|r| r.join('')}.join("\n")
puts s
A play on what Chron did for an earlier version of ruby (e.g., 1.8.x). Example based on your original input that showed newline characters
str="abc\\n
def ghi\\n
jk lm no\\n"
def transpose s
lines = s.gsub("\\n","").split("\n")
longest = lines.map { |line| line.length }.max
(0..longest).map do |char_index|
lines.map { |line| line.split('')[char_index] || ' ' }.join
end * "\\n\n"
end
puts transpose(str)
I would write it like this:
def transpose_text(text)
# split the text into lines
text = text.split("\n")
# find the length of the longest line
max_line_length = text.map(&:size).max
# pad each line with white space and convert them to character arrays
text.map! { |line| line.ljust(max_line_length).chars }
#transpose the character arrays and then join them all into one string
text.transpose.map(&:join).join("\n")
end

Resources