What are the values in the Array cells_per_number refering to - ruby

This was a solution to a problem on GitHub.I was looking over the solution and I was wondering what the numbers in the array are referring to
LED Clock: You are (voluntarily) in a room that is completely dark except for
the light coming from an old LED digital alarm clock. This is one of those
clocks with 4 seven segment displays using an HH:MM time format. The clock is
configured to display time in a 24 hour format and the leading digit will be
blank if not used. What is the period of time between when the room is at its
darkest to when it is at its lightest?
def compute_brightness(units)
cells_per_number = [ 6, 2, 5, 5, 4, 5, 6, 3, 7, 6 ]
units.each_with_object({}) do |t, hash|
digits = t.split('')
hash[t] = digits.map { |d| cells_per_number[d.to_i] }.reduce(:+)
end
end

The numbers refer to the number of segments that are "on" when displaying the corresponding digit. When displaying the number "0," six segments are "on" (all except the center segment), so the number at index 0 is 6. When displaying 1, only two segments are "on," so the number at index 1 is 2. You get the idea.
_ _ _
0) | | = 6 1) | = 2 2) _| = 5 3) _| = 5
|_| | |_ _|
_ _ _
4) |_| = 4 5) |_ = 5 6) |_ = 6 7) | = 3
| _| |_| |
_ _
8) |_| = 7 9) |_| = 6
|_| _|

#Jordan has answered your specific question, but the code isn't a complete solution to the stated problem. Here's a way of doing that.
lpd = { 0=>6, 1=>2, 2=>5, 3=>5, 4=>4, 5=>5, 6=>6, 7=>3, 8=>7, 9=>6 }
def min_leds(lpd, range)
leds(lpd, range).min_by(&:last).first
end
def max_leds(lpd, range)
leds(lpd, range).max_by(&:last)
end
def leds(lpd, range)
lpd.select { |k,_| range.cover?(k) }
end
darkest =
[ *[
[max_leds(lpd, (1..1)), max_leds(lpd, (0..2))],
[[0,0], max_leds(lpd, (1..9))]
].max_by { |(_,a), (_,b)| a+b },
max_leds(lpd, (0..5)),
max_leds(lpd, (0..9))
].transpose.first.join.insert(2,':')
#=> "10:08"
lightest = [0, min_leds(lpd, (1..9)),
min_leds(lpd, (0..5)),
min_leds(lpd, (0..9))
].join.insert(2,':')
#=> "0111"
To make the solution more realistic, an array (possibly empty) of the locations of the burnt-out leds should be passed to the method.

Related

The Number of the Smallest Unoccupied Chair solution in ruby

