Reversing a list. Problem with appending to lists in Ruby - ruby

So, I've been trying to reverse a list in Ruby.
Here is the code:
def reverse(arr)
retval = []
arr.each do |e|
retval = [e] << retval[0..retval.length]
end
return retval
end
arr = [1, 2, 3, 4, 5]
res = reverse(arr)
puts "[" + res * ", " + "]"
The expected output is:
[5, 4, 3, 2, 1]
Result:
[5, 4, 3, 2, 1, ]
I believe [e] << retval[0..retval.length] does not append properly here. I think instead of, for example, [1] << [] = [1] I get [1] << [] = [1, []].
Any idea how do I fix this?
Note: Please do not suggest any other way how to reverse a list. Thank you :)

Never mind, I found the solution. It had simple yet vital mistake. I wrote + the concatenation operator instead of << and it fixed it. Here is the full code:
def reverse(arr)
retval = []
arr.each do |e|
retval = [e] + retval[0..retval.length]
end
return retval
end
arr = [1, 2, 3, 4, 5]
res = reverse(arr)
puts "[" + res * ", " + "]"

Related

Ruby - Can't replace last range's element with another one

So I want to merge overlapping ranges and it should like the following:
Input: ranges = [(1..2), (3..6), (5..8)]
Output: expected = [(1..2), (3..8)]
but when the code iterate over the intervals and goes to the else statement I just get a message "function_merge.rb:9:in block in merge': undefined methodend=' for 2..19:Range (NoMethodError)"
I tried to save merged.last.end and interval.end to variables, rewrote the if statement over couple of lines (if interval.end > merged.last.end merged.last.end = interval.end end) but all that didn't work :-(
def merge(intervals)
merged = []
intervals.sort_by! { |interval| interval.begin }
intervals.each do |interval|
if merged.empty? || merged.last.end < interval.begin
merged << interval
else
merged.last.end = interval.end if interval.end > merged.last.end
end
end
return merged
end
I don't understand why I get this error message since "end" is a range method? I just want to "update" the merged.last.end with the interval.end number.
If you have any tips how to solve it, would be very nice :-)
As Sebastian points out, Ranges are immutable. Instead of trying to change the Range you'll have to make a new one.
def merge(intervals)
merged = []
intervals.sort_by! { |interval| interval.begin }
intervals.each do |interval|
if merged.empty? || merged.last.end < interval.begin
merged << interval
else
merged[-1] = Range.new(merged.last.begin, interval.end, interval.exclude_end?)
end
end
return merged
end
It has been explained that ranges are immutable. The question implies the elements covered by the ranges are all comparable (e.g, not ['a'..'z', 1..10]). I assume that the array of ranges does not contain a mix of finite and infinite ranges.
Solution
Code
def distill(arr)
a = arr.reject { |r| r.exclude_end? ? (r.end <= r.begin) : r.end < r.begin }.
sort_by(&:begin)
return [] if a.empty?
combined = []
curr = a.shift
loop do
break (combined << curr) if a.empty?
nxt = a.shift
if nxt.begin > curr.end
combined << curr
curr = nxt
else
last = [curr, nxt].max_by { |r| [r.end, r.exclude_end? ? 0 : 1] }
curr = last.exclude_end? ? (curr.begin...last.end) :
curr.begin..last.end
end
end
end
Examples
distill [5..8, 7...9, 9..11, 1...4, 38..37]
#=> [1...4, 5..11]
distill [1.5...2.2, 2.2..3.0, 3.0...4.5, 4.7..5.3, 5.2..4.6]
#=> [1.5...4.5, 4.7..5.3]
distill ['a'..'d', 'c'..'f', 'b'..'g']
# 'a'..'g'
Explanation
See Range#exclude_end?.
The steps for the first example are as follows.
arr = [5..8, 7...9, 9..11, 1...4, 38..37]
a = arr.reject { |r| r.exclude_end? ? (r.end <= r.begin) : r.end < r.begin }.
sort_by(&:begin)
#=> [1...4, 5..8, 7...9, 9..11]
a.empty?
#=> false, so do not return
combined = []
curr = a.shift
#=> 1...4
a #=> [5..8, 7...9, 9..11]
The calculations within the loop can be best explained by salting the code with puts statements and displaying the results.
loop do
puts "a.empty? #=> true, so break #{combined + [curr]}" if a.empty?
break (combined << curr) if a.empty?
puts "a.empty? #=> false"
nxt = a.shift
puts "nxt=#{nxt}, a=#{a}"
puts "nxt.begin=#{nxt.begin} > #{curr.end} = curr.end = #{nxt.begin > curr.end}"
if nxt.begin > curr.end
combined << curr
puts "combined << #{curr} = #{combined}"
curr = nxt
puts "curr = nxt = #{curr}"
else
last = [curr, nxt].max_by { |r| [r.end, r.exclude_end? ? 0 : 1] }
puts "last=#{last}, last.exclude_end?=#{last.exclude_end?}"
curr = last.exclude_end? ? (curr.begin...last.end) :
curr.begin..last.end
puts "new value of curr=#{curr}"
end
puts
end
a.empty? #=> false
nxt=5..8, a=[7...9, 9..11]
nxt.begin=5 > 4 = curr.end = true
combined << 1...4 = [1...4]
curr = nxt = 5..8
a.empty? #=> false
nxt=7...9, a=[9..11]
nxt.begin=7 > 8 = curr.end = false
last=7...9, last.exclude_end?=true
new value of curr=5...9
a.empty? #=> false
nxt=9..11, a=[]
nxt.begin=9 > 9 = curr.end = false
last=9..11, last.exclude_end?=false
new value of curr=5..11
a.empty? #=> true, so break [1...4, 5..11]
It is sometimes convenient to be able to return an empty (but valid) range such as 38..37; one should not think of empty ranges as necessarily being an indication that something is amiss.
Alternative solution
If the ranges are all finite, as in the example, and the combined sizes of the ranges is not excessive, one could write the following.
Code
def distill(arr)
arr.flat_map(&:to_a).
uniq.
sort.
chunk_while { |x,y| y == x.next }.
map { |a| a.first..a.last }
end
Examples
distill [5..8, 7...9, 9..11, 1...4, 38..37]
#=> [1..3, 5..11]
distill ['a'..'d', 'c'..'f', 'b'..'g']
# 'a'..'g'
Explanation
The steps for the first example are as follows.
arr = [5..8, 7...9, 9..11, 1...4, 38..37]
a = arr.flat_map(&:to_a)
#=> => [5, 6, 7, 8, 7, 8, 9, 10, 11, 1, 2, 3]
b = a.uniq
#=> [5, 6, 7, 8, 9, 10, 11, 1, 2, 3]
c = b.sort
#=> [1, 2, 3, 5, 6, 7, 8, 9, 10, 11]
d = c.chunk_while { |x,y| y == x.next }
#=> #<Enumerator: #<Enumerator::Generator:0x00005c2683af8dd0>:each>
e = d.map { |a| a.first..a.last }
#=> [1..3, 5..11]
One can convert the enumerator d to an array to see the elements it will generate and pass to chunk_while's block:
d.to_a
#=> [[1, 2, 3], [5, 6, 7, 8, 9, 10, 11]]
See Enumerable#chunk_while. One could alternatively use Enumerable#slice_when.

