-- 3wXLZ5Iqn2laxPaVCRn
Content-Type: text/plain
Content-Transfer-Encoding: 7bit
My solution started off from the most basic hash to openstruct
conversion I could think of: OpenStruct.new(some_hash). Those pesky
nested hashes still needed to be dealt with, so I came up with:
class Hash
def to_ostruct(clz penStruct)
clz.new Hash[*inject([]){|ar,(k,v)|ar<<k<<(v.to_ostruct(clz) rescue v)}]
end
end
This works, but it's very inefficient, it doesn't pass the case
Mentalguy posted, and it doesn't fail well with invalid keys or other
errors. To handle those things, I had to go a bit longer:
class Hash
def to_ostruct(clz penStruct, cch })
cch[self] os lz.new)
each do |k,v|
raise "Invalid key: #{k}" unless k /[a-z_][a-zA-Z0-9_]*/
os.__send__("#{k} v.is_a?(Hash)? cch[v] || v.to_ostruct(clz,cch) : v)
end
os
end
end
I chose to fail for invalid keys, rather than introducing potentially
confusing renaming rules or similar. It's still not as efficient as it
might be, but a bit better than the first one.
Neither solution takes into consideration the problems Ara pointed out -
this is the reason for the optional 'clz' parameter to both methods.
Undef'ing methods from OpenStruct turned out to be a non-starter, since
it uses them itself, so I just implemented a simple, naive DumbStruct
that can be used with the to_ostruct methods above:
class DumbStruct
alias :__iv_set__ :instance_variable_set
alias :__class__ :class
instance_methods.each do |m|
undef_method(m) unless m /^(__|method_missing|inspect|to_s)|\?$/
end
def initialize(hsh })
hsh.each { |k,v| method_missing("#{k} v) }
end
def method_missing(name, *args, &blk)
if (name ame.to_s) /[^ $/
name ame[0..-2]
__iv_set__("@#{name}", args.first)
(class << self; self; end).class_eval { attr_accessor name }
else
super
end
end
end
Attached are the full files including testcases and a basic benchmark.
Thanks for another fun and interesting quiz :)
--
Ross Bamford - rosco / roscopeco.REMOVE.co.uk
-- 3wXLZ5Iqn2laxPaVCRn
Content-Disposition: attachment; filename=dstruct.rb
Content-Type: text/plain; name=dstruct.rb; charset=utf-8
Content-Transfer-Encoding: 7bit
# An mostly-compatible OpenStruct replacement that is, oddly, faster than
# OpenStruct some of the time, and crucially allows us to use Object method
# names for struct members. Needed to pass Ara's test case. To use, just
# pass it to to_ostruct:
#
# ds :class 1, :method 2}.to_ostruct(DumbStruct)
#
class DumbStruct
alias :__iv_set__ :instance_variable_set
alias :__class__ :class
instance_methods.each do |m|
undef_method(m) unless m /^(__|method_missing|inspect|to_s)|\?$/
end
def initialize(hsh })
hsh.each { |k,v| method_missing("#{k} v) }
end
def method_missing(name, *args, &blk)
if (name ame.to_s) /[^ $/
name ame[0..-2]
__iv_set__("@#{name}", args.first)
(class << self; self; end).class_eval { attr_accessor name }
else
super
end
end
end
-- 3wXLZ5Iqn2laxPaVCRn
Content-Disposition: attachment; filename=hash2ostruct-basic.rb
Content-Type: text/plain; name=hash2ostruct-basic.rb; charset=utf-8
Content-Transfer-Encoding: 7bit
require 'ostruct'
class Hash
# Convert this hash to a tree of nested OpenStructs (or equivalent class
# if specified - see DumbStruct). This is the first version I wrote, and
# it doesn't take account of the evil stuff Mentalguy demo'd, or of illegal
# method name keys as Adam Shelly pointed out. It's a simple recursive
# solution, and very inefficient.
def to_ostruct(clz penStruct)
clz.new Hash[*inject([]){|ar,(k,v)|ar<< k<<(v.to_ostruct(clz) rescue v)}]
end
end
if $0 __FILE__
require 'yaml'
require 'test/unit'
require 'benchmark'
require 'dstruct'
class TestHashToOstruct < Test::Unit::TestCase
TD1 AML::load(%{
---
foo: 1
bar:
baz: [1, 2, 3]
quux: 42
doctors:
- William Hartnell
- Patrick Troughton
- Jon Pertwee
- Tom Baker
- Peter Davison
- Colin Baker
- Sylvester McCoy
- Paul McGann
- Christopher Eccleston
- David Tennant
a: {x: 1, y: 2, z: 3}
})
TD2 AML::load(%{
---
foo: 1
bar:
baz: [1, 2, 3]
quux: 42
doctors:
- William Hartnell
- Patrick Troughton
- Jon Pertwee
- Tom Baker
- Peter Davison
- Colin Baker
- Sylvester McCoy
- Paul McGann
- Christopher Eccleston
- David Tennant
a: {x: 1, y: 2, z: 3}
table: walnut
method: linseed oil
type: contemporary
id: 1234
send: fedex
})
def test_01
os D1.to_ostruct
assert_equal 1, os.foo
assert_equal [1,2,3], os.bar.baz
assert_equal ['William Hartnell', 'Patrick Troughton', 'Jon Pertwee',
'Tom Baker', 'Peter Davison', 'Colin Baker',
'Sylvester McCoy', 'Paul McGann', 'Christopher Eccleston',
'David Tennant'], os.bar.doctors
assert_equal 1, os.bar.a.x
assert_equal 2, os.bar.a.y
assert_equal 3, os.bar.a.z
end
def test_02 # ara - need to use DumbStruct to pass this one
os D2.to_ostruct(DumbStruct)
assert_equal 1, os.foo
assert_equal [1,2,3], os.bar.baz
assert_equal ['William Hartnell', 'Patrick Troughton', 'Jon Pertwee',
'Tom Baker', 'Peter Davison', 'Colin Baker',
'Sylvester McCoy', 'Paul McGann', 'Christopher Eccleston',
'David Tennant'], os.bar.doctors
assert_equal 1, os.bar.a.x
assert_equal 2, os.bar.a.y
assert_equal 3, os.bar.a.z
assert_equal 'walnut', os.table
assert_equal 'linseed oil', os.method
assert_equal 'contemporary', os.type
assert_equal 1234, os.id
assert_equal 'fedex', os.send
end
end
if ARGV.delete('--bm')
puts "#### Basic impl - OpenStruct ####"
Benchmark.bm do |x|
x.report('base ') { 5000.times { TestHashToOstruct::TD1.to_ostruct } }
x.report('ara ') { 5000.times { TestHashToOstruct::TD2.to_ostruct } }
end
puts "\n#### Basic impl - DumbStruct ####"
d umbStruct
Benchmark.bm do |x|
x.report('base ') { 5000.times {TestHashToOstruct::TD1.to_ostruct(d)}}
x.report('ara ') { 5000.times {TestHashToOstruct::TD2.to_ostruct(d)}}
end
puts "\n#### tests ####"
end
end
-- 3wXLZ5Iqn2laxPaVCRn
Content-Disposition: attachment; filename=hash2ostruct.rb
Content-Type: text/plain; name=hash2ostruct.rb; charset=utf-8
Content-Transfer-Encoding: 7bit
require 'ostruct'
class Hash
# Convert this hash to a tree of nested OpenStructs (or equivalent class
# if specified - see DumbStruct). This version passes all the test-cases
# supplied on the ML, though it does need to be used with DumbStruct in
# order to pass the test Ara posted, and I chose to fail fast in the
# case of illegal method names in the hash keys. It's another recursive
# solution, and though it's a bit quicker than the first one, it's still
# no speed demon.
def to_ostruct(clz penStruct, cch })
cch[self] os lz.new)
each do |k,v|
raise "Invalid key: #{k}" unless k /[a-z_][a-zA-Z0-9_]*/
os.__send__("#{k} v.is_a?(Hash)? cch[v] || v.to_ostruct(clz,cch) : v)
end
os
end
end
if $0 __FILE__
require 'yaml'
require 'test/unit'
require 'benchmark'
require 'dstruct'
class TestHashToOstruct < Test::Unit::TestCase
TD1 AML::load(%{
---
foo: 1
bar:
baz: [1, 2, 3]
quux: 42
doctors:
- William Hartnell
- Patrick Troughton
- Jon Pertwee
- Tom Baker
- Peter Davison
- Colin Baker
- Sylvester McCoy
- Paul McGann
- Christopher Eccleston
- David Tennant
a: {x: 1, y: 2, z: 3}
})
TD2 AML::load(%{
---
foo: 1
bar:
baz: [1, 2, 3]
quux: 42
doctors:
- William Hartnell
- Patrick Troughton
- Jon Pertwee
- Tom Baker
- Peter Davison
- Colin Baker
- Sylvester McCoy
- Paul McGann
- Christopher Eccleston
- David Tennant
a: {x: 1, y: 2, z: 3}
table: walnut
method: linseed oil
type: contemporary
id: 1234
send: fedex
})
TD3 AML::load(%{
---
&verily
lemurs:
unite: *verily
beneath:
- patagonian
- bread
- products
thusly: [1, 2, 3, 4]
})
TD4 AML::load(%{
---
1: for the money
2: for the show
3: to get ready
4: go go go
})
def test_01
os D1.to_ostruct
assert_equal 1, os.foo
assert_equal [1,2,3], os.bar.baz
assert_equal ['William Hartnell', 'Patrick Troughton', 'Jon Pertwee',
'Tom Baker', 'Peter Davison', 'Colin Baker',
'Sylvester McCoy', 'Paul McGann', 'Christopher Eccleston',
'David Tennant'], os.bar.doctors
assert_equal 1, os.bar.a.x
assert_equal 2, os.bar.a.y
assert_equal 3, os.bar.a.z
end
def test_02 # ara - need to use DumbStruct to pass this one
os D2.to_ostruct(DumbStruct)
assert_equal 1, os.foo
assert_equal [1,2,3], os.bar.baz
assert_equal ['William Hartnell', 'Patrick Troughton', 'Jon Pertwee',
'Tom Baker', 'Peter Davison', 'Colin Baker',
'Sylvester McCoy', 'Paul McGann', 'Christopher Eccleston',
'David Tennant'], os.bar.doctors
assert_equal 1, os.bar.a.x
assert_equal 2, os.bar.a.y
assert_equal 3, os.bar.a.z
assert_equal 'walnut', os.table
assert_equal 'linseed oil', os.method
assert_equal 'contemporary', os.type
assert_equal 1234, os.id
assert_equal 'fedex', os.send
end
def test_03 # mental
os D3.to_ostruct
assert_equal OpenStruct, os.lemurs.unite.lemurs.unite.lemurs.class
assert_same os.lemurs, os.lemurs.unite.lemurs
assert_equal ['patagonian', 'bread', 'products'], os.lemurs.beneath
assert_same os.lemurs.beneath, os.lemurs.unite.lemurs.beneath
assert_equal [1,2,3,4], os.thusly
assert_same os.thusly, os.lemurs.unite.thusly
end
def test_04 # adam shelly
assert_raise(RuntimeError) do
os D4.to_ostruct
end
end
end
if ARGV.delete('--bm')
puts "#### Better impl - OpenStruct ####"
Benchmark.bm do |x|
x.report('base ') { 5000.times { TestHashToOstruct::TD1.to_ostruct } }
x.report('ara ') { 5000.times { TestHashToOstruct::TD2.to_ostruct } }
x.report('mental') { 5000.times { TestHashToOstruct::TD3.to_ostruct } }
end
puts "\n#### Better impl - DumbStruct ####"
d umbStruct
Benchmark.bm do |x|
x.report('base ') { 5000.times {TestHashToOstruct::TD1.to_ostruct(d)}}
x.report('ara ') { 5000.times {TestHashToOstruct::TD2.to_ostruct(d)}}
x.report('mental') { 5000.times {TestHashToOstruct::TD3.to_ostruct(d)}}
end
puts "\n#### tests ####"
end
end
-- 3wXLZ5Iqn2laxPaVCRn--