I am learning ruby and have started practicing problems from leetcode, yesterday I have a problem which I am not able to solve since yesterday.
I tried hard doing that in ruby, but not able to do yet.
I tried this
def give_chair(a)
u = a.uniq
d = []
u.each do |i|
d << i if a.count(i) == 1
end
d
end
def smallest_chair(times, target_friend)
friend = times[target_friend]
sorted_arrival_times = times.sort
leave_time_chair = {}
chair = 0
chairs_array = []
uniq_chars_array = []
sorted_arrival_times.each do |i|
if leave_time_chair.keys.select { |k| i[0] > k }.empty?
leave_time_chair[i[1]] = chair
chair+=1
else
all_keys = leave_time_chair.keys.select { |k| k <= i[0] }
chairs_array = leave_time_chair.values
p chairs_array
if give_chair(chairs_array).empty?
leave_time_chair[i[1]] = chairs_array.sort.first
else
leave_time_chair[i[1]] = give_chair(chairs_array).sort.first
end
end
if i == friend
p leave_time_chair
return leave_time_chair[i[1]]
end
end
end
# a = [[33889,98676],[80071,89737],[44118,52565],[52992,84310],[78492,88209],[21695,67063],[84622,95452],[98048,98856],[98411,99433],[55333,56548],[65375,88566],[55011,62821],[48548,48656],[87396,94825],[55273,81868],[75629,91467]]
# b = 6
# p smallest_chair(a, b)
but it is failing for some test cases.
I am not able to create an algorithm for it.
Question = https://leetcode.com/problems/the-number-of-the-smallest-unoccupied-chair
My approach:
First I sort the times array according to arrival times.
Then I iterate over each array element
Now if the arrival time is greater than all the previous leaving time (I am creating key, value pair of leaving time and chair given) then I add a new key=> value pair in leave_time_chair (which is hash) and where key is the leaving time of current array and value is the chair given to it.
Then I increment the chair (chair+=1)
Else I get all those leaving time which are equal or less than the current arrival time (all_keys = leave_time_chair.keys.select { |k| k <= i[0] })
Then I get all the chairs of those times
Now I have all the chairs like this => [0, 0, 1, 2] so I wrote one function [ give_chair(a) ] which gives me those elements which are not repeated. like this => [1, 2] and then I assign the shortest number (chair) to the leaving time of current array. and so on...
Then if my current array is equal to the friend I return the chair of it. by extracting it from a hash (leave_time_chair) return leave_time_chair[i[1]]
my naive solution (not optimize yet), basically my idea that i flat-map the input array into an array with each element is a pair [time arrive/leave, friend index], then i will sort that array base on time (don't care arrive or leave), if both pair have same time, then i'll compare the arrive time of fiend index. Finally i loop through the sorted array and evaluate minimum free chair index each step, whenever i meet the targetFriend i return that minimum free chair index.
# #param {Integer[][]} times
# #param {Integer} target_friend
# #return {Integer}
def smallest_chair(times, target_friend)
# times = [[1,2],[4,7],[2,4]]
# targetFriend = 1
sit_times = times.each_with_index.inject([]) { |combi, (time, index)|
combi += [[time.first, index], [time.last, index]]
}
# [[1, 0], [2, 0], [4, 1], [7, 1], [2, 2], [4, 2]]
sit_times.sort! {|x, y|
c = x[0] <=> y[0]
# [[1, 0], [2, 0], [2, 2], [4, 1], [4, 2], [7, 1]]
c = times[x[1]][0] <=> times[y[1]][0] if c == 0
# [[1, 0], [2, 0], [2, 2], [4, 2], [4, 1], [7, 1]]
c
}
chairs = {} # to mark time of friend
occupied = Array.new(times.size, 0) # occupied chair: 1, otherwise: 0
min_free = 0 # current minimum not occupied chair
sit_times.each do |time, friend_index|
if target_friend == friend_index # check
return min_free
end
sit = chairs[friend_index]
if sit # leave
occupied[sit] = 0
chairs[friend_index] = nil
min_free = sit if min_free > sit
else # arrive
chairs[friend_index] = min_free
occupied[min_free] = 1
min_free += 1 until occupied[min_free] == 0 # re-calculate
end
end
end
Note: the code pass test cases on leetcode but the performance is not good.
update
here is the better version, using 3 priority queues, one for arrive times, one for leave times and the last for chair.
PriorityQueue class
class PriorityQueue
attr_reader :length
def initialize(opts={}, &comparator)
order_opt = opts.fetch(:order, :asc)
#order = order_opt == :asc ? -1 : 1
#comparator = comparator
#items = [nil]
#length = 0
end
def push(item)
#items << item
#length += 1
swim(#length)
true
end
def pop
return nil if empty?
swap(1, #length) if #length > 1
#length -= 1
sink(1) if #length > 0
#items.pop
end
def empty?
#length == 0
end
def swap(i, j)
temp = #items[i]
#items[i] = #items[j]
#items[j] = temp
end
def in_order?(i, j)
x = #items[i]
y = #items[j]
order = #comparator.nil? ? (x <=> y) : #comparator.call(x, y)
order == #order
end
def swim(from)
while (up = from / 2) >= 1
break if in_order?(up, from)
swap(up, from)
from = up
end
end
def sink(from)
while (down = from * 2) <= #length
down += 1 if down < #length && in_order?(down + 1, down)
break if in_order?(from, down)
swap(down, from)
from = down
end
end
end
smallest_chair with priority queues (note that i found using sort is faster than a queue for arrive times, but basically the idea is same)
def smallest_chair_pq(times, target_friend)
# a_pq = PriorityQueue.new { |x, y|
# x[0] <=> y[0]
# }
#
# times.each do |t|
# a_pq.push(t)
# end
# sort arrive times is faster than a priority queue
a_pq = times.sort_by(&:first).reverse
# leave times queue
l_pq = PriorityQueue.new { |x, y|
c = x[0] <=> y[0]
c = x[1] <=> y[1] if c == 0
c
}
# chair-indexes queue
# consider case a friend come in at arrive-time at1
# and there's a range chairs with leave times in range lm <= at1 <= ln
# that mean that friend could pick one of those chairs
# and according this problem requirement, should pick the minimun chair index
c_pq = PriorityQueue.new
target_time = times[target_friend][0]
last_chair_index = 0
until a_pq.empty?
a_top = a_pq.pop
arrive_time = a_top.first
if l_pq.empty?
return 0 if arrive_time == target_time
l_pq.push([a_top.last, 0])
else
l_top = l_pq.pop
if l_top.first <= arrive_time
c_pq.push(l_top.last)
until (l_ntop = l_pq.pop).nil? || arrive_time < l_ntop.first
c_pq.push(l_ntop.last)
end
l_pq.push(l_ntop) unless l_ntop.nil?
min_chair_index = c_pq.pop
return min_chair_index if arrive_time == target_time
l_pq.push([a_top.last, min_chair_index])
else
unless c_pq.empty?
chair_index = c_pq.pop
return chair_index if arrive_time == target_time
l_pq.push([a_top.last, chair_index])
else
last_chair_index += 1
return last_chair_index if arrive_time == target_time
l_pq.push([a_top.last, last_chair_index])
end
l_pq.push(l_top)
end
end
end
end

Minimax algorithm in Ruby in Object-Oriented way

I am trying to implement minimax algorithm for a tic tac toe game in an object-oriented way. I was a bit stuck with how to deal with reverting the state for my board object once the algorithm determined the best move. When running the program, I have noticed that minimax method operated on the current board object which is not ideal.
I added a method to undo the move done by the minimax method: board.[]=(empty_square, Square::INITIAL_MARKER)
I have noticed the algorithm makes the wrong choice. Here, X is a player and O is a computer. If this is the state of the board:
| |
| |
| |
-----+-----+-----
| |
| X |
| |
-----+-----+-----
| |
| | O
| |
When the player X makes a move and picks square 2, minimax (computer, O) will choose 7 instead of 8 which would be a better choice:
| |
| X |
| |
-----+-----+-----
| |
| X |
| |
-----+-----+-----
| |
O | | O
| |
Due to my inexperience, I am a little bit lost on how to proceed and would appreciate any guidance!
Here is the minimax method:
def minimax
best_move = 0
score_current_move = nil
best_score = -10000 if #current_marker == COMPUTER_MARKER
best_score = 10000 if #current_marker == HUMAN_MARKER
board.unmarked_keys.each do |empty_square|
board.[]=(empty_square, #current_marker)
if board.full?
score_current_move = 0
elsif board.someone_won?
score_current_move = -1 if board.winning_marker == HUMAN_MARKER
score_current_move = 1 if board.winning_marker == COMPUTER_MARKER
else
alternate_player
score_current_move = minimax[0]
end
if ((#current_marker == COMPUTER_MARKER) && (score_current_move >= best_score))
best_score = score_current_move
best_move = empty_square
elsif ((#current_marker == HUMAN_MARKER) && (score_current_move <= best_score))
best_score = score_current_move
best_move = empty_square
end
board.[]=(empty_square, Square::INITIAL_MARKER)
end
[best_score, best_move]
end
I see no particular advantage here to defining any classes at all. There is only one board and only two players (the machine and the human) who operate quite differently.
Main method
Next I will write the main method, which depends on several helper methods, all of which could be private.
def play_game(human_moves_first = true)
raise ArgumentError unless [true, false].include?(human_moves_first)
human_marker, machine_marker =
human_moves_first ? ['X', 'O'] : ['O', 'X']
board = Array.new(9)
if human_moves_first
display(board)
human_to_move(board, 'X')
end
loop do
display(board)
play = machine_best_play(board, machine_marker)
board[play] = machine_marker
display(board)
if win?(board)
puts "Computer wins"
break
end
if tie?(board)
puts "Tie game"
break
end
human_to_move(board, human_marker)
if tie?(board)
puts "Tie game"
break
end
end
end
As you see I have provided a choice of who starts, the machine or the human.
Initially, board is an array of 9 nils.
The method simply loops until a determination is made as to whether the machine wins or there is a tie. As we know, the machine, acting logically, cannot lose. In each pass of the loop the machine makes a mark. If that results in a win or a tie the game is over; else the human is called upon to make a mark.
Before considering the method machine_best_play, let's consider a few simple helper method that are needed.
Simple helper methods
I will demonstrate these methods with board defined as follows:
board = ['X', 'O', 'X',
nil, 'O', nil,
nil, nil, 'X']
Note that while the human refers to the nine locations as 1 through 9, internally they they are represented as indices of board, 0 through 8.
Determine unmarked cells
def unmarked_cells(board)
board.each_index.select { |i| board[i].nil? }
end
unmarked_cells(board)
#=> [3, 5, 6, 7]
Ask human to make a selection
def human_to_move(board, marker)
loop do
puts "Please mark '#{marker}' in an unmarked cell"
cell = gets.chomp
if (n = Integer(cell, exception: false)) && n.between?(1, 9)
n -= 1 # convert to index in board
if board[n].nil?
board[n] = marker
break
else
puts "That cell is occupied"
end
else
puts "That is not a number between 1 and 9"
end
end
end
human_to_move(board, 'O')
Please mark an 'O' in an unmarked cell
If cell = gets.chomp #=> "6" then
board
#=> ["X", "O", "X", nil, "O", "O", nil, nil, "X"]
For the following I have set board to its original value above.
Display the board
def display(board)
board.each_slice(3).with_index do |row, idx|
puts " | |"
puts " #{row.map { |obj| obj || ' ' }.join(' | ')}"
puts " | |"
puts "-----+-----+-----" unless idx == 2
end
end
display(board)
| |
X | O | X
| |
-----+-----+-----
| |
| O |
| |
-----+-----+-----
| |
| | X
| |
Determine if the last move (by the machine or human) wins
WINNING_CELL_COMBOS = [
[0,1,2], [3,4,5], [6,7,8], [0,3,6], [1,4,7], [2,5,8], [0,4,8], [2,4,6]
]
def win?(board)
WINNING_CELL_COMBOS.any? do |arr|
(f = arr.first) != nil && arr == [f,f,f]
end
end
win? board
#=> false
win? ['X', nil, 'O', 'nil', 'X', 'O', nil, nil, 'X']
#=> true
win? ['X', nil, 'O', 'nil', 'X', 'O', 'X', nil, 'O']
#=> true
Determine if game ends in a tie
def tie?(board)
unmarked_cells(board).empty?
end
tie?(board)
#=> false
tie? ['X', 'X', 'O', 'O', 'X', 'X', 'X', 'O', 'O']
#=> true
Note unmarked_cells.empty? can be replaced with board.all?.
Determine machine's best play using minimax algorithm
MACHINE_WINS = 0
TIE = 1
MACHINE_LOSES = 2
NEXT_MARKER = { "X"=>"O", "O"=>"X" }
def machine_best_play(board, marker)
plays = open_cells(board)
plays.min_by |play|
board_after_play = board.dup.tap { |a| a[play] = marker }
if machine_wins?(board_after_play, marker)
MACHINE_WIN
elsif plays.size == 1
TIE
else
human_worst_outcome(board_after_play, NEXT_MARKER[marker])
end
end
end
This requires two more methods.
Determine machine's best worst outcome for current state of board
def machine_worst_outcome(board, marker)
plays = open_cells(board)
plays.map |play|
board_after_play = board.dup.tap { |a| a[play] = marker }
if win?(board_after_play)
MACHINE_WINS
elsif plays.size == 1
TIE
else
human_worst_outcome(board_after_play, NEXT_MARKER[marker])
end
end.min
end
Determine human's best worst outcome for current state of board assuming
human also plays a minimax strategy
def human_worst_outcome(board, marker)
plays = open_cells(board)
plays.map |play|
board_after_play = board.dup.tap { |a| a[play] = marker }
if win?(board_after_play)
MACHINE_LOSES
elsif plays.size == 1
TIE
else
machine_worst_outcome(board_after_play, NEXT_MARKER[marker])
end
end.max
end
Notice that the human maximizes the worst outcome from the machine's perspective whereas the machine minimizes its worst outcome.
Almost there
All that remains is to quash any bugs that are present. Being short of time at the moment I will leave that to you, should you wish to do so. Feel free to edit my answer to make any corrections.

Projecting an N-dimensional array to 1-d

I have an n-dimensional array I'd like to display in a table. Something like this:
#data = [[1,2,3],[4,5,6],[7,8,9]]
#dimensions = [{:name => "speed", :values => [0..20,20..40,40..60]},
{:name => "distance", :values => [0..50, 50..100, 100..150]}]
And I'd like the table to end up looking like this:
speed | distance | count
0..20 | 0..50 | 1
0..20 | 50..100 | 2
0..20 | 100..150 | 3
20..40 | 0..50 | 4
20..40 | 50..100 | 5
20..40 | 100..150 | 6
40..60 | 0..50 | 7
40..60 | 50..100 | 8
40..60 | 100..150 | 9
Is there a pretty way to pull this off? I have a working solution that I'm actually kind of proud of; this post is a bit humble-brag. However, it does feel overly complicated, and there's no way I or anyone else is going to understand what's going on later.
[nil].product(*#dimensions.map do |d|
(0...d[:values].size).to_a
end).map(&:compact).map(&:flatten).each do |data_idxs|
row = data_idxs.each_with_index.map{|data_idx, dim_idx|
#dimensions[dim_idx][:values][data_idx]
}
row << data_idxs.inject(#data){|data, idx| data[idx]}
puts row.join(" |\t ")
end
What about this?
first, *rest = #dimensions.map {|d| d[:values]}
puts first
.product(*rest)
.transpose
.push(#data.flatten)
.transpose
.map {|row| row.map {|cell| cell.to_s.ljust 10}.join '|' }
.join("\n")
Bent, let me first offer a few comments on your solution. (Then I will offer an alternative approach that also uses Array#product.) Here is your code, formatted to expose the structure:
[nil].product(*#dimensions.map { |d| (0...d[:values].size).to_a })
.map(&:compact)
.map(&:flatten)
.each do |data_idxs|
row = data_idxs.each_with_index.map
{ |data_idx, dim_idx| #dimensions[dim_idx][:values][data_idx] }
row << data_idxs.inject(#data) { |data, idx| data[idx] }
puts row.join(" |\t ")
end
I find it very confusing, in part because of your reluctance to define intermediate variables. I would first compute product's argument and assign it to a variable x. I say x because it's hard to come up with a good name for it. I would then assign the results of product to another variable, like so: y = x.shift.product(x) or (if you don't want x modified) y = x.first.product(x[1..-1). This avoids the need for compact and flatten.
I find the choice of variable names confusing. The root of the problem is that #dimensions and #data both begin with d! This problem would be diminished greatly if you simply used, say, #vals instead of #data.
It would be more idiomatic to write data_idxs.each_with_index.map as data_idxs.map.with_index.
Lastly, but most important, is your decision to use indices rather than the values themselves. Don't do that. Just don't do that. Not only is this unnecessary, but it makes your code so complex that figuring it out is time-consuming and headache-producing.
Consider how easy it is to manipulate the data without any reference to indices:
vals = #dimensions.map {|h| h.values }
# [["speed", [0..20, 20..40, 40..60 ],
# ["distance", [0..50, 50..100, 100..150]]
attributes = vals.map(&:shift)
# ["speed", "distance"]
# vals => [[[0..20, 20..40, 40..60]],[[0..50, 50..100, 100..150]]]
vals = vals.flatten(1).map {|a| a.map(&:to_s)}
# [["0..20", "20..40", "40..60"],["0..50", "50..100", "100..150"]]
rows = vals.first.product(*vals[1..-1]).zip(#data.flatten).map { |a,d| a << d }
# [["0..20", "0..50", 1],["0..20", "50..100", 2],["0..20", "100..150", 3],
# ["20..40", "0..50", 4],["20..40", "50..100", 5],["20..40", "100..150", 6],
# ["40..60", "0..50", 7],["40..60", "50..100", 8],["40..60", "100..150", 9]]
I would address the problem in such a way that you could have any number of attributes (i.e., "speed", "distance",...) and the formatting would dictated by the data:
V_DIVIDER = ' | '
COUNT = 'count'
attributes = #dimensions.map {|h| h[:name]}
sd = #dimensions.map { |h| h[:values].map(&:to_s) }
fmt = sd.zip(attributes)
.map(&:flatten)
.map {|a| a.map(&:size)}
.map {|a| "%-#{a.max}s" }
attributes.zip(fmt).each { |a,f| print f % a + V_DIVIDER }
puts COUNT
prod = (sd.shift).product(*sd)
flat_data = #data.flatten
until flat_data.empty? do
prod.shift.zip(fmt).each { |d,f| print f % d + V_DIVIDER }
puts (flat_data.shift)
end
If
#dimensions = [{:name => "speed", :values => [0..20,20..40,40..60] },
{:name => "volume", :values => [0..30, 30..100, 100..1000]},
{:name => "distance", :values => [0..50, 50..100, 100..150] }]
this is displayed:
speed | volume | distance | count
0..20 | 0..30 | 0..50 | 1
0..20 | 0..30 | 50..100 | 2
0..20 | 0..30 | 100..150 | 3
0..20 | 30..100 | 0..50 | 4
0..20 | 30..100 | 50..100 | 5
0..20 | 30..100 | 100..150 | 6
0..20 | 100..1000 | 0..50 | 7
0..20 | 100..1000 | 50..100 | 8
0..20 | 100..1000 | 100..150 | 9
It works as follows (with the original value of #dimensions, having just the two attributes, "speed" and "distance"):
Attributes is a list of the attributes. Being an array, it maintains their order:
attributes = #dimensions.map {|h| h[:name]}
# => ["speed", "distance"]
We pull out the ranges from #dimensions and convert them to strings:
sd = #dimensions.map { |h| h[:values].map(&:to_s) }
# => [["0..20", "20..40", "40..60"], ["0..50", "50..100", "100..150"]]
Next we compute the string formating for all columns but the last:
fmt = sd.zip(attributes)
.map(&:flatten)
.map {|a| a.map(&:size)}
.map {|a| "%-#{a.max}s" }
# => ["%-6s", "%-8s"]
Here
sd.zip(attributes)
# => [[["0..20", "20..40", "40..60"], "speed" ],
# [["0..50", "50..100", "100..150"], "distance"]]
8 in "%-8s" equals the maximum of the length of the column label, distance (8) and the length of the longest string representation of a distance range (also 8, for "100..150"). The - in the formatting string left-adjusts the strings.
We can now print the header:
attributes.zip(fmt).each { |a,f| print f % a + V_DIVIDER }
puts COUNT
speed | distance | count
To print the remaining lines, we construct an array containing the contents of the first two columns. Each element of the array corresponds to a row of the table:
prod = (sd.shift).product(*sd)
# => ["0..20", "20..40", "40..60"].product(*[["0..50", "50..100", "100..150"]])
# => ["0..20", "20..40", "40..60"].product(["0..50", "50..100", "100..150"])
# => [["0..20", "0..50"], ["0..20", "50..100"], ["0..20", "100..150"],
# ["20..40", "0..50"], ["20..40", "50..100"], ["20..40", "100..150"],
# ["40..60", "0..50"], ["40..60", "50..100"], ["40..60", "100..150"]]
We need to flaten #data:
flat_data = #data.flatten
# => [1, 2, 3, 4, 5, 6, 7, 8, 9]
The first time through the until do loop,
r1 = prod.shift
# => ["0..20", "0..50"]
# prod now => [["0..20", "50..100"],...,["40..60", "100..150"]]
r2 = r1.zip(fmt)
# => [["0..20", "%-6s"], ["0..50", "%-8s"]]
r2.each { |d,f| print f % d + V_DIVIDER }
0..20 | 0..50 |
puts (flat_data.shift)
0..20 | 0..50 | 1
# flat_data now => [2, 3, 4, 5, 6, 7, 8, 9]

Calculating Median in Ruby

How do I calculate the median of an array of numbers using Ruby?
I am a beginner and am struggling with handling the cases of the array being of odd and even length.
Here is a solution that works on both even and odd length array and won't alter the array:
def median(array)
return nil if array.empty?
sorted = array.sort
len = sorted.length
(sorted[(len - 1) / 2] + sorted[len / 2]) / 2.0
end
Similar to nbarraille's, but I find it a bit easier to keep track of why this one works:
class Array
def median
sorted = self.sort
half_len = (sorted.length / 2.0).ceil
(sorted[half_len-1] + sorted[-half_len]) / 2.0
end
end
half_len = number of elements up to and including (for array with odd number of items) middle of array.
Even simpler:
class Array
def median
sorted = self.sort
mid = (sorted.length - 1) / 2.0
(sorted[mid.floor] + sorted[mid.ceil]) / 2.0
end
end
If by calculating Median you mean this
Then
a = [12,3,4,5,123,4,5,6,66]
a.sort!
elements = a.count
center = elements/2
elements.even? ? (a[center] + a[center+1])/2 : a[center]
def median(array) #Define your method accepting an array as an argument.
array = array.sort #sort the array from least to greatest
if array.length.odd? #is the length of the array odd?
array[(array.length - 1) / 2] #find value at this index
else array.length.even? #is the length of the array even?
(array[array.length/2] + array[array.length/2 - 1])/2.to_f
#average the values found at these two indexes and convert to float
end
end
More correct solution with handling edge cases:
class Array
def median
sorted = self.sort
size = sorted.size
center = size / 2
if size == 0
nil
elsif size.even?
(sorted[center - 1] + sorted[center]) / 2.0
else
sorted[center]
end
end
end
There is a specs to prove:
describe Array do
describe '#median' do
subject { arr.median }
context 'on empty array' do
let(:arr) { [] }
it { is_expected.to eq nil }
end
context 'on 1-element array' do
let(:arr) { [5] }
it { is_expected.to eq 5 }
end
context 'on 2-elements array' do
let(:arr) { [1, 2] }
it { is_expected.to eq 1.5 }
end
context 'on odd-size array' do
let(:arr) { [100, 5, 2, 12, 1] }
it { is_expected.to eq 5 }
end
context 'on even-size array' do
let(:arr) { [7, 100, 5, 2, 12, 1] }
it { is_expected.to eq 6 }
end
end
end
I like to use Refinements, which is a safe way to Monkey Patch the ruby classes without collateral effects over the system.
The usage become much more cleaner than a new method.
With the Refinements you can monkey patch the Array class, implement the Array#median and this method will only be available inside the scope of the class that is using the refinement! :)
Refinements
module ArrayRefinements
refine Array do
def median
return nil if empty?
sorted = sort
(sorted[(length - 1) / 2] + sorted[length / 2]) / 2.0
end
end
end
class MyClass
using ArrayRefinements
# You can use the Array#median as you wish here
def test(array)
array.median
end
end
MyClass.new.test([1, 2, 2, 2, 3])
=> 2.0
def median(array)
half = array.sort!.length / 2
array.length.odd? ? array[half] : (array[half] + array[half - 1]) / 2
end
*If the length is even, you must add the middle point plus the middle point - 1 to account for the index starting at 0
def median(arr)
sorted = arr.sort
if sorted == []
return nil
end
if sorted.length % 2 != 0
result = sorted.length / 2 # 7/2 = 3.5 (rounded to 3)
return sorted[result] # 6
end
if sorted.length % 2 == 0
result = (sorted.length / 2) - 1
return (sorted[result] + sorted[result+1]) / 2.0 # (4 + 5) / 2
end
end
p median([5, 0, 2, 6, 11, 10, 9])
Here's a solution:
app_arry = [2, 3, 4, 2, 5, 6, 16].sort
# check array isn't empty
if app_arry.empty? || app_arry == ""
puts "Sorry, This will not work."
return nil
end
length = app_arry.length
puts "Array length = #{length}"
puts "Array = #{app_arry}"
if length % 2 == 0
# even number of elements
puts "median is #{(app_arry[length/2].to_f + app_arry[(length-1)/2].to_f)/2}"
else
# odd number of elements
puts "median is #{app_arry[(length-1)/2]}"
end
OUTPUT
Array length = 7
Array = [2, 3, 4, 2, 5, 6, 16]
median is 2
def median(array, already_sorted=false)
return nil if array.empty?
array = array.sort unless already_sorted
m_pos = array.size / 2
return array.size % 2 == 1 ? array[m_pos] : mean(array[m_pos-1..m_pos])
end
There are many ways to do this, but for both performance and reliability, I suggest using the enumerable-statistics library created by Ruby committer mrkn.
https://github.com/mrkn/enumerable-statistics
require 'enumerable/statistics'
ary = [1,2,3,3,4]
ary.mean # => 2.6
ary.median # => 3
I think it's good:
#!/usr/bin/env ruby
#in-the-middle value when odd or
#first of second half when even.
def median(ary)
middle = ary.size/2
sorted = ary.sort_by{ |a| a }
sorted[middle]
end
or
#in-the-middle value when odd or
#average of 2 middle when even.
def median(ary)
middle = ary.size/2
sorted = ary.sort_by{ |a| a }
ary.size.odd? ? sorted[middle] : (sorted[middle]+sorted[middle-1])/2.0
end
I used sort_by rather than sort because it's faster: Sorting an array in descending order in Ruby.

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

Resources