How to convert a string of values with a range to an array in Ruby

I'm trying to parse a string of numbers and ranges joined by ",", and convert it to a numerical array. I have this as input: "1,3,6-8,5", and would like to have an array like this: [1,3,5,6,7,8].
I can only do it without the range, like this:
"12,2,6".split(",").map { |s| s.to_i }.sort #=> [2, 6, 12]
With a range, I cannot do it:
a = "12,3-5,2,6"
b = a.gsub(/-/, "..") #=> "12,3..5,2,6"
c = b.split(",") #=> ["12", "3..5", "2", "6"]
d = c.sort_by(&:to_i) #=> ["2", "3..5", "6", "12"]
e = d.split(",").map { |s| s.to_i } #>> Error
How can I do this?
I was also thinking to use the splat operator in map, but splat doesn't accept strings like [*(3..5)].
"12,3-5,2,6".
gsub(/(\d+)-(\d+)/) { ($1..$2).to_a.join(',') }.
split(',').
map(&:to_i)
#⇒ [12, 3, 4, 5, 2, 6]
"1,3,6-8,5".split(',').map do |str|
if matched = str.match(/(\d+)\-(\d+)/)
(matched[1].to_i..matched[2].to_i).to_a
else
str.to_i
end
end.flatten
or
"1,3,6-8,5".split(',').each_with_object([]) do |str, output|
if matched = str.match(/(\d+)\-(\d+)/)
output.concat (matched[1].to_i..matched[2].to_i).to_a
else
output << str.to_i
end
end
or strict
RANGE_PATTERN = /\A(\d+)\-(\d+)\z/
INT_PATTERN = /\A\d+\z/
"1,3,6-8,5".split(',').each_with_object([]) do |str, output|
if matched = str.match(RANGE_PATTERN)
output.concat (matched[1].to_i..matched[2].to_i).to_a
elsif str.match(INT_PATTERN)
output << str.to_i
else
raise 'Wrong format given'
end
end
"1,3,6-8,5".split(',').flat_map do |s|
if s.include?('-')
f,l = s.split('-').map(&:to_i)
(f..l).to_a
else
s.to_i
end
end.sort
#=> [1, 3, 5, 6, 7, 8]
"1,3,6-8,5"
.scan(/(\d+)\-(\d+)|(\d+)/)
.flat_map{|low, high, num| num&.to_i || (low.to_i..high.to_i).to_a}
#=> [1, 3, 6, 7, 8, 5]

