Newbie: Case statement

G

Graham Foster

As a newbie I'm confused about the Case construct. All the
documentation I've found states "case operates by comparing the
target with EACH of the expressions after the When keyword"
This does NOT appear to happen, as only the first match is ever
actioned, so overlapping WHEN cases are skipped.
def test(x)
case x
when 1..2
print "In A\n"
when 1..3
print "In B\n"
when 2..4
print "In C\n"
end
end
test(2)
only outputs "In A"
I assume this is expected behaviour? (I'm ex-C - and was looking for
fall-through behaviour)
So my underlying question is:- what's the fastest Ruby for checking
which of the 8 possible adjacent cells on a board (2D array) are
still "on the board"
Thx
Graham
 
H

Hal Fulton

Graham said:
As a newbie I'm confused about the Case construct. All the
documentation I've found states "case operates by comparing the
target with EACH of the expressions after the When keyword"
This does NOT appear to happen, as only the first match is ever
actioned, so overlapping WHEN cases are skipped.

"Each" but only "until a match."
I assume this is expected behaviour? (I'm ex-C - and was looking for
fall-through behaviour)

It's a strict multi-way branch; there's no fall-through from
one limb to another.
So my underlying question is:- what's the fastest Ruby for checking
which of the 8 possible adjacent cells on a board (2D array) are
still "on the board"

I'm not sure I grasp what you mean. But you *might* try something
like this:

ranges = {a..b=>"A",c..d=>"B",e..f=>"C",g..h=>"D",i..j=>"E"]
# looking for 'target'
list = ranges.reject {|x| ! x.include?(target) }
list = list.values
p list


Hal
 
M

Markus

So my underlying question is:- what's the fastest Ruby for checking
which of the 8 possible adjacent cells on a board (2D array) are
still "on the board"

given x,y:

x_range = (x=min_x ? x : x-1)..(x=max_x ? x : x+1)
y_range = (y=min_y ? y : y-1)..(y=max_y ? y : y+1)
x_range.collect { |x0|
y_range.collect { |y0| [x0,y0] unless x0 == x and y0 == y}
}.compact

might do what you want. Depends on what you mean by fast.

-- Markus

P.S. In use, this might be something more like:

class A_chess_board
attr_reader :min_x,:min_y,:max_x,:max_y

def for_each_neighbor_of(x,y)
x_range = (x=min_x ? x : x-1)..(x=max_x ? x : x+1)
y_range = (y=min_y ? y : y-1)..(y=max_y ? y : y+1)
x_range.collect { |x0|
y_range.collect { |y0|
yield(x0,y0) unless x0 == x and y0 == y}
}
}
}
end
 
B

Brian Schroeder

As a newbie I'm confused about the Case construct. All the documentation
I've found states "case operates by comparing the target with EACH of
the expressions after the When keyword" This does NOT appear to happen,
as only the first match is ever actioned, so overlapping WHEN cases are
skipped. def test(x)
case x
when 1..2
print "In A\n"
when 1..3
print "In B\n"
when 2..4
print "In C\n"
end
end
test(2)
only outputs "In A"
I assume this is expected behaviour? (I'm ex-C - and was looking for
fall-through behaviour)
So my underlying question is:- what's the fastest Ruby for checking
which of the 8 possible adjacent cells on a board (2D array) are still
"on the board"
Thx
Graham

Hello Graham,

what exactly do you want to know? Do you want to test for each cell if it
is on the board,

def in_board(x, y)
(0 <= x) and (x < @width) and (0 <= y) and (y < @height)
end

or do you want to do something based on the position on the board, in
which case the fastest (if not most concise ruby) I can come up with at
this hour of the night would be:

def position(x, y)
return :eek:ff_board if (x < 0) or (y < 0) or (@width <= y) or (@height
<= y) if (x == 0)
if (y == 0)
return :top_left
elsif (y == @height - 1)
return :bottom_left
else
return :left
end
elsif (x == @width - 1)
if (y == 0)
return :top_right
elsif (y == @height - 1)
return :bottom_right
end
else
return :not_at_border
end
end

But this is not very concise. It is depending on what you want to do?

If you have a two dimensional array, you will get in any case nil values
for off-board positions, so maybe

class Board
attr_reader :width, :height

def initialize(width, height)
@width = width; @height = height
@array = Array.new(width) { Array.new(height) { :empty } }
end

def [](x, y)
return nil if (x < 0) or (y < 0)
return @array[x][y]
end
end

will be enough for you, because it returns nil for each cell that is off
board.

regards,

Brian

Disclaimer: I consider myself also a nuby, so don't take anything for
granted I'm proposing.
 
D

David A. Black

Hi --

So my underlying question is:- what's the fastest Ruby for checking
which of the 8 possible adjacent cells on a board (2D array) are
still "on the board"

