Ruby, replacing values in a string array with strings/ints - ruby

I'm trying to replace an array with a new array, replacing values as I go along.
Here's the code:
minesarray = [['*','.','.','.'],['.','.','*','.'],['.','.','.','.']]
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
pp_board(minesarray)
count = 0
minesarray.map{ |row|
row.each { |col|
if minesarray[row][col] = '*'
minesarray[row][col]="a"
elif minesarray[row][col] = '.'
minesarray[row][col] = 0
end
}
}
I receive the following error:
mines2.rb:17:in '[]': can't convert Array into Integer (TypeError)
from mines2.rb:17:in 'block (2 levels) in (main)'
from mines2.rb:16:in 'each'
from mines2.rb:16:in 'block in (main)'
from mines2.rb:15:in 'map'
from mines2.rb:15:in '(main)'

The row and col is not the index of the array, but the element itself.
You could do like below:
minesarray = [['*','.','.','.'],['.','.','*','.'],['.','.','.','.']]
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
pp_board(minesarray)
minesarray = minesarray.map { |row|
row.map { |v|
if v == '*'
'a'
elsif v == '.'
'0'
end
}
}
pp_board(minesarray)

The problem is that row and col are not indexes, they are the actual elements of the array.
You also have a couple of other issues. "elif" should be elsif and you're using assignment (=) in your conditionals where you should be using an equality check (==).

Related

Any way to optimize this character counter i wrote in ruby for String class

# Character Counter
class String
def count_lcases
count(('a'..'z').to_a.join(''))
end
def count_upcases
count(('A'..'Z').to_a.join(''))
end
def count_num
count((0..9).to_a.join(''))
end
def count_spl_chars
length - count_lcases - count_upcases - count_num
end
end
input = ARGV[0]
if ARGV.empty?
puts 'Please provide an input'
exit
end
puts 'Lowercase characters = %d' % [input.count_lcases]
puts 'Uppercase characters = %d' % [input.count_upcases]
puts 'Numeric characters = %d' % [input.count_num]
puts 'Special characters = %d' % [input.count_spl_chars]
I used ranges to count characters but count function is called 3 times.
I can always use loops and count it one by one.I was wondering is there any way to optimize this?...
If you are using Ruby 2.7 you could use tally; the string's chars are just iterated one time.
def classify_char(c)
case c
when /[a-z]/ then :lcase
when /[A-Z]/ then :ucase
when /\d/ then :digit
else :other
end
end
p "asg3456 ERTYaeth".chars.map{|c| classify_char(c) }.tally
# => {:lcase=>7, :digit=>4, :other=>2, :ucase=>4}
If Ruby 2.3...2.7, this will work:
CHAR_CLASSES = {
lcase: ?a..?z,
ucase: ?A..?Z,
digit: ?0..?9,
}
p "asg3456 ERTYaeth".each_char.with_object(Hash.new(0)) { |c, o|
o[CHAR_CLASSES.find { |label, group| group === c }&.first || :other] += 1
}
For < 2.3,
p "asg3456 ERTYaeth".each_char.with_object(Hash.new(0)) { |c, o|
p = CHAR_CLASSES.find { |label, group| group === c }
o[p ? p.first : :other] += 1
}

For loop and if in puts function - Ruby

