Populating a ruby array reading from a file - ruby

I'm trying to populate a multi-dimensional array in ruby by reading from a file. Here is the code:
class Maze
attr_accessor :array
def initialize(filename)
handler = File.open(filename,"r")
#array = Array.new(10,Array.new(10))
n = 0;
i = 0;
while(line = handler.gets) do
i = 0
line.chomp.each_char do |char|
p char
#array[n][i] = char
i += 1
end #iterator over character in every line
n += 1
end #iterator over file lines
handler.close
end #constructor
end #class
a = Maze.new("maze.txt")
p a.array
Here is the content of the file:
##########
# #
# #### #
# # # #
# # # ?
# # # #
# # #
# #### #
# #
##########
However this line of code (the last line of code)
p a.array
Will print an array 10 by 10 but full of "#" symbols. No spaces nor "?". Something important to note is that
p char
In the block where I assign the array with values prints the right characters. It prints spaces at the right time and question marks etc... I know it's something very simple but it's bugging me and I think I need a fresh pair of eyes to look over it
Why is it that the array has only "#" symbols. Why aren't all the other characters such as " ", "#", "?" in my array? Is the assigning in my code wrongly coded

I'd write the code differently. It's not quite clear what you're trying to do, so here's two different things I'd do:
class Maze
attr_accessor :array
def initialize(filename)
#array = []
File.foreach(filename) do |line|
#array << line.chomp
end
end
end
a = Maze.new("maze.txt")
p a.array
puts a.array
Which outputs:
["##########", "# #", "# #### #", "# # # #", "# # # ?", "# # # #", "# # #", "# #### #", "# #", "##########"]
##########
# #
# #### #
# # # #
# # # ?
# # # #
# # #
# #### #
# #
##########
Or:
class Maze
attr_accessor :array
def initialize(filename)
#array = []
File.foreach(filename) do |line|
#array << line.chomp.split('')
end
end
end
a = Maze.new("maze.txt")
p a.array
puts a.array.map(&:join)
which outputs:
[["#", "#", "#", "#", "#", "#", "#", "#", "#", "#"], ["#", " ", " ", " ", " ", " ", " ", " ", " ", "#"], ["#", " ", "#", "#", "#", "#", " ", " ", " ", "#"], ["#", " ", "#", " ", " ", "#", " ", " ", " ", "#"], ["#", " ", "#", " ", " ", "#", " ", " ", " ", "?"], ["#", " ", "#", " ", " ", "#", " ", " ", " ", "#"], ["#", " ", "#", " ", " ", " ", " ", " ", " ", "#"], ["#", " ", "#", "#", "#", "#", " ", " ", " ", "#"], ["#", " ", " ", " ", " ", " ", " ", " ", " ", "#"], ["#", "#", "#", "#", "#", "#", "#", "#", "#", "#"]]
##########
# #
# #### #
# # # #
# # # ?
# # # #
# # #
# #### #
# #
##########
The primary problem in your code is you're using array references when you assign to the sub-arrays, which results in them all pointing to the same memory. I think you did that because you're used to another language where you have to predefine your array size. Ruby is more friendly than that, allowing us to append to an array easily. I used to << operator, but other methods exist to do the same thing; << is easier to see and understand.
Instead of opening the file, then using a while loop to iterate over it, it's more idiomatic to use foreach, which accomplishes the same thing and automatically closes the file when the block exits.
Also, instead of iterating over each line's characters, simply split the line using split('') which will return an array of characters. Append that to #array and move on.

The culprit is the way you are initializing the array in this line:
#array = Array.new(10,Array.new(10))
this is creating an array of 10 elements and it is initializing all of them with a pointer to another array of 10 elements. All the 10 elements will be sharing the same array. You can see a simplified example in here:
b = Array.new(3,Array.new(3))
b[0][0] = '00'
puts b # you'll see '00' printed 3 times!
One way to fix your program will be to create the array of rows first, then then initialize each row to a different array:
#array = Array.new(10)
for i in 0..9
# initialize each row to a unique array
#array[i] = Array.new(10)
end
# the rest of your code

Related

How to remove quotes from strings in an array?