Why doesn't String.tap return the modified string?

I'm doing
String.new.tap do |string|
polygon.points.each do |point|
x, y = point.x + (page_padding/2), point.y + (page_padding/2)
string += "#{x}, #{y} "
end
end
But it returns an empty string.
If I call
Array.new.tap do |array|
polygon.points.each do |point|
x, y = point.x + (page_padding/2), point.y + (page_padding/2)
array << "#{x}, #{y} "
end
end
It returns a modified array. Why does this not work on a string?
Using Ruby 2.4.0
+= is not a mutator: str1 += str2 creates a new string. Use << to mutate string:
string1 = 'foo'
string1.object_id
=> 70298699576220
(string1 += '2').object_id # changed
=> 70298695972240
(string1 << '2').object_id # not changed: original object has been mutated
=> 70298695972240
tap just yields self to the block, and then returns self. Since self does not change in your instance, tap just returns the original value.
Solution
String
To get a String out of your points, just use map and join :
polygon.points.map do |point|
[point.x + (page_padding/2), point.y + (page_padding/2)].join(', ')
end.join(' ')
Array
To get an aray, just use map :
polygon.points.map do |point|
[point.x + (page_padding/2), point.y + (page_padding/2)]
end
+ and <<
+ and << can both be used on Array and Strings, with a very similar behaviour for both classes. + creates a new Array or String, << mutates the original one.
s = "1 2"
#=> "1 2"
s + " 3"
#=> "1 2 3"
s
#=> "1 2"
s << " 3"
#=> "1 2 3"
s
#=> "1 2 3"
a = [1, 2]
#=> [1, 2]
a + [3]
#=> [1, 2, 3]
a
#=> [1, 2]
a << 3
#=> [1, 2, 3]
a
#=> [1, 2, 3]

Printing a readable Matrix in Ruby

