Forum: Ruby Create a nested structure from a CSV

3c3b05d468b6de2505c0a657bcb6da75?d=identicon&s=25 Oli Sanders (rubella)
on 2013-06-04 16:11
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.
14b5582046b4e7b24ab69b7886a35868?d=identicon&s=25 Joel Pearson (virtuoso)
on 2013-06-04 16:36
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.
3c3b05d468b6de2505c0a657bcb6da75?d=identicon&s=25 Oli Sanders (rubella)
on 2013-06-04 16:55
Joel Pearson 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.
14b5582046b4e7b24ab69b7886a35868?d=identicon&s=25 Joel Pearson (virtuoso)
on 2013-06-04 17:43
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]]
957379f7ff731c04bd4ed3a6b54b7b3e?d=identicon&s=25 Anton Scipio (Guest)
on 2013-06-04 18:00
(Received via mailing list)
unsubscribe


________________________________________
From: Joel Pearson [lists@ruby-forum.com]
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]]
3c3b05d468b6de2505c0a657bcb6da75?d=identicon&s=25 Oli Sanders (rubella)
on 2013-06-05 14:16
Joel Pearson 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 :)
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 :/
Aa082c8b00a50928e5860dcd70bf2368?d=identicon&s=25 Tamara Temple (Guest)
on 2013-06-06 02:02
(Received via mailing list)
Oli Sanders <lists@ruby-forum.com> 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.
7cee9dd780723c5fa0f06a242338e57c?d=identicon&s=25 Douglas Seifert (Guest)
on 2013-06-06 05:02
(Received via mailing list)
My effort here:

https://gist.github.com/seifertd/5718964

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
Please log in before posting. Registration is free and takes only a minute.
Existing account

NEW: Do you have a Google/GoogleMail, Yahoo or Facebook account? No registration required!
Log in with Google account | Log in with Yahoo account | Log in with Facebook account
No account? Register here.