Assuming I have the following input:
names = ["\"Петр Сергеевич\"", "\"Курсатов Роман\"", "\" \"", "\"Павел2 Олегович\"", "\"Илья иванович\"", "\" \""]
Each whitespace is actually a non-breaking space (U+00A0).
How do I remove \" in pure ruby, so the following is true:
p names
=> ["Петр Сергеевич", "Курсатов Роман", " ", "Павел2 Олегович", "Илья иванович", " "]
I tried:
names.map { |i| i.gsub(/[\"]/, "")}.map(&:inspect)
names.map { |i| i.delete('\\"')}.map(&:inspect)
names.map { |i| i.gsub('\\"', '')}.map(&:inspect)
Nothing seems to work.
string.delete("\"")
# => " "
or
string.tr("\"", "")
# => " "

ruby multiline scan between ; and negate?

I'm trying to match text between ;-.
I used:
inputx.scan(/;-.+?\n[^\n]*;-/)
but it doesn't work.
My text is:
baseball;-1
norm;4
dad;3
soda;1
robot;-8
mmm;3
fly;-1
cat;4
bird;4
dragon;6
mor;-1
I need to separate the text between ;-.
For example, this is the first element of the resulting array:
baseball;-1
norm;4
dad;3
soda;1
robot;-8
And this is second:
fly;-1
cat;4
bird;4
dragon;6
mor;-1
You may use a regex that will match any line that ends with - and 1 or more digits, and then matches any text up to the first line that ends with - and 1 or more digits:
/.*-\d+$(?m:.*?-\d+$)/
See the Rubular demo
Details:
.*-\d+$ - any 0+ chars other than line breaks, followed with - and 1+ digits
(?m:.*?-\d+$) - a modifier group where . matches line breaks matching:
.*? - any 0+ chars, as few as possible
- - a hyphen
\d+ - 1 or more digits
$ - end of line.
You can use Array#split twice, the first to split by lines, and the second to split based on the presence of either ; or ;- (using the pattern /;-?/)
The pattern /;-?/ matches a semicolon followed by an optional -.
inputx.split("\n").map{|s| s.split(/;-?/)}
#=> [[" baseball", "1"], [" norm", "4"], [" dad", "3"], [" soda", "1"], [" robot", "8"], [" mmm", "3"], [" fly", "1"], [" cat", "4"], [" bird", "4"], [" dragon", "6"], [" mor", "1"]]
A pattern with scan or split results in a regex that is needlessly complicated because it's not the best tool in the box for the problem.
I'd use something like this:
text = <<EOT
baseball;-1
norm;4
dad;3
soda;1
robot;-8
mmm;3
fly;-1
cat;4
bird;4
dragon;6
mor;-1
EOT
ary = [[]]
text.lines.each do |l|
if l[';-'] ... l[';-']
ary.last << l
else
ary << []
end
end
ary
# => [[" baseball;-1\n",
# " norm;4\n",
# " dad;3\n",
# " soda;1\n",
# " robot;-8\n"],
# [" fly;-1\n",
# " cat;4\n",
# " bird;4\n",
# " dragon;6\n",
# " mor;-1\n"]]
If you don't want trailing new-lines:
ary = [[]]
text.lines.map(&:chomp).each do |l|
if l[';-'] ... l[';-']
ary.last << l
else
ary << []
end
end
ary
# => [[" baseball;-1", " norm;4", " dad;3", " soda;1", " robot;-8"],
# [" fly;-1", " cat;4", " bird;4", " dragon;6", " mor;-1"]]
If you don't want the whitespace surrounding each element:
ary = [[]]
text.lines.map(&:strip).each do |l|
if l[';-'] ... l[';-']
ary.last << l
else
ary << []
end
end
ary
# => [["baseball;-1", "norm;4", "dad;3", "soda;1", "robot;-8"],
# ["fly;-1", "cat;4", "bird;4", "dragon;6", "mor;-1"]]
How does this work? The .. and ... operator changes meaning depending on whether it's used in the context of a Range, or in an if condition. .. is called a "flip-flop" operator, which changes state when the first condition is met. It will begin returning true at that point, and will continue to do so until the second condition is met, at which point it begins returning false again. That makes it easy to look for something, then begin acting on subsequent lines until the second condition occurs.
Normally we'd use different conditions, such as searching for "begin" and "end" in a block of lines in a file. In this case though, we needed it to not immediately toggle since both the start and end condition were the same, which is where ... comes it. It waits one loop before testing for the second condition, allowing this code to continue, find the next lines until the "closing" ';-'.
I have to say, this data set is one of the weirdest I've ever seen. (The weirdest was some binary data for the address book out of an old email program years ago). I'd be concerned about the process that's generating it, and if that generation was under my control I'd change it to use something more standard.
We can use Enumerable#chunk and Ruby's flip-flop operator. This does not require the use of a regular expression. str is the string given by the OP.
arr = str.lines.chunk do |line|
true if line.include?('-') ... line.include?('-')
end.select(&:first).map { |_,a| a.join }
#=> ["baseball;-1\nnorm;4\ndad;3\nsoda;1\nrobot;-8\n",
# "fly;-1\ncat;4\nbird;4\ndragon;6\nmor;-1\n"]
arr.each { |s| puts "\n"; puts s }
baseball;-1
norm;4
dad;3
soda;1
robot;-8
fly;-1
cat;4
bird;4
dragon;6
mor;-1
It is necessary to use three (not two) dots in the flip-flop expression (search for "three dot" in the reference given above).

Replacing "\n" but not "\n\n"

How to replace "\n" but not "\n\n" etc. with " \n"?
text1 = "Hello\nWorld"
text1.sub! "\n", " \n"
=> "Hello \nWorld"
text2 = "Hello\n\nWorld"
text2.sub! "\n\n", " \n"
=> "Hello \n\nWorld"
SHOULD BE: => "Hello\n\nWorld"
You can use the regular expression /(?<!\n)\n(?!\n)/, which matches a \n only when it's not prefixed with another \n and not followed by a \n.
text1 = "Hello\nWorld"
# => "Hello\nWorld"
text1.sub /(?<!\n)\n(?!\n)/, " \n"
# => "Hello \nWorld"
text2 = "Hello\n\nWorld"
# => "Hello\n\nWorld"
text2.sub /(?<!\n)\n(?!\n)/, " \n"
# => "Hello\n\nWorld"
Here's another way:
r = /\n+/
"Hello\nWorld".sub(r) { |s| (s.size==1) ? " \n" : s }
#=> "Hello \nWorld"
"Hello\n\nWorld".sub(r) { |s| (s.size==1) ? " \n" : s }
#=> "Hello\n\nWorld"
and one more:
h = Hash.new { |h,k| h[k] = k }.update("\n"=>" \n")
#=> {"\n"=>" \n"}
"Hello\nWorld".sub(r,h)
#=> "Hello \nWorld"
"Hello\n\nWorld".sub(r,h)
#=> "Hello\n\nWorld"
In the latter method, each string of one or more consecutive newline characters is passed to the hash. If it's a single newline, "\n" is replaced with h["\n"] #=> " \n". If it's two or more newlines, say s = "\n\n", and h does not have a key equal to s (initally it won't), the key-value pair s=>s will be added to h (because of the default value defined for the hash) and s will be replaced by itself.
Another solution you could use:
string = "Hello\nWorld"
string.split("\n") == string.split("\n").reject(&:empty?) ? string.sub!("\n", " \n") : string
#=> "Hello \nWorld"
string = "Hello\n\nWorld"
string.split("\n") == string.split("\n").reject(&:empty?) ? string.sub!("\n", " \n") : string
#=> "Hello\n\nWorld"

Creating matrix with `Array.new(n, Array.new)`

I created an array by doing the following:
#gameboard = Array.new(3, Array.new(3, " "))
I tried to assign a value like so, and I got this:
#gameboard[0][2] = "X"
#gameboard #=> [[" ", " ", "X"], [" ", " ", "X"], [" ", " ", "X"]]
When I declare the array differently,
#gameboard = [[" ", " ", " "], [" ", " ", " "], [" ", " ", " "]]
I get this result:
#gameboard[0][2] = "X"
#gameboard # => [[" ", " ", "X"], [" ", " ", " "], [" ", " ", " "]]
Why does using the Array.new method illicit different behavior when assigning values to the array?
Follow the code:
#gameboard = Array.new(3, Array.new(3, " "))
#gameboard.map { |a| a.object_id }
# => [76584030, 76584030, 76584030]
means new(size=0, obj=nil) method creates an array of size, having the same ob.
But new(size) {|index| block } method works in a different way; it creates an array of size, having different obs.
See the code below:
#gameboard = Array.new(3) { Array.new(3, " ") }
#gameboard.map { |a| a.object_id }
# => [75510080, 75509920, 75509540]
The above is the same as your second code example:
#gameboard = [[" ", " ", " "], [" ", " ", " "], [" ", " ", " "]]
#gameboard.map { |a| a.object_id }
# => [80194090, 80193400, 80193080]
If you change or update the the value at index 1 of the first element array of #gameboard, it wouldn't affect all other inner array elements.
#gameboard = Array.new(3) { Array.new(3, " ") }
#gameboard[0][1] = 2
#gameboard
# => [[" ", 2, " "], [" ", " ", " "], [" ", " ", " "]]
The Array constructor will not duplicate the object you passed; it will reuse the object to fill the array.
Use the block form in order to create a new object for each index:
#gameboard = Array.new(3) { |i| Array.new(3) { |j| " " } }

How to add a postfix in incremental order to a printed string in Ruby?

For microarray data processing, I need to make a list of gene names from 1 to 654, like Gene_1 ... Gene_654.
My simple Ruby code produces the following:
1.upto(654).each { |i| print "Gene" }
The result is:
GeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGene
GeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGene
GeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGene
GeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGene
GeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGene
GeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGene
GeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGene
GeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGene
GeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGene
..................................
GeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGeneGene=> 1
irb(main):008:0>
How do I add a "postfix _#" in sequential incremental order to a printed string and put them in a column, like:
Gene_1
Gene_2
::::::
Gene_654
1.upto(654).each { |i| printf "%8s\t", "Gene_#{i}" }
Source: http://www.ruby-doc.org/core-2.0.0/Kernel.html#format-method
Edited to conform to the new requirements:
1.upto(654).each { |i| puts "Gene_#{i}" }
--output:--
Gene_1
Gene_2
...
Geen_654
I'd use:
str = 'Gene_0'
654.times { puts str.next! }
Which outputs:
Gene_1
...
Gene_654
If you need the text output to the same width, perhaps because you're going to append information to each line, use some formatting:
str = 'Gene_0'
654.times { puts '%8s ' % str.next! }
# >> Gene_1
...
# >> Gene_9
# >> Gene_10
...
# >> Gene_99
# >> Gene_100
...
# >> Gene_654
If you need columns across a page:
str = 'Gene_0'
654.times { print '%8s ' % str.next! }
puts
Which spaces them out in 8-space-wide columns.
By default %8s uses right alignment, which isn't always what you want. Instead you can use %-8s for left-alignment.
You can build an array containing the column headings:
str = 'Gene_0'
columns = []
654.times { columns << '%-8s' % str.next! }
puts columns.join(' ')
You could even use something like inject:
str = 'Gene_0'
columns = []
(1..654).inject(columns) { |a, i| a.push('%-8s' % str.next!) }
puts columns.join(' ')
But that starts to add code that doesn't really help.
The OP asked:
...how to add " " to the result...
The output above doesn't make it easy to see the whitespace automatically appended to the output by '%8s ', so I tweaked the format-string to make it more obvious by wrapping the output in double-quotes:
str = 'Gene_0'
654.times { puts '"%8s "' % str.next! }
And here's the corresponding output, trimmed down to show how the format string maintains the column width as the string value increments:
# >> " Gene_1 "
...
# >> " Gene_9 "
# >> " Gene_10 "
...
# >> " Gene_99 "
# >> "Gene_100 "
...
# >> "Gene_654 "
If you want all the white-space to occur at the end of the column, use a left-alignment:
str = 'Gene_0'
654.times { puts '"%-8s "' % str.next! }
Which outputs:
# >> "Gene_1 "
...
# >> "Gene_9 "
# >> "Gene_10 "
...
# >> "Gene_99 "
# >> "Gene_100 "
...
# >> "Gene_654 "

Resources