Data Structure for n-dimension values with defaults


#1

Help! I can’t figure out how to store a rather particular set of data.

An object in my system has properties. Each property has a value.
However, the value of each property can change based on a number of
“build axes”. There are an arbitrary number of orthogonal build axes,
none of which is more important than another. (However, if it’s needed,
I am happy to impose a particular ordering on them.)

Each build axis has a number of ‘notches’, or discrete values along the
axis. In addition to all the notches, there is one special notch named
“Master”. A value set in this notch is a fallback, default value if
other notches in the axis have no values.

So, for example, assume I’m talking about a Text object. One of it’s
properties is “textString”.
The system has two build axes: “Language” and “Rating”.

The “Language” axis has “French”, “English”, “German”, and “Jibbrish”
notches in addition to the Master notch. The “Rating” axis has “G”,
“PG”, “R”, and “X” values.

Here’s a monospace-font diagram of the values I want to store:

            -------------------Language-------------------------

            Master    English      French     German    Jibbrish

R Master (yell) - - Ach! -
A G - Wow! Oh! - -
T PG - Holy Crap! Merde! - -
I R - Holy Shit! Mon Dieu! - -
N X $!@* - - XXX -
G

With the above, the textString property should have a value of “Ach!”
if the Language axis is set to German and the Rating axis is set to G,
PG, or R.

If the Rating is set to “X”, then the textString should be set to
“$!@*” regardless of the Language axis setting…except for if the
Language is set to German, in which case it should be “XXX”

If the Language is set to “Jibbrish”, then the value is “(yell)”,
except when the Rating is “X”.

If the Rating is set to “PG” and the Language is set to “English”, then
the textString value should be set to “Holy Crap!”. And so on for other
values that are explicitly set for all axis combinations.

Hopefully that example is clear enough to show that explicit values win
over master values.

So, how the heck would you store this sort of information? I mostly
don’t even care about speed.


#2

On Mar 26, 2006, at 6:28 PM, Phrogz wrote:

the
“PG”, “R”, and “X” values.
N X $!@* - - XXX -
If the Language is set to “Jibbrish”, then the value is “(yell)”,
win
over master values.

So, how the heck would you store this sort of information? I mostly
don’t even care about speed.

An array of hashes? Actually a hash of structs would probably be better

Row = Struct.new(:master, :english, :french, :german, :gibberish)

table = {
:G => Row.new(’(yell)’, nil, nil, ‘Ach!’, nil),
:PG => Row.new(nil, ‘Wow!’, ‘Oh!’, nil, nil),
:R => Row.new(nil, ‘Holy Shit!’, ‘Mon Dieu!’,
nil, nil),
:X => Row.new(’$!@*’, nil, nil, ‘XXX’, nil)
}

def table.get(rating, language)
res = self[rating].send(language)
if res.nil?
self[rating].master
else
res
end
end

irb(main):040:0> table.get(:X, :german)
=> “XXX”
irb(main):041:0> table.get(:X, :english)
=> “$!@*”


#3

Close, but it fails from the ordered-axis problem I’ve been seeing.

puts table.get( :R, :german )
#=> nil

that should be “Ach!”


#4

On Mar 26, 2006, at 6:53 PM, Phrogz wrote:

Close, but it fails from the ordered-axis problem I’ve been seeing.

puts table.get( :R, :german )
#=> nil

that should be “Ach!”

Improved version:
% cat table_structure.rb
Row = Struct.new(:master, :english, :french, :german, :gibberish)

table = {
:master => Row.new(’(yell)’, nil, nil, ‘Ach!’, nil),
:G => Row.new(nil, ‘Wow!’, ‘Oh!’, nil, nil),
:PG => Row.new(nil, ‘Holy Crap!’, ‘Merde!’, nil, nil),
:R => Row.new(nil, ‘Holy Shit!’, ‘Mon Dieu!’, nil, nil),
:X => Row.new(’$!@*’, nil, nil, ‘XXX’, nil)
}

def table.get(rating, language)
res = self[rating].send(language)
if res.nil?
res = self[rating].master
end
if res.nil?
res = self[:master].send(language)
end
if res.nil?
res = self[:master].master
end
res
end