Is there a built in way of printing a readable matrix in Ruby?
For example
require 'matrix'
m1 = Matrix[[1,2], [3,4]]
print m1
and have it show
=> 1 2
3 4
in the REPL instead of:
=> Matrix[[1,2][3,4]]
The Ruby Docs for matrix make it look like that's what should show happen, but that's not what I'm seeing. I know that it would be trivial to write a function to do this, but if there is a 'right' way I'd rather learn!
You could convert it to an array:
m1.to_a.each {|r| puts r.inspect}
=> [1, 2]
[3, 4]
EDIT:
Here is a "point free" version:
puts m1.to_a.map(&:inspect)
I couldn't get it to look like the documentation so I wrote a function for you that accomplishes the same task.
require 'matrix'
m1 = Matrix[[1,2],[3,4],[5,6]]
class Matrix
def to_readable
i = 0
self.each do |number|
print number.to_s + " "
i+= 1
if i == self.column_size
print "\n"
i = 0
end
end
end
end
m1.to_readable
=> 1 2
3 4
5 6
Disclaimer: I'm the lead developer for NMatrix.
It's trivial in NMatrix. Just do matrix.pretty_print.
The columns aren't cleanly aligned, but that'd be easy to fix and we'd love any contributions to that effect.
Incidentally, nice to see a fellow VT person on here. =)
You can use the each_slice method combined with the column_size method.
m1.each_slice(m1.column_size) {|r| p r }
=> [1,2]
[3,4]
Ok, I'm a total newbie in ruby programming. I'm just making my very first incursions, but it happens I got the same problem and made this quick'n'dirty approach.
Works with the standard Matrix library and will print columns formatted with same size.
class Matrix
def to_readable
column_counter = 0
columns_arrays = []
while column_counter < self.column_size
maximum_length = 0
self.column(column_counter).each do |column_element|# Get maximal size
length = column_element.to_s.size
if length > maximal_length
maximum_length = length
end
end # now we've got the maximum size
column_array = []
self.column(column_counter).each do |column_element| # Add needed spaces to equalize each column
element_string = column_element.to_s
element_size = element_string.size
space_needed = maximal_length - element_size +1
if space_needed > 0
space_needed.times {element_string.prepend " "}
if column_counter == 0
element_string.prepend "["
else
element_string.prepend ","
end
end
column_array << element_string
end
columns_arrays << column_array # Now columns contains equal size strings
column_counter += 1
end
row_counter = 0
while row_counter < self.row_size
columns_arrays.each do |column|
element = column[row_counter]
print element #Each column yield the correspondant row in order
end
print "]\n"
row_counter += 1
end
end
end
Any correction or upgrades welcome!
This is working for me
require 'matrix'
class Matrix
def print
matrix = self.to_a
field_size = matrix.flatten.collect{|i|i.to_s.size}.max
matrix.each do |row|
puts (row.collect{|i| ' ' * (field_size - i.to_s.size) + i.to_s}).join(' ')
end
end
end
m = Matrix[[1,23,3],[123,64.5, 2],[0,0,0]]
m.print
Here is my answer:
require 'matrix'
class Matrix
def to_pretty_s
s = ""
i = 0
while i < self.column_size
s += "\n" if i != 0
j = 0
while j < self.row_size
s += ' ' if j != 0
s += self.element(i, j).to_s
j += 1
end
i += 1
end
s
end
end
m = Matrix[[0, 3], [3, 4]]
puts m # same as 'puts m.to_s'
# Matrix[[0, 3], [3, 4]]
puts m.to_pretty_s
# 0 3
# 3 4
p m.to_pretty_s
# "0 3\n3 4"
You could use Matrix#to_pretty_s to get a pretty string for format.
There is no inbuilt Ruby way of doing this. However, I have created a Module which can be included into Matrix that includes a method readable. You can find this code here, but it is also in the following code block.
require 'matrix'
module ReadableArrays
def readable(factor: 1, method: :rjust)
repr = to_a.map { |row|
row.map(&:inspect)
}
column_widths = repr.transpose.map { |col|
col.map(&:size).max + factor
}
res = ""
repr.each { |row|
row.each_with_index { |el, j|
res += el.send method, column_widths[j]
}
res += "\n"
}
res.chomp
end
end
## example usage ##
class Matrix
include ReadableArrays
end
class Array
include ReadableArrays
end
arr = [[1, 20, 3], [20, 3, 19], [-32, 3, 5]]
mat = Matrix[*arr]
p arr
#=> [[1, 20, 3], [20, 3, 19], [-2, 3, 5]]
p mat
#=> Matrix[[1, 20, 3], [20, 3, 19], [-2, 3, 5]]
puts arr.readable
#=>
# 1 20 3
# 20 3 19
# -32 3 5
puts mat.readable
#=>
# 1 20 3
# 20 3 19
# -32 3 5
puts mat.readable(method: :ljust)
#=>
# 1 20 3
# 20 3 19
# -32 3 5
puts mat.readable(method: :center)
#=>
# 1 20 3
# 20 3 19
# -32 3 5
I had this problem just yet and haven't seen anyone posting it here, so I will put my solution if it helps someone. I know 2 for loops are not the best idea, but for smaller matrix it should be okay, and it prints beautifully and just how you want it, also without of use of require 'matrix' nor 'pp'
matrix = Array.new(numRows) { Array.new(numCols) { arrToTakeValuesFrom.sample } }
for i in 0..numRows-1 do
for j in 0..numCols-1 do
print " #{matrix[i][j]} "
end
puts ""
end