I am trying to use for loop and if condition in creating a file using File.open and puts function. My code is
I want to write these entries only if it is not null. How to do it?
Edit: Full code is
require 'fileutils'
require 'json'
require 'open-uri'
require 'pp'
data = JSON.parse('data')
array = data
if array &.any?
drafts_dir = File.expand_path('../drats', dir)
FileUtils.mkdir_p(drafts_dir)
array.each do |entry|
File.open(File.join(drafts_dir, "#{entry['twitter']}.md"), 'wb') do |draft|
keys = 1.upto(6).map { |i| "key_#{i}" }
values = keys.map { |k| "<img src='#{entry['image']} alt='image'>" if entry['image']}
# you can also do values = entry.values_at(*keys)
str = values.reject do |val|
val.nil? || val.length == 0
end.join("\n")
draft.puts str
end
end
end
I need the the file `mark.md` as
https://somesite.com/image.png' alt='image'>
https://twitter.com/mark'>mark
and `kevin.md` likewise.
you can build the string from an array, rejecting the null values:
keys = 1.upto(6).map { |i| "key_#{i}" }
values = keys.map { |k| entry[k] }
# you can also do values = entry.values_at(*keys)
str = values.reject do |val|
val.nil? || val.length == 0
end.join("\n")
draft.puts str
update in response to your changed question. Do this:
array.each do |entry|
File.open(File.join(drafts_dir, "#{entry['twitter']}.md"), 'wb') do |draft|
next unless ['image', 'twitter'].all? { |k| entry[k]&.length > 1 }
str = [
"<img src='#{entry['image']} alt='image'>",
"<a href='https://twitter.com/#{entry['twitter']}'>#{entry['twitter']}</a>"
].join("\n")
draft.puts str
end
end
Assuming, your entry is hash.
final_string = ''
entry.each_value { |value| final_string << "#{value}\n" }
puts final_string

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

Need help indenting tags in the output in Ruby

UPDATE: OK, so I implemented your code, but now the indentation is not showing up! Any ideas what might be wrong? I modified the code so that it would attempt to pass my original test (this is only an exercise so in real life I would not be overriding the XmlDocument class) and here is the modified code:
class XmlDocument
attr_reader :indent_depth, :bool
def initialize(bool = false, indent_depth = 0)
#indent_depth = indent_depth
#bool = bool
end
def method_missing(name, *args)
indentation = ' '*indent_depth
attrs = (args[0] || {}).map { |k, v| " #{k}='#{v}'" }.join(' ')
if block_given?
puts indent_depth
opening = "#{indentation}<#{name}#{attrs}>"
contents = yield(XmlDocument.new(true,indent_depth+1))
closing = "#{indentation}</#{name}>"
bool ? opening + "\n" + contents + "\n" + closing : opening + contents + closing
else
"#{indentation}<#{name}#{attrs}/>"
end
end
end
I'm trying to get the method to pass this test:
it "indents" do
#xml = XmlDocument.new(true)
#xml.hello do
#xml.goodbye do
#xml.come_back do
#xml.ok_fine(:be => "that_way")
end
end
end.should ==
"<hello>\n" +
" <goodbye>\n" +
" <come_back>\n" +
" <ok_fine be='that_way'/>\n" +
" </come_back>\n" +
" </goodbye>\n" +
"</hello>\n"
...but I'm unsure as to where to go with my code, below. I was thinking of using a counter to keep track of how far indented we have to go. I tried some code, but then deleted it because it was getting too messy and I have a feeling that the indentation should not be too complicated to implement.
class XmlDocument
def initialize(bool = false)
#bool = bool
end
def send(tag_name)
"<#{tag_name}/>"
end
def method_missing(meth, arg={}, &block)
arbitrary_method = meth.to_s
tag_string = ''
# 1) test for block
# 2) test for arguments
# 3) test for hash
if block_given? # check for #xml.hello do; #xml.goodbye; end
if yield.class == String # base case: #xml.hello do; "yellow"; end
"<#{arbitrary_method}>#{yield}</#{arbitrary_method}>"
else # in the block we do not have a string, we may have another method
method_missing(yield)
end
elsif arg.empty? # no arguments e.g. #xml.hello
send(arbitrary_method)
else # hash as argument e.g. #xml.hello(:name => 'dolly')
send("#{arbitrary_method} #{arg.keys[0]}='#{arg.values[0]}'")
end
end
end
Your code needs a lot of work - some pointers:
Do not override the send method!
Don't call yield over and over - you don't know what side effects you might cause, not to mention a performance hit - call it once, and remember the return value.
You might want to read up on how to write a DSL (here is a blogpost on the subject), to see how it was done correctly in other places.
Ignoring the above, I will try to answer your question regarding indentation.
In a DSL use case, you might want to use a context object which holds the indentation depth as state:
class Indented
attr_reader :indent_depth
def initialize(indent_depth = 0)
#indent_depth = indent_depth
end
def method_missing(name, *args)
indentation = ' ' * indent_depth
attrs = (args[0] || {}).map { |k, v| "#{k}='#{v}'" }.join(' ')
if block_given?
"#{indentation}<#{name} #{attrs}>\n" +
yield(Indented.new(indent_depth + 1)) +
"\n#{indentation}</#{name}>"
else
"#{indentation}<#{name} #{attrs}/>"
end
end
end
xml = Indented.new
puts xml.hello do |x|
x.goodbye do |x|
x.come_back do |x|
x.ok_fine(:be => "that_way")
end
end
end
# => <hello >
# => <goodbye >
# => <come_back >
# => <ok_fine be='that_way'/>
# => </come_back>
# => </goodbye>
# => </hello>