I'm not sure about fastest, but in case you collect implementations
and want to test them, I've got one from a networked Boggle game I
wrote a while back -- see <http://www.wobblini.net/board.txt>. I
haven't looked at this in a while and I don't know how much of it I'd
change if I were rewriting it now, but anyway, in case you're
interested, there it is.


David
 
F

Florian Gross

Graham said:
As a newbie I'm confused about the Case construct. All the
documentation I've found states "case operates by comparing the
target with EACH of the expressions after the When keyword"
This does NOT appear to happen, as only the first match is ever
actioned, so overlapping WHEN cases are skipped.
I assume this is expected behaviour? (I'm ex-C - and was looking for
fall-through behaviour)

Yup, you can use this as a case that checks all options:

def greedy_case(obj, cases)
cases.find_all do |condition, action|
condition === obj
end.inject(Hash.new) do |hash, (condition, action)|
hash[condition] = action.call; hash
end
end

def test(x)
greedy_case(x,
1 .. 2 => lambda { puts "In A" },
1 .. 3 => lambda { puts "In B" },
2 .. 4 => lambda { puts "In C" })
end

test(2)

Regards,
Florian Gross
 
M

Markus

Yup, you can use this as a case that checks all options:

def greedy_case(obj, cases)
cases.find_all do |condition, action|
condition === obj
end.inject(Hash.new) do |hash, (condition, action)|
hash[condition] = action.call; hash
end
end

def test(x)
greedy_case(x,
1 .. 2 => lambda { puts "In A" },
1 .. 3 => lambda { puts "In B" },
2 .. 4 => lambda { puts "In C" })
end

test(2)

Nice. Thats the best examples of writing your own control structure
I've seen in a quite while. *smile* And it even has the
non-deterministic aspect that Dijkstra was so fond of.

-- Markus
 
G

Graham Foster

thanks to everyone who replied. It is really interesting seeing the
diversity of solutions for a simple probelm. I must look into the .
compact and .collect methods as they look intriging. Some of the
answers (Sorry Florian) were rather too advanced for me. (I really
did mean Newbie).
Thx
Graham
 
F

Florian Gross

Graham said:
thanks to everyone who replied. It is really interesting seeing the
diversity of solutions for a simple probelm. I must look into the .
compact and .collect methods as they look intriging. Some of the
answers (Sorry Florian) were rather too advanced for me. (I really
did mean Newbie).

If you have any questions I'd be pleased to answer them. :)
Thx
Graham

Regards,
Florian Gross
 
G

Graham Foster

Yup, you can use this as a case that checks all options:
def greedy_case(obj, cases)
cases.find_all do |condition, action|
condition === obj
end.inject(Hash.new) do |hash, (condition, action)|
hash[condition] = action.call; hash
end
end

OK - Does this do the following (roughly)
- passes the input variable (x) and a hash of ranges and pointers to
functions (C speak)
- No idea what end.inject means, but the effect is to iterate through
the hash of "ranges" (or presumably eny condition), and for each
match (determined by === must look this up) execute the inline
function (block).
- What is the last hash for?
Roughly right?
What a devious mind you have Florian.
Graham
 
M

Markus

Yup, you can use this as a case that checks all options:

def greedy_case(obj, cases)
cases.find_all do |condition, action|
condition === obj
end.inject(Hash.new) do |hash, (condition, action)|
hash[condition] = action.call; hash
end
end

OK - Does this do the following (roughly)
- passes the input variable (x) and a hash of ranges and pointers to
functions (C speak)

More or less, though there is no requirement that the conditions be
ranges; the could be values, classes, regexp's, etc. as needed.
- No idea what end.inject means, but the effect is to iterate through
the hash of "ranges" (or presumably eny condition), and for each
match (determined by === must look this up) execute the inline
function (block).
- What is the last hash for?

Actually, the find_all takes the hash of cases and reduces it to
those that apply to this obj.

The inject then iterates over this hash of conditions (keys) and
actions (values) and produces a new hash (which will be the result) with
the same keys but the _results_ of the actions as the values.

[1,3,17].inject(0) { |running_total,value_to_add|
running_total+value_to_add
}

does: 0+1+3+17; the argument to inject is the "seed value" and the block
takes a pair of values and "combines" them in some way. In this case he
combines a hash and a condition/action pair by adding the result of the
action to the hash with the condition as the key.

Roughly right?

Yep.

-- MarkusQ
 
F

Florian Gross

Markus said:
def greedy_case(obj, cases)
cases.find_all do |condition, action|
condition === obj
end.inject(Hash.new) do |hash, (condition, action)|
hash[condition] = action.call; hash
end
end
OK - Does this do the following (roughly)
- passes the input variable (x) and a hash of ranges and pointers to
functions (C speak)
More or less, though there is no requirement that the conditions be
ranges; the could be values, classes, regexp's, etc. as needed.

