P
Phrogz
I recently had to solve a one-off problem for work, and the solution I
came up with was about as non-DRY as you can imagine. I couldn't
figure out how to make it simple, however. In order to expand my
knowledge, I'm hoping to get some ideas (or even related problem
domains) to solve this generically.
The problem was this:
This website has a menu of industries, a menu of categories, and a
menu of products.
If a single industry is selected, highlight the categories and
products are available for that industry.
If a single category is selected, highlight the industries and
products are available for that category.
If a single product is selected, highlight the industries and
categories available for that industry.
If a pair of items is selected (for example, one industry and one
product) show all the related items that match the intersection (for
example, all the categories that apply to the intersection of those
two choices).
I solved it by storing the data as a series of nested hashes, dictated
an order in which axes were specified, and hard-coded it for the 3
axes above, and repeated almost the same code for all 6 possible axis
queries (axis1, axis2, axis3, axis1+axis2, axis1+axis3, axis2+axis3).
It was huge and bloated and menial.
SO...this is like a data cube (in this particular set of 3
dimensions). I'm looking for a solution that is generic to n
dimensions. Doing that will simultaneously make the code simpler and
more generally useful.
Here's Ruby code and plenty of test cases to exactly describe what I'm
looking for. It's echoed at http://pastie.caboo.se/130061 for easier
reading and preventing unintended line-wrapping.
My preemptive thanks to all who provide some ideas (or working code)
towards the solution.
# BOYv NUM>| one | two | three |
# ----------+-----------+-----------+----------+
# jim | alpha | | beta |
# jam | beta | alpha | |
# jem | delta | | delta |
# jom |beta, alpha| omega | delta |
AXES = [ :boy, :num, :greek ]
COMBOS = {
:jim => {
ne => [ :alpha ],
:three => [ :beta ]
},
:jam => {
ne => [ :beta ],
:two => [ :alpha ]
},
:jem => {
ne => [ :delta ],
:three => [ :delta ]
},
:jom => {
ne => [ :beta, :alpha ],
:two => [ mega ],
:three => [ :delta ]
}
}
def make_database( axes, nested_hash )
# Parse however you like to create
# your own data structure.
end
def fetch_options( database, axes_values = {} )
# Return all valid possibilities based on the supplied values
end
require 'test/unit'
class TestMultiDimensionalDB < Test::Unit::TestCase
def setup
$db = make_database( AXES, COMBOS )
end
def test_one_axis
opts = fetch_options( $db, :greek=>mega )
assert( opts[:boy] == [:jom],
"The only boy with greekmega is jom." )
assert( opts[:num] == [:two],
"The only num with greekmega is two." )
opts = fetch_options( $db, :greek=>:alpha )
assert( opts[:boy].length == 3,
"There are 3 boys with greek:alpha." )
assert( opts[:boy].include?jim),
"One of the 3 boys with greek:alpha is jim." )
assert( opts[:boy].include?jam),
"One of the 3 boys with greek:alpha is jam." )
assert( opts[:boy].include?jom),
"One of the 3 boys with greek:alpha is jom." )
assert( opts[:num].length == 2,
"There are 2 nums with greek:alpha." )
assert( opts[:num].include?one) && opts[:num].include?two),
"The nums with greek:alpha are one and two." )
opts = fetch_options( $db, :boy=>:jem )
assert( opts[:num].length == 2,
"There are 2 nums with the boy:jem." )
assert( opts[:num].include?one) && opts[:num].include?three),
"The nums with the boy:jem are one and three." )
assert( opts[:greek] == [:delta]
"The only greek with the boy:jem is delta" )
opts = fetch_options( $db, :num=>ne )
assert( opts[:boy].length == 4,
"There are 4 boys with numne." )
assert( opts[:boy].include?jim),
"One of the 4 boys with numne is jim." )
assert( opts[:boy].include?jam),
"One of the 4 boys with numne is jam." )
assert( opts[:boy].include?jem),
"One of the 4 boys with numne is jem." )
assert( opts[:boy].include?jom),
"One of the 4 boys with numne is jom." )
assert( opts[:greek].length == 3,
"There are 3 greek with numne." )
assert( opts[:greek].include?alpha),
"One of the 3 greeks with numne is alpha." )
assert( opts[:greek].include?beta),
"One of the 3 greeks with numne is beta." )
assert( opts[:greek].include?delta),
"One of the 3 greeks with numne is delta." )
end
def test_two_axes
opts = fetch_options( $db, :greek=>:alpha, :num=>ne )
assert( opts[:boy].length == 2,
"There are 2 boys with greek:alpha and numne." )
assert( opts[:boy].include?jim) && opts[:boy].include?jom),
"The boys with greek:alpha and numne are jim and jom." )
opts = fetch_options( $db, :greek=>:alpha, :num=>:two )
assert( opts[:boy] == [:jam],
"The only boy with greek:alpha and num:two is jam." )
opts = fetch_options( $db, :boy=>:jom, :num=>ne )
assert( opts[:greek].length == 2,
"There are 2 greeks with the boy:jom and numne." )
assert( opts[:greek].include?alpha) && opts[:greek].include?
beta),
"The greeks with the boy:jom and numne are alpha and beta." )
opts = fetch_options( $db, :boy=>:jam, :greek=>mega )
assert( opts[:num].length == 0,
"There are no nums with the boy:jam and greekmega." )
opts = fetch_options( $db, :boy=>:jem, :greek=>:delta )
assert( opts[:num].length == 2,
"There are 2 nums with the boy:jem and greek:delta." )
assert( opts[:num].include?one) && opts[:num].include?three),
"The nums with the boy:jem and greek:delta are one and three." )
end
end
came up with was about as non-DRY as you can imagine. I couldn't
figure out how to make it simple, however. In order to expand my
knowledge, I'm hoping to get some ideas (or even related problem
domains) to solve this generically.
The problem was this:
This website has a menu of industries, a menu of categories, and a
menu of products.
If a single industry is selected, highlight the categories and
products are available for that industry.
If a single category is selected, highlight the industries and
products are available for that category.
If a single product is selected, highlight the industries and
categories available for that industry.
If a pair of items is selected (for example, one industry and one
product) show all the related items that match the intersection (for
example, all the categories that apply to the intersection of those
two choices).
I solved it by storing the data as a series of nested hashes, dictated
an order in which axes were specified, and hard-coded it for the 3
axes above, and repeated almost the same code for all 6 possible axis
queries (axis1, axis2, axis3, axis1+axis2, axis1+axis3, axis2+axis3).
It was huge and bloated and menial.
SO...this is like a data cube (in this particular set of 3
dimensions). I'm looking for a solution that is generic to n
dimensions. Doing that will simultaneously make the code simpler and
more generally useful.
Here's Ruby code and plenty of test cases to exactly describe what I'm
looking for. It's echoed at http://pastie.caboo.se/130061 for easier
reading and preventing unintended line-wrapping.
My preemptive thanks to all who provide some ideas (or working code)
towards the solution.
# BOYv NUM>| one | two | three |
# ----------+-----------+-----------+----------+
# jim | alpha | | beta |
# jam | beta | alpha | |
# jem | delta | | delta |
# jom |beta, alpha| omega | delta |
AXES = [ :boy, :num, :greek ]
COMBOS = {
:jim => {
ne => [ :alpha ],
:three => [ :beta ]
},
:jam => {
ne => [ :beta ],
:two => [ :alpha ]
},
:jem => {
ne => [ :delta ],
:three => [ :delta ]
},
:jom => {
ne => [ :beta, :alpha ],
:two => [ mega ],
:three => [ :delta ]
}
}
def make_database( axes, nested_hash )
# Parse however you like to create
# your own data structure.
end
def fetch_options( database, axes_values = {} )
# Return all valid possibilities based on the supplied values
end
require 'test/unit'
class TestMultiDimensionalDB < Test::Unit::TestCase
def setup
$db = make_database( AXES, COMBOS )
end
def test_one_axis
opts = fetch_options( $db, :greek=>mega )
assert( opts[:boy] == [:jom],
"The only boy with greekmega is jom." )
assert( opts[:num] == [:two],
"The only num with greekmega is two." )
opts = fetch_options( $db, :greek=>:alpha )
assert( opts[:boy].length == 3,
"There are 3 boys with greek:alpha." )
assert( opts[:boy].include?jim),
"One of the 3 boys with greek:alpha is jim." )
assert( opts[:boy].include?jam),
"One of the 3 boys with greek:alpha is jam." )
assert( opts[:boy].include?jom),
"One of the 3 boys with greek:alpha is jom." )
assert( opts[:num].length == 2,
"There are 2 nums with greek:alpha." )
assert( opts[:num].include?one) && opts[:num].include?two),
"The nums with greek:alpha are one and two." )
opts = fetch_options( $db, :boy=>:jem )
assert( opts[:num].length == 2,
"There are 2 nums with the boy:jem." )
assert( opts[:num].include?one) && opts[:num].include?three),
"The nums with the boy:jem are one and three." )
assert( opts[:greek] == [:delta]
"The only greek with the boy:jem is delta" )
opts = fetch_options( $db, :num=>ne )
assert( opts[:boy].length == 4,
"There are 4 boys with numne." )
assert( opts[:boy].include?jim),
"One of the 4 boys with numne is jim." )
assert( opts[:boy].include?jam),
"One of the 4 boys with numne is jam." )
assert( opts[:boy].include?jem),
"One of the 4 boys with numne is jem." )
assert( opts[:boy].include?jom),
"One of the 4 boys with numne is jom." )
assert( opts[:greek].length == 3,
"There are 3 greek with numne." )
assert( opts[:greek].include?alpha),
"One of the 3 greeks with numne is alpha." )
assert( opts[:greek].include?beta),
"One of the 3 greeks with numne is beta." )
assert( opts[:greek].include?delta),
"One of the 3 greeks with numne is delta." )
end
def test_two_axes
opts = fetch_options( $db, :greek=>:alpha, :num=>ne )
assert( opts[:boy].length == 2,
"There are 2 boys with greek:alpha and numne." )
assert( opts[:boy].include?jim) && opts[:boy].include?jom),
"The boys with greek:alpha and numne are jim and jom." )
opts = fetch_options( $db, :greek=>:alpha, :num=>:two )
assert( opts[:boy] == [:jam],
"The only boy with greek:alpha and num:two is jam." )
opts = fetch_options( $db, :boy=>:jom, :num=>ne )
assert( opts[:greek].length == 2,
"There are 2 greeks with the boy:jom and numne." )
assert( opts[:greek].include?alpha) && opts[:greek].include?
beta),
"The greeks with the boy:jom and numne are alpha and beta." )
opts = fetch_options( $db, :boy=>:jam, :greek=>mega )
assert( opts[:num].length == 0,
"There are no nums with the boy:jam and greekmega." )
opts = fetch_options( $db, :boy=>:jem, :greek=>:delta )
assert( opts[:num].length == 2,
"There are 2 nums with the boy:jem and greek:delta." )
assert( opts[:num].include?one) && opts[:num].include?three),
"The nums with the boy:jem and greek:delta are one and three." )
end
end