Distribute range in array

I need to create one array of numbers inside one range, like:
[1..5] in 10 times = [1,1,2,2,3,3,4,4,5,5]
[1..5] in 5 times = [1,2,3,4,5]
[1..5] in 3 times = [1,3,5]
def distribute(start_value, end_value, times, is_integer)
array = Array.new(times-1)
min_value = [end_value,start_value].min
max_value = [end_value,start_value].max
if max_value-min_value<times
factor = (max_value-min_value).abs/(array.size).to_f
else
factor = (max_value-min_value).abs/(array.size-1).to_f
end
for i in 0..array.size
v = [ [max_value, factor*(i+1)].min, min_value].max
is_integer ? array[i] = v.round : array[i] = v
end
start_value < end_value ? array : array.reverse
end
distribute(1, 5, 10, true)
=> [1, 1, 1, 2, 2, 3, 3, 4, 4, 4] #WRONG should be [1,1,2,2,3,3,4,4,5,5]
distribute(5, 1, 5, true)
=> [5, 4, 3, 2, 1] #OK
distribute(1, 5, 3, true)
=> [4, 5, 5] #WRONG should be [1, 3, 5]
How 'bout this:
def distribute(min,max,items)
min,max = [min,max].sort
(0...items).map {|i| (min + i * (max - min) / (items-1.0)).round}
end
Or if you really need the int/float flag:
def distribute(min,max,items,ints)
min,max = [min,max].sort
a = (0...items).map {|i| min + i * (max - min) / (items-1.0)}
ints ? a.map {|i| i.round} : a
end
And if you really need this to go in reverse if the parameters are given to you backwards:
def distribute(min,max,items,ints)
usemin,usemax = [min,max].sort
diff = usemax - usemin
a = (0...items).map {|i| usemin + i * diff / (items-1.0)}
a.map! {|i| i.round} if ints
min != usemin ? a.reverse : a
end
just a little correction... when the array_size is 0
def distribute(start_value, end_value, array_size, want_ints)
diff = 1.0 * (end_value - start_value)
n = [array_size-1, 1].max
(0..(array_size-1)).map { |i|
v = start_value + i * diff / n
want_ints ? v.round : v
}
end

Resources