Also the 'pointer' part is a bit misleading. It's just a normal object.

In Ruby one can create lambdas (kinda like blocks turned into Objects)
and .call them later one. They remember their creation context just like
blocks do. Here's a sample of a higher-order function (a function
returning another function) -- note that the argument of the outer
lambda, 'a', will still be available when the returned lambda is called.

times_fun = lambda do |a|
lambda { |b| a * b }
end

two_times_fun = times_fun.call(2)
two_times_fun.call(4) # => 8

And greedy_case() doesn't even really need a lambda. Anything that has a
#call method will work. Here's a bad sample:

result = greedy_case(obj,
String => obj.method:)reverse),
Numeric => obj.method:)to_s)).values.first
- No idea what end.inject means, but the effect is to iterate through
the hash of "ranges" (or presumably eny condition), and for each
match (determined by === must look this up) execute the inline
function (block).
- What is the last hash for?
[Detailed explanation]

I couldn't have explained this better. Thank you. :)

Regards,
Florian Gross
 
G

Graham Foster

I'm embarassed to post this.. but this is the (completely braindead)
solution I put together BEFORE I saw your solutions. You can see why
I thought it needed improving. My starting point is Conway's game of
LIFE (which should give you more of a clue what I'm doing here). It
might also gie you a clue as to why I was thinking of some form of
fall through / multiple case statement to reduce the duplication here
The world is a simple 2D array of (randomly initialised) cells, and
this is the "generation" stage. I'm new to OO as well as Ruby, so
there are probably LOADS of improvements I need to learn. (Guess this
method could be a class method, not an ordinary method too...?)
The Cell class has this
def hasneighbour
@neighbour +=1
end
# determine rules for the fate of any cells
def fate
case @neighbour
when 2
# no change to the alive or dead status
when 3
# cell springs into life
@isalive = true
else
# all other conditions the cell dies
@isalive = false
end
# reset the neighbour count for the next generation
@neighbour = 0
return @isalive
end

The World class has this monster
def calcGeneration
# work through a grid, identifying all live cells
# and incrementing the neighbour count of their neighbours
# the faster we can weed out entries wih no live cell - the
# faster this algorithm works
@world.each_index {
|row|
@world[row].each_index { |col|
# - ----- now what
# for each row and column
if @world[row][col].isalive
case row
# first row
when 0
case col
# first column
when 0
@world[row][col+1].hasneighbour
@world[row+1][col].hasneighbour
@world[row+1][col+1].hasneighbour
# middle columns
when 1..(@x-2)
@world[row][col-1].hasneighbour
@world[row][col+1].hasneighbour
@world[row+1][col].hasneighbour
@world[row+1][col+1].hasneighbour
@world[row+1][col-1].hasneighbour
# RHS columns
else
@world[row][col-1].hasneighbour
@world[row+1][col-1].hasneighbour
@world[row+1][col].hasneighbour
end
# all middle rows
when 1..(@y -2)
case col
when 0
@world[row-1][col].hasneighbour
@world[row-1][col+1].hasneighbour
@world[row][col+1].hasneighbour
@world[row+1][col].hasneighbour
@world[row+1][col+1].hasneighbour
when 1..(@x-2)
@world[row-1][col-1].hasneighbour
@world[row-1][col].hasneighbour
@world[row-1][col+1].hasneighbour
@world[row][col-1].hasneighbour
@world[row][col+1].hasneighbour
@world[row+1][col-1].hasneighbour
@world[row+1][col].hasneighbour
@world[row+1][col+1].hasneighbour
else
@world[row-1][col-1].hasneighbour
@world[row-1][col].hasneighbour
@world[row][col-1].hasneighbour
@world[row+1][col].hasneighbour
@world[row+1][col-1].hasneighbour
end
else # bottom row
case col
when 0
@world[row-1][col].hasneighbour
@world[row-1][col+1].hasneighbour
@world[row][col+1].hasneighbour
when 1..(@x-2)
@world[row-1][col-1].hasneighbour
@world[row-1][col].hasneighbour
@world[row-1][col+1].hasneighbour
@world[row][col-1].hasneighbour
@world[row][col+1].hasneighbour
else
@world[row-1][col-1].hasneighbour
@world[row-1][col].hasneighbour
@world[row][col-1].hasneighbour
end
end
end
}
}

# then go through all cells in the world and determine their
fate
@world.each {
|row|
row.each { |cell|
# for each cell determine its fate
cell.fate
}
}
end
(Sorry for the long listing. ) Thanks for listening. My next task is
to try to put some graphics on this then I can see what is going on!
(all a very long way from my embedded real-time stuff in C &
assembler!).
Graham
 
J

James Edward Gray II

I'm embarassed to post this..

Don't be! We're all learning from each other, just at different levels.

Let me make some general comments below.
The Cell class has this
def hasneighbour
@neighbour +=1
end

Okay, first I think the name of this method needs some work. Usually,
we use a form like has_neighbour, to make things a little easier on our
eyes.

Then we need to look at the name itself. Is has_neighbour answering a
question? That's what I expect from that name. It looks to me like
it's changing something and that's certainly not what I expect from
that name.

This all may sound silly, but eventually you'll need to read your code
too and we need to read it now to help, so you want to keep it as self
documenting as possible.
# determine rules for the fate of any cells
def fate
case @neighbour
when 2
# no change to the alive or dead status
when 3
# cell springs into life
@isalive = true
else
# all other conditions the cell dies
@isalive = false
end
# reset the neighbour count for the next generation
@neighbour = 0
return @isalive
end

To me a full blown class seems a bit overkill for a cell. What are we
really tracking about each cell? One piece of information right? Does
it have a critter in it or not?

Because of that, I would probably just put a 2D array in my world class
and fill it with spaces and Xs. Pretty much any calculation needs to
be done from that level anyway, since neighboring cells are always
involved, so it seems like this cell level abstraction just gets in the
way.
The World class has this monster
def calcGeneration
# work through a grid, identifying all live cells
# and incrementing the neighbour count of their neighbours
# the faster we can weed out entries wih no live cell - the
# faster this algorithm works
@world.each_index {
|row|
@world[row].each_index { |col|
# - ----- now what
# for each row and column
if @world[row][col].isalive
case row
# first row
when 0
case col
# first column
when 0
@world[row][col+1].hasneighbour
@world[row+1][col].hasneighbour
@world[row+1][col+1].hasneighbour
# middle columns
when 1..(@x-2)
@world[row][col-1].hasneighbour
@world[row][col+1].hasneighbour
@world[row+1][col].hasneighbour
@world[row+1][col+1].hasneighbour
@world[row+1][col-1].hasneighbour
# RHS columns
else
@world[row][col-1].hasneighbour
@world[row+1][col-1].hasneighbour
@world[row+1][col].hasneighbour
end
# all middle rows
when 1..(@y -2)
case col
when 0
@world[row-1][col].hasneighbour
@world[row-1][col+1].hasneighbour
@world[row][col+1].hasneighbour
@world[row+1][col].hasneighbour
@world[row+1][col+1].hasneighbour
when 1..(@x-2)
@world[row-1][col-1].hasneighbour
@world[row-1][col].hasneighbour
@world[row-1][col+1].hasneighbour
@world[row][col-1].hasneighbour
@world[row][col+1].hasneighbour
@world[row+1][col-1].hasneighbour
@world[row+1][col].hasneighbour
@world[row+1][col+1].hasneighbour
else
@world[row-1][col-1].hasneighbour
@world[row-1][col].hasneighbour
@world[row][col-1].hasneighbour
@world[row+1][col].hasneighbour
@world[row+1][col-1].hasneighbour
end
else # bottom row
case col
when 0
@world[row-1][col].hasneighbour
@world[row-1][col+1].hasneighbour
@world[row][col+1].hasneighbour
when 1..(@x-2)
@world[row-1][col-1].hasneighbour
@world[row-1][col].hasneighbour
@world[row-1][col+1].hasneighbour
@world[row][col-1].hasneighbour
@world[row][col+1].hasneighbour
else
@world[row-1][col-1].hasneighbour
@world[row-1][col].hasneighbour
@world[row][col-1].hasneighbour
end
end
end
}
}

# then go through all cells in the world and determine their
fate
@world.each {
|row|
row.each { |cell|
# for each cell determine its fate
cell.fate
}
}
end

See if this little script gives you some fresh ideas...

#!/usr/bin/ruby -w

world = [ ]

# generate random world
10.times do
world.push(Array.new(10) { rand(2) == 1 ? "X" : " " })
end

# and later...

world.each_with_index do |row, y|
row.each_with_index do |cell, x|
# list all possible neighbors
neighbors = [ ]
[-1, 0, 1].each { |n| neighbors.push( [x + n, y + -1],
[x + n, y + 0],
[x + n, y + 1] ) }
# filter neighbor list with bounds checking
neighbors.delete_if { |xy| xy.include?(-1) ||
xy[0] >= row.size ||
xy[1] >= world.size ||
(xy[0] == x && xy[1] == y) }

# process neighbor cells
puts "#{x}, #{y} is next to:"
neighbors.each do |e|
puts "\t#{e[0]}, #{e[1]}"
end
end
end

__END__

Hope that helps.

James Edward Gray II
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,756
Messages
2,569,535
Members
45,008
Latest member
obedient dusk

Latest Threads

Top