Refactoring feedback for Reverse Polish Notation (RPN) or Postfix Notation

One of the pre-work exercises for Dev Bootcamp is an RPN calculator. I made it work but would like refactoring feedback. Any and all help to make this code cleaner is greatly appreciated.
class RPNCalculator
def evaluate(rpn)
a = rpn.split(' ')
array = a.inject([]) do |array, i|
if i =~ /\d+/
array << i.to_i
else
b = array.pop(2)
case
when i == "+" then array << b[0] + b[1]
when i == '-' then array << b[0] - b[1]
when i == '*' then array << b[0] * b[1]
when i == '/' then array << b[0] / b[1]
end
end
end
p array.pop
end
end
calc = RPNCalculator.new
calc.evaluate('1 2 +') # => 3
calc.evaluate('2 5 *') # => 10
calc.evaluate('50 20 -') # => 30
calc.evaluate('70 10 4 + 5 * -') # => 0
class RPNCalculator
def evaluate rpn
array = rpn.split(" ").inject([]) do |array, i|
if i =~ /\d+/
array << i.to_i
else
b = array.pop(2)
array << b[0].send(i, b[1])
end
end
p array.pop
end
end
I tend to prefer avoiding case..when in favor of lookup tables. So I'd change your code to:
class RPNCalculator
def evaluate(rpn)
a = rpn.split(' ')
array = a.inject([]) do |array, i|
if i =~ /\d+/
array << i.to_i
else
array << array.pop(2).reduce(op(i))
end
end
p array.pop
end
private
def op(char)
{'+'=>:+, '-'=>:-, '/'=>:/, '*'=>:*}[char]
end
end
I also don't believe you should only be popping off 2 operands. "1 2 3 +" would be valid RPN, evaluating to 6. The entire stack should be reduced. This also avoids the mutation, which is a good thing, as it follows a more functional style.
class RPNCalculator
def evaluate(rpn)
a = rpn.split(' ')
array = a.inject([]) do |array, i|
if i =~ /\d+/
[*array, i.to_i]
else
[array.reduce(op(i))]
end
end
p array.pop
end
private
def op(char)
{'+'=>:+, '-'=>:-, '/'=>:/, '*'=>:*}[char]
end
end
I removed the other mutation here too, by using [*arr, value] instead of actually modifying the array.
Finally, I'd avoid printing directly from your #evaluate method and just return the number. I'd also (again) avoid the mutation:
class RPNCalculator
def evaluate(rpn)
a = rpn.split(' ')
stack = a.inject([]) do |stack, i|
if i =~ /\d+/
[*stack, i.to_i]
else
[stack.reduce(op(i))]
end
end
stack.last
end
private
def op(char)
{'+'=>:+, '-'=>:-, '/'=>:/, '*'=>:*}[char]
end
end
I renamed 'array' to 'stack', since it is a parser stack and is less generic than just array.

Resources