I have a model that uses a method to display an attribute on a view. I've managed to get them sorted, but they are not sorting the way I need them to be sorted.
I have the red quantities at the top, which I want, but I need the green and yellow quantities reversed. The order should be red, yellow then green.
Here is the method that adds the colors the column:
def get_quantity_text_class
case
when quantity_on_hand > reorder_quantity then 'text-success'
when quantity_on_hand > p_level then 'text-warning'
else 'text-danger'
end
end
And here is the method that creates the column:
def quantity_on_hand
ppkb.sum(:quantity)
end
Here is the sort algorithm I'm using:
sort_by{ |item| item.get_quantity_text_class }
I feel like I'm so close, but I just can't figure out how to reverse the green and yellow numbers.
It is currently sorted based on the string values text-danger, text-success and text-warning. To sort it the way you want, try sorting it based on numeric values:
sort_by do |item|
case item.get_quantity_text_class
when 'text-danger'
0
when 'text-warning'
1
else
2
end
end
you can define your comparator inside Item
class Item
def <=>(another_item)
txt_class = self.get_quantity_text_class
o_txt_class = another_item.get_quantity_text_class
if txt_class == o_txt_class
# put your logic here for items with identical classes.
# for example:
self.quantity_on_hand <=> another_item.quantity_on_hand
end
text_classes = ['text-danger', 'text-warning', 'text-success']
txt_class_idx = text_classes.find_index { |e| e == txt_class }
o_txt_class_idx = text_classes.find_index { |e| e == o_txt_class }
txt_class_idx <=> o_txt_class_idx
end
end
then call items.sort
I'm trying to create a tic-tac-toe game. I listed only the code for the relevant class and methods I got so far:
class Box
attr_reader :name, :row, :column
attr_accessor :is_marked, :contents
def initialize(name, row, column, is_marked=false, contents)
#name = name
#row = row
#column = column
#is_marked = is_marked
#contents = contents
end
def self.display_box
print '|#{contents}|'
end
end
#generate box instances
(1..9).each do |i|
if i > 3
col = i % 3
else
col = i
end
box#{i} = Box.new('box#{i}', (i/3).ceil, col, false, '_')
end
board = [[box1, box2, box3], [box4, box5, box6], [box7, box8, box9]]
def display_board
box1.display_box; box2.display_box; box3.display_box; print '\n'
box4.display_box; box5.display_box; box6.display_box; print '\n'
box7.display_box; box8.display_box; box9.display_box; print '\n'
end
display_board
I can't figure out why creating an instance of my class throws an error. The error is:
undefined local variable or method `box1' for <Context:0x000000024df8a8>
(repl):44:in display_board'
(repl):61:in initialize'
I tried running it with and without the 'self' in the display_box method, same error.
The problem is this line.
box#{i} = Box.new('box#{i}', (i/3).ceil, col, false, '_')
It seems like you're trying to create a bunch of variables named box1, box2, ... There might be away way to do that in ruby, but that's not it. You could do it with eval, but you don't want to this. As you can see from the rest of your code, working with a big pile of variables like that is annoying.
Instead, make an Array of boxes.
boxes[i] = Box.new("box#{i}", (i/3).ceil, col, false, '_')
You need to declare boxes = [] before the loop.
Then create your board from that list using ranges.
board = [boxes[1..3], boxes[4..6], boxes[7..9]]
And display_board becomes less repetitive.
def display_board(board)
board.each { |row|
row.each { |box|
box.display_box
}
puts "\n"
}
end
Finally, if you want to interpolate variables in strings you have to use ". For example, in display_box.
def display_box
print "|#{contents}|"
end
This is an error:
box#{i}
You can interpolate into Strings(and regexes), but a variable name is not a
String. A String has quotes around it.
Even if you could interpolate into variable names, you would never do this:
box#{i} = Box.new('box#{i}', (i/3).ceil, col, false, '_')
Instead, you would create an Array and add instances to the Array:
boxes = []
(1..9).each do |i|
if i > 3
col = i % 3
else
col = i
end
boxes << Box.new('box#{i}', (i/3).ceil, col, false, '_')
end
Then the names of your instances are boxes[0], boxes[1], etc.
You also need to know that a def creates a new scope and variables outside the def cannot be seen inside the def. So any box1, box2, etc. variables created outside the def cannot be seen inside the def. What you need to do is pass the boxes Array to the display_board() method, like this:
def display_board(board_boxes)
...
end
display_board(boxes)
Ruby then lines up your method call and the method header like this:
display_board(boxes)
|
V
def display_board(board_boxes)
and the boxes Array gets assigned to a variable called board_boxes:
board_boxes = boxes
Then inside the def, board_boxes will be the Array containing the boxes.
Don't combine lines of code on one line using semi colons. When you use an Array to store your box instances, you can display your boxes like this:
board_boxes.each do |box|
box.display_box
end
If you want to print a newline after every three boxes, you can do this:
count = 1
board_boxes.each do |box|
box.display_box
print "\n" if count%3 == 0
count += 1
end
You get undefined local variable or method `box1' because box1 is not defined.
Apparently, you expected this line to create box1:
box#{i} = ...
but it is really just this:
box
because # starts a comment.
Double-quotes strings allow interpolation, but you can't interpolate variable names like this.
How to solve it?
It is almost always a bad idea to create variables dynamically. If you have a large or a varying number of values, you should store them in a collection - either an Array, a Hash, or a custom one.
The box object
Setting up the boxes is quite complex, because you are passing name, row and column to each box. Does a box really have to know its position within the board? Think about other objects that are part of a larger structure: an array element doesn't know its index (the array does), a character doesn't know its position (the string does) and a hash value doesn't know its key (the hash does).
Let's remove these extra attributes from Box and have it just store its content:
class Box
attr_accessor :content
def initialize
#content = '_'
end
def marked?
content != '_'
end
end
b = Box.new #=> #<Box:0x007fab7a9f58a8 #content="_">
b.content #=> "_"
b.marked? #=> false
b.content = 'X'
b.marked? #=> true
The board object
To create a 3×3 array of boxes, we can write: (I've removed the object ids from the output)
board = Array.new(3) { Array.new(3) { Box.new } }
#=> [[#<Box #content="_">, #<Box #content="_">, #<Box #content="_">],
# [#<Box #content="_">, #<Box #content="_">, #<Box #content="_">],
# [#<Box #content="_">, #<Box #content="_">, #<Box #content="_">]]>
Single boxes can be accessed via: (indices are zero-based)
board[0][0] #=> #<Box #content="_">
But since board is such an important object, I'd write a custom class:
class Board
def initialize
#boxes = Array.new(3) { Array.new(3) { Box.new } }
end
def box(x, y)
#boxes[x][y]
end
end
The class does not expose #boxes, because we don't want the array to be changed. It provides a method Board#box instead that returns the box for the given x and y coordinates.
Formatting
In my opinion neither Box nor Board should contain code that handles formatting, so let's write a small utility class to handle this part:
class BoardFormatter
def draw(board)
template.gsub(/[0-8]/) do |n|
x, y = n.to_i.divmod 3
board.box(x, y).content
end
end
def template
<<-STR
0 | 1 | 2
---+---+---
3 | 4 | 5
---+---+---
6 | 7 | 8
STR
end
end
BoardFormatter#template returns the string for the board. You might not have seen <<- before: it's called a heredoc and creates a multiline string between the given STR delimiters:
formatter = BoardFormatter.new
formatter.template #=> " 0 | 1 | 2\n---+---+---\n 3 | 4 | 5\n---+---+---\n 6 | 7 | 8\n"
BoardFormatter#draw uses String#gsub to replace each digit ([0-8]) with the respective box' content.
divmod is the key here, it returns both, a / b and a % b as an array. And because we're using zero-based indices, these are just the box coordinates for the nth box:
0.divmod(3) #=> [0, 0]
1.divmod(3) #=> [0, 1]
2.divmod(3) #=> [0, 2]
3.divmod(3) #=> [1, 0]
4.divmod(3) #=> [1, 1]
5.divmod(3) #=> [1, 2]
6.divmod(3) #=> [2, 0]
7.divmod(3) #=> [2, 1]
8.divmod(3) #=> [2, 2]
Complete example:
board = Board.new
board.box(0, 2).content = 'X'
board.box(0, 0).content = 'O'
board.box(2, 0).content = 'X'
board.box(1, 1).content = 'O'
formatter = BoardFormatter.new
puts formatter.draw(board)
Output:
O | _ | X
---+---+---
_ | O | _
---+---+---
X | _ | _
One-based indices
The players should probably enter one-based indices when playing your game. This conversion should be handled when processing the user input. Within your core code, you should always use zero-based indices, just like Ruby.
As other users mentioned you can create an array to hold the instances of Box.
If you are dead set on using variables you can use instance_variable_set although using an array is probably better. See here for more details.
With instance_variable_set:
class Box
attr_reader :name, :row, :column
attr_accessor :is_marked, :contents
def initialize(name, row, column, is_marked=false, contents)
#name = name
#row = row
#column = column
#is_marked = is_marked
#contents = contents
end
def display_box
print '|#{contents}|'
end
end
#generate box instances
(1..9).each do |i|
if i > 3
col = i % 3
else
col = i
end
instance_variable_set("#box#{i}", Box.new('box#{i}', (i/3).ceil, col, false, '_'))
end
board = [[#box1, #box2, #box3], [#box4, #box5, #box6], [#box7, #box8, #box9]]
def display_board
#box1.display_box; #box2.display_box; #box3.display_box; print '\n'
#box4.display_box; #box5.display_box; #box6.display_box; print '\n'
#box7.display_box; #box8.display_box; #box9.display_box; print '\n'
end
display_board
With an array:
class Box
attr_reader :name, :row, :column
attr_accessor :is_marked, :contents
def initialize(name, row, column, is_marked=false, contents)
#name = name
#row = row
#column = column
#is_marked = is_marked
#contents = contents
end
def display_box
print '|#{contents}|'
end
end
#generate box instances
arr = [];
(1..9).each do |i|
if i > 3
col = i % 3
else
col = i
end
name = 'box' + i.to_s
arr.push(Box.new(name, (i/3).ceil, col, false, '_'))
end
board = [[arr[0], arr[1], arr[2]], [arr[3], arr[4], arr[5]], [arr[6], arr[7], arr[8]]]
def display_board(arr)
arr[0].display_box; arr[1].display_box; arr[2].display_box; print '\n'
arr[3].display_box; arr[4].display_box; arr[5].display_box; print '\n'
arr[6].display_box; arr[7].display_box; arr[8].display_box; print '\n'
end
display_board(arr)
I came across an old piece of code which was creating the hash datatype using simple arrays in ruby. I get most of it, except one part:
The point as I understand it is, every element of #store is a memory location for a particular key/value pair, as a function of the key. So #store[3] would generally store key/value pairs corresponding to key=3, key=53, ... and in general where key % size == 3 (in this case size = 50).
However, when I set hash[3] = 7, hash[53] = 9, etc., every element of #store array is populated with the key/value pairs, and not just the index 3 element. It seems like the line #store[store_key] << [key, value] in the method []=(key, value) is adding [key, value] to every element of #store, and not just one with the index store_key. Any ideas?
class SimpleHash
attr_accessor :size, :store
def initialize(size)
#size = size
#store = Array.new(size, [])
end
def []=(key, value)
store_key = key % #size
index = find_key(key, #store[store_key])
if index
#store[store_key][index][1] = value
else
p "***********************************"
p #store
#store[store_key] << [key, value]
p "after"
p store_key
p #store
end
end
end
hash = SimpleHash.new(50)
p hash
hash[3] = 5
p hash
hash[3] = 7
hash[53] = 9
hash[103] = 11
hash[104] = 11
You can simply do this
#store = Array.new(size){ [] }
Every element is a separate array.
Although your question's a bit unclear, I can guess what the problem is.
#store = Array.new(size, [])
That creates an array of the right size, but where every element is the SAME OBJECT.
Change the array-within-an-array at any position, and the change will be evident in every position.
Try instead
#store = Array.new
size.times { #store << [] }
Every sub-array will be a separate object that way.
EDIT
#nafaa boutefer's answer is better. The block gets evaluated for each instance of the array so each sub-array is a different object.
#store = Array.new(size){ [] }
I have a long code but I tried to copy and adapt my problem in as few lines as possible . I have a method which creates an array( 2D ) with 0 and 1
array1 = newValue(2) - the number 2 represents how many 1 the array has
array2 = newValue(3)
and this loop
(0..9).each do|i|
(0..9).each do|j|
while((array1[i][j] == array2[i][j]) && (array2[i][j] == 1)) do
array1 = newvalue(2)
array2 = newvalue(3)
end
end
end
I'm using the while loop so I won t have a 1 in the same position in both arrays . But what is inside the while loop doesn't modify the values of the array . I also tried using map!/collect! but I think I did something wrong because nothing happened. I hope you can understand what I was trying to do .
Edit:
def newValue(value)
value = value.to_i
array = Array.new(10) { Array.new(10 , 0) }
(a lot of conditions on how to position the items in the array)
return array
end
Here's my take... hopefully it'll help out. It seems that what you noticed was true. The arrays are not getting reset. Probably because inside the each blocks, the scope is lost. This is probably because the are arrays. I took a slightly different approach. Put everything in a class so you can have instance variables that you can control and you know where they are and that they are always the same.
I pulled out the compare_arrays function which just returns the coordinates of the match if there is one. If not it returns nil. Then, youre while loop is simplified in the reprocess method. If you found a match, reprocess until you don't have a match any more. I used a dummy newValue method that just returned another 2d array (as you suggested yours does). This seems to do the trick from what I can tell. Give it a whirl and see what you think. You can access the two arrays after all the processing with processor.array1 as you can see I did at the bottom.
# generate a random 2d array with 0's and val's
def generateRandomArray(val=1)
array = []
(0..9).each do |i|
(0..9).each do |j|
array[i] ||= []
array[i][j] = (rand > 0.1) ? 0 : val
end
end
array
end
array1 = generateRandomArray
array2 = generateRandomArray
def newValue(val)
generateRandomArray(val)
end
class Processor
attr_reader :array1, :array2
def initialize(array1, array2)
#array1 = array1
#array2 = array2
end
def compare_arrays
found = false
for ii in 0..9
break unless for jj in 0..9
if ((#array2[ii][jj] == 1) && (#array1[ii][jj] == 1))
found = true
break
end
end
end
[ii,jj] if found
end
def reprocess
while compare_arrays
puts "Reprocessing"
#array1 = newValue(2)
#array2 = newValue(3)
reprocess
end
end
end
processor = Processor.new(array1, array2)
processor.reprocess
puts processor.array1.inspect
Excuse the newbie question. I'm trying to create a two dimensional array in ruby, and initialise all its values to 1. My code is creating the two dimensional array just fine, but fails to modify any of its values.
Can anyone explain what I'm doing wrong?
def mda(width,height)
#make a two dimensional array
a = Array.new(width)
a.map! { Array.new(height) }
#init all its values to 1
a.each do |row|
row.each do |column|
column = 1
end
end
return a
end
It the line row.each do |column| the variable column is the copy of the value in row. You can't edit its value in such way. You must do:
def mda(width,height)
a = Array.new(width)
a.map! { Array.new(height) }
a.each do |row|
row.map!{1}
end
return a
end
Or better:
def mda(width,height)
a = Array.new(width)
a.map! { Array.new(height) }
a.map do |row|
row.map!{1}
end
end
Or better:
def mda(width,height)
a = Array.new(width){ Array.new(height) }
a.map do |row|
row.map!{1}
end
end
Or better:
def mda(width,height)
Array.new(width) { Array.new(height){1} }
end
each passes into the block parameter the value of each element, not the element itself, so column = 1 doesn't actually modify the array.
You can do this in one step, though - see the API docs for details on the various forms of Array#new. Try a = Array.new(width) {|i| Array.new(height) {|j| 1 } }
you can create it like this?
a=Array.new(width) { Array.new(height,1) }
column in your nested each loop is a copy of the value at that place in the array, not a pointer/reference to it, so when you change its value you're only changing the value of the copy (which ceases to exist outside the block).
If you just want a two-dimensional array populated with 1s something as simple as this will work:
def mda(width,height)
[ [1] * width ] * height
end
Pretty simple.
By the way, if you want to know how to modify the elements of a two-dimensional array as you're iterating over it, here's one way (starting from line 6 in your code):
#init all its values to 1
a.length.times do |i|
a[i].length.times do |j|
a[i][j] = 1
end
end