puts table.get(:R, :german)
puts table.get(:X, :german)
puts table.get(:X, :english)

% ruby table_structure.rb
Ach!
XXX
$!@*


#5

Cool, that seems to do it. So now, I just need to figure out how that
resolution algorithm scales in the n-dimensional case when I’m trying
to optimize for the fewest number of master values involved. And, as
Ryan D. pointed out on IRC, what my logic is when conflicting values
have the same weight (and if I should even be able to get into such a
situation).

This is helpful, though. I was previously trying to approach the
problem from some sort of default-hash value solution. This has made
apparent that I need to be storing n-dimensional data, and only if I
find a nil value at the intersection of all axes do I go into a
separate resolution mode for searching for the best value.


#6

On Tue, 28 Mar 2006, Ernest Obusek wrote:

Ernest
you have not given a receiver to gets and the default one is ARGF (see
pickaxe
for desc). try

STDIN.gets

hth.

-a


#7

I’m new to Ruby so perhaps this is obvious to you but not to me. Can
anyone tell me what is wrong with my simple script below:

$ cat t.rb

#!/usr/bin/ruby

puts “Timer set to #{ARGV[0]} seconds. Press to start the
countdown.”
gets

$ ./t.rb 60
Timer set to 60 seconds. Press to start the countdown.
./t.rb:4:in `gets’: No such file or directory - 60 (Errno::ENOENT)
from ./t.rb:4

It seems to think the argument of 60 should be a file or
directory… ???

Thanks,

Ernest


#8

removed_email_address@domain.invalid wrote:

you have not given a receiver to gets and the default one is ARGF (see
pickaxe for desc). try

STDIN.gets

That almost makes sense, except for the name of the receiver.
gets is supposed to read from a file, if specified. Otherwise
it reads from piped input (according to the ancient books I’ve
consulted).

I can understand that you would need to specify a different
receiver to specify “current console device”. But surely
the input pipe is standard input, yes? So STDIN is the current
console device, and that’s not the same as standard in???

Signed, Confused in Peoria.
:_)


#9

On Tue, 28 Mar 2006, Eric A. wrote:

???

I can understand that you would need to specify a different
receiver to specify “current console device”. But surely
the input pipe is standard input, yes? So STDIN is the current
console device, and that’s not the same as standard in???

Signed, Confused in Peoria.
:_)

yes and no. first off gets is a method of IO and it does read from
piped
input. however, there is a magic variable called ARGF which is the list
of
all files on the command line, or stdin if none are given. so ‘cat’, in
ruby
could be written as

ARGF.each{|line| print line}

and this would work with

cat one | ruby a.rb
ruby a.rb < two
ruby a.rb one two three

make sense? see ARGF in pickaxe for more.

regards.

-a


#10

On Tue, 28 Mar 2006, Eric A. wrote:

would look like this:
invocation gets do the same thing as STDIN.gets?
ruby doesn’t know that you didn’t mean to say to read the file ‘60’,
which is
on the command line.

when you use ARGF, deliberately or not, you telling ruby that ANY
command line
argument can be opened are read from. in your case you gave ‘60’ and
this
file does not exist. your code will work if you parse the command line
first:

 harp:~ > cat a.rb
 print ARGF.read

 harp:~ > echo 42|ruby a.rb 60
 a.rb:1:in `read': No such file or directory - 60 (Errno::ENOENT)
         from a.rb:1


 harp:~ > cat a.rb
 ARGV.shift
 print ARGF.read

 harp:~ > echo 42|ruby a.rb 60
 42

regards.

-a


#11

Brilliant! Thanks much for the explain-o.

It wasn’t my code, but I learned a lot anyway.


#12

removed_email_address@domain.invalid wrote:

gets is a method of IO and it does read from piped input.
however, there is a magic variable called ARGF which is the list of
all files on the command line, or stdin if none are given. so ‘cat’,
in ruby could be written as

ARGF.each{|line| print line}

So far, so good. Makes perfect sense.
What is more perplexing is that the formula for console I/O
would look like this:

puts prompt_string
STDIN.gets               --succeeds?

Why would standard input work there, but fail in this case:

puts prompt_string
gets                     --fails?

If no file is specified on the command line, why doesn’t that
invocation gets do the same thing as STDIN.gets?