Create a nested structure from a CSV

Hello,
I am parsing a CSV file which has rows like this:

|user_name|user_id|supervisor_id|is_supervisor|
| admin | 1 | 0 | t |
| foo | 2 | 1 | t |
| test | 3 | 2 | f |

So from this I would like a hash that looks like:

{
:user_name=>‘admin’, :user_id=>1,
:users=>[
{:user_name=>‘foo’, :user_id=>2,
:users=>[
{:user_name=>‘test’,:user_id=>3}
]
}
]
}

Basically, a user can have a supervisor, but each preceding supervisor
can be supervised themselves.

The problem is, there is an arbritary number of supervisors (user ‘test’
may supervise an additional user) so I think I need to solve this
recursively.

Can anyone help with the ruby-way to go about this?
Thanks.

You have attributes like “is supervisor”. Maybe a Hash isn’t the best
way to do this. What about a custom class with its own attributes and
links?

something like this:

class MyUser

attr_accessor :name, :user_id, :supervisor_id, :is_supervisor,
:supervisor

def initialize( name, user_id, supervisor_id, is_supervisor )
@name = name
@user_id = user_id
@supervisor_id = supervisor_id
@is_supervisor = is_supervisor
end

end

Then you just extract the data, create the class instances, and set the
“supervisor” of each by finding the supervisor_id

a = File.read(‘test.txt’).split($/).map{|s|
s.split(’|’)[1…-1].map(&:strip)}

a.map! {|arr| MyUser.new( *arr ) }

a.each { |my_user| my_user.supervisor = a.find{ |user| user.user_id ==
my_user.supervisor_id } }

Now you have an Array of users, each with a link to their supervisor.

Joel P. wrote in post #1111319:

You have attributes like “is supervisor”. Maybe a Hash isn’t the best
way to do this. What about a custom class with its own attributes and
links?

Hi Joel,
Thanks for responding. I had considered something like that but I want
to generate different views based on the proposed structure.

I think the nested structure would make that easier, and also help me
when I need to edit it in forms (eventually).

Thanks.

Bah, my brain hurts too much to write a recursive hashifier at the
moment.

Maybe this will help inspire you; I wrote something a while ago to do
this the other way around: Turning a nested Hash into a 2D Array
(although I still don’t know how it works).

h = {
Part1: {
Type1: {
SubType1: 1, SubType2: 2, SubType3: 3
},
Type2: {
SubType1: 4, SubType2: 5, SubType3: 6
}
},
Part2: {
Type1: {
SubType1: 1, SubType2: 2, SubType3: 3
},
Type2: {
SubType1: 4, SubType2: 5, SubType3: 6
}
}
}

def convert_hash(h)
hash_to_a(h).each_slice(2).map { |a1,a2| a1 << a2.last }
end

def hash_to_a(h)
h.map { |k,v| v.is_a?(Hash) ? hash_to_a(v).map { |val| ([ k ] + [ val
]).flatten(1) } : [ k, v ] }.flatten(1)
end

require ‘pp’

pp convert_hash(h)
[[:Part1, :Type1, :SubType1, 1],
[:Part1, :Type1, :SubType2, 2],
[:Part1, :Type1, :SubType3, 3],
[:Part1, :Type2, :SubType1, 4],
[:Part1, :Type2, :SubType2, 5],
[:Part1, :Type2, :SubType3, 6],
[:Part2, :Type1, :SubType1, 1],
[:Part2, :Type1, :SubType2, 2],
[:Part2, :Type1, :SubType3, 3],
[:Part2, :Type2, :SubType1, 4],
[:Part2, :Type2, :SubType2, 5],
[:Part2, :Type2, :SubType3, 6]]

Joel P. wrote in post #1111332:

Bah, my brain hurts too much to write a recursive hashifier at the
moment.

Maybe this will help inspire you; I wrote something a while ago to do
this the other way around: Turning a nested Hash into a 2D Array
(although I still don’t know how it works).

thanks for the reply and the code. haven’t been able to figure it out
myself :slight_smile:
will keep trying.

I’m thiking something like:

def create_hierachy(csv_rows)
csv_rows.each do |row|
# New user hash
user = {:user_name=>row[‘user_name’], :user_id=>row[‘user_id’]}

if row.is_supervisor=='t'
  # GET THE THE SUB-USERS
  users = csv_rows.collect{|r| r.supervisor_id==row.user_id}.compact
  user[:users] = create_hierarchy(users)
end

end
end

Just can’t it it working :confused:

unsubscribe


From: Joel P. [[email protected]]
Sent: 04 June 2013 16:43
To: ruby-talk ML
Subject: Re: Create a nested structure from a CSV

Bah, my brain hurts too much to write a recursive hashifier at the
moment.

Maybe this will help inspire you; I wrote something a while ago to do
this the other way around: Turning a nested Hash into a 2D Array
(although I still don’t know how it works).

h = {
Part1: {
Type1: {
SubType1: 1, SubType2: 2, SubType3: 3
},
Type2: {
SubType1: 4, SubType2: 5, SubType3: 6
}
},
Part2: {
Type1: {
SubType1: 1, SubType2: 2, SubType3: 3
},
Type2: {
SubType1: 4, SubType2: 5, SubType3: 6
}
}
}

def convert_hash(h)
_hash_to_a(h).each_slice(2).map { |a1,a2| a1 << a2.last }
end

def hash_to_a(h)
h.map { |k,v| v.is_a?(Hash) ? _hash_to_a(v).map { |val| ([ k ] + [ val
]).flatten(1) } : [ k, v ] }.flatten(1)
end

require ‘pp’

pp convert_hash(h)
[[:Part1, :Type1, :SubType1, 1],
[:Part1, :Type1, :SubType2, 2],
[:Part1, :Type1, :SubType3, 3],
[:Part1, :Type2, :SubType1, 4],
[:Part1, :Type2, :SubType2, 5],
[:Part1, :Type2, :SubType3, 6],
[:Part2, :Type1, :SubType1, 1],
[:Part2, :Type1, :SubType2, 2],
[:Part2, :Type1, :SubType3, 3],
[:Part2, :Type2, :SubType1, 4],
[:Part2, :Type2, :SubType2, 5],
[:Part2, :Type2, :SubType3, 6]]

Oli S. [email protected] wrote:

I think the nested structure would make that easier, and also help me
when I need to edit it in forms (eventually).

Actually, I’d go ahead and use ActiveRecord or Sequel with an sqlite
database, which can be memory mapped if you don’t want it to persist
across uses. This lets you do ORMish things like:

user.name
user.id
user.supervisor
user.supervisor.name
user.supervisor.supervisor.supervisor.name (and so on)

And if you end up with even more attributes, win.

My effort here:

I think this would be easier to work with if implemented using classes
and
a utility method that converted a user to a nested hash.

-Doug