Click to See Complete Forum and Search --> : recursive programming


ukndoit
05-20-2010, 05:44 AM
I am trying desperately to get this done. I have to pre-populate some fields for a new program we are opening, after that is done then the system will be automated upon new people registering.

We have a network of users that refer other users whom refer other users and so on and so forth.

What I want to do is climb down the array of people that any given person referrs and then climb it all the way down to the bottom and back up.

for instance, John doe refers Jane doe and Bob doe.
Bob doe refers Tom, Dick and Harry.
Dick refers Mary and Larry.
Mary refers Mike.
Mike refers Chuck.
Chuck refers Tommy
Larry refers Michelle
xxxx
xxx
xx
x


kind of confusing, but should be easy to climb down from a programming point of view.

What I want to do is like this:


$sth = $dbh->prepare(qq{select * from `registered_users` where ## requirements go here ##});
$sth->execute(##placeholderValuesHere##);
while($rows = $sth->fetchrow_hashref()) {
$_thsMbrId = $rows->{membersid};
&performTask($_thsMbrId,$_thsMbrId,"##OtherFieldHere##",0);
}
$sth->finish();

sub performTask {
my($_t1field,$_t2field,$_t3field,$_level) = @_;
$_level++;
$sth2 = $dbh->prepare(qq{select * from `registered_users` where `referrersid` = ?});
$sth2->execute($_t2field);
while(my $trow = $sth2->fetchrow_hashref()) {
my $_ths2MbrId = $trow->{membersid};
#update table to set the flag I need set
&performTask($_t1field,$_ths2MbrId,"##OtherFieldHere##",$_level);
# Ok, it should be back now from getting all the people this person referred.
# Do something and then return
}
return 1;
}


Should not every time I call the subroutine performTask from within the same subroutine, it start anew, and then when it returns be where it was when it started itself over?

Do you understand what I am trying to do?
Is there a way to do it that I don't see here?
I cannot get this to work in this format. It seems to start working but it never finishes.

(All the code is an example, I wrote it just to show what I am trying to do, the code I am actually trying to do, is very large and would probably complicate things further. I just need you the understand the jist of what I am trying to do)

Thanks,
Richard

Sixtease
05-21-2010, 04:16 AM
I've not taken the time to understand your problem into detail, but schematically, I'd do something like this... assuming I have a function get_referred that gives me all persons that a given person refers to.

my $start = some_person(); # this is the person whose "social network" you want to get
my @result;
my %people_found;
my @buffer = ({ person => $start, level => 0 });

PERSON:
while (@buffer) {
my $entry = shift @buffer;
my $person = $entry->{person};
my $new_level = $entry->{level}+1;
push @result, $entry;
%people_found{ $person } = 1;
my @batch = get_referred($person);
CANDIDATE:
for my $candidate (@batch) {
next CANDIDATE if exists $people_found{ $candidate };
push @buffer, { person => $candidate, level => $new_level };
$people_found{ $candidate } = 1;
}
}


This is pretty much BFS -- if the persons are connected like this:

bob -> mary -> john
-> mike -> kate
-> kevin -> mary

then the @result array will contain
[
{ person => 'bob', level => 0 },
{ person => 'mary', level => 1 },
{ person => 'mike', level => 1 },
{ person => 'john', level => 2 },
{ person => 'kate', level => 2 },
{ person => 'kevin', level => 2 },
]

I didn't test this, but it could give you an idea. If you run into problems, let me know.
HTH

ukndoit
05-21-2010, 07:58 AM
Sorry, that does not do what I need it to.

:(

Here is an example:

------------------------------------------------------------------------------------
| members_id | Name | referrer_id | rank1 | manager_array | president_array
------------------------------------------------------------------------------------
| 20 | John | 1 | president | NULL | NULL
------------------------------------------------------------------------------------
| 21 | Jane | 20 | manager | 20 | 20
------------------------------------------------------------------------------------
| 22 | Tom | 20 | NULL | 21 | 20
------------------------------------------------------------------------------------
| 23 | Dick | 21 | manager | 21 | 20
------------------------------------------------------------------------------------
| 24 | Harry | 21 | NULL | 23 | 20
------------------------------------------------------------------------------------
| 25 | Sandy | 23 | NULL | 23 | 20
------------------------------------------------------------------------------------
| 26 | Charles | 25 | NULL | 23 | 20
------------------------------------------------------------------------------------


This is kind of confusing huh...

Ok, here is how it works.
Let's say you joined us and you referred a lot of people and were making like 5k per month from us, and we reward you by making you a manager over your group. Now everyone that you referred and that all those referred and so on, we would not have any of them if you did not refer the first ones. So we reward you instead of spending the money on advertising, because you referred people to us so we want to thank you.
So you are now a manager, however, you are not yet a president. So, everyone that is in your referred organization would be 'coded' to you as manager, but not president.
all those people that are coded to you as manager would have the same president as you until you became a president.

So, what I need to do, is do this.

Select * from registered_users_table where rank1 = manager
then for each of those 'managers' I want to:
select * from registered_users_table where referrer_id = managersAutoIncrementId
So, now while I am looking at these people, I need to know if they are also a manager, if so, then I do not want to check the people they referred, but the person I am looking at even though they are a manger, they can be coded to the manager I am already looking down their referred organization.
If they are NOT a manager then after I 'code' this person to the manager I am looking down at, then I can go to anyone they referred since it is in the organization of the manager I am looking at. So, I would need to start over:
select * from registered_users_table where referrer_id = membersIdwasLookingatFromLevel1OfManagerIamWorkingOn

and then it would repeat the process, if this person is a manager I can code them to the manager I am looking at, but do not check the people they referred since they will be coded to this person here...
If this person is NOT a manager then after coding them to the manager I'm working on go check their referred organization.

Repeat on their referred...

all the way down to the bottom.


very complicated but should not be.

When you make a subroutine, you should be able to call it from the middle of it and it should fork a new process so that it can work until you call return, at which point it would go back and continue where it *was*, so if you do this:


sub something {
($_startingId,$_rank,$_level) = @_;
}


then those variable ($_startingId,$_rank,$_level) *should* all be different it should not keep the value that you passed it once you call return, it should 'return' the script to where it was before, so if you do this:


sub something {
($_startingId,$_rank,$_level) = @_;
$_level++;
print qq~$_startingId,$_rank,$_level\n~;
if($_level < 2) {
&something($_startingId,$_rank,$_level);
} else {
return 1;
}
print $_level . "\n";
}

Then when it should print 1 last... if you send it level 0 when you send it.

anyhow, thank you for your time in what you tried to do, I hope I explained what I am trying to do good enough.

Sixtease
05-21-2010, 08:07 AM
Yes, that looks complicated. Now we have this nice example table. So say we'll start with Jane #21, who happens to be a manager. Tell me exactly which people you want to get for her.

ukndoit
05-21-2010, 08:15 AM
Well, if I did:
select * from registered_users_table where referrer_id = "21" [janes id]
then for that first check it would get:

Dick id 23 and
Harry id 24

then since Dick is not a manager it would check his people:
select * from registered_users_table where referrer_id = "23" (Dicks id)
and that would result in:
Sandy id 25


so it would check a total of two levels down... that is just according to that example...
One of our customers has referred like 1028 people now, and there are like 40k+ in her referred organization, she is a manager, so it should set at least all 1028 of her personally referred people as coded to her. a few of those 1028 are managers, the rest it should climb down their referred oganization too, until it reached another manager and code all of them to her.

understand now?
hth

Sixtease
05-21-2010, 08:28 AM
So if I understand it correctly, for one "seed" person, you want their whole subtree, except the subtrees of people marked as managers.

If we start with the person at the top, and purple points denote managers, then what you want are the people with red crosses, right?

Graph image (http://sixtease.net/static/junk/wd-grapf.png)

ukndoit
05-21-2010, 08:39 AM
right...

it should climb down their whole subtree, and when it reaches another manager, only code that manager to the manager it is working on, but do not climb down the manager it just found...

This could climb down like 200 levels for one person, if there are no managers.

So, I don't want to create like 500 different subroutines, to make sure it gets enough of them. I thought about creating two of them, where it would pass them back and forth to go down as far as it must.

There are currently like 15 managers or so in our database.
no presidents yet.

So, the first part, not in a subroutine would be:


# real table info:
$sth = $dbh->prepare(qq{select * from `members` where `rankedManager` = "1"});
$sth->execute;
while($_mbrInfo = $sth->fetchrow_hashref()) {
&checkMemberTree($_mbrInfo->{MemberId},$_mbrInfo->{MemberId},'manager',0);
}
$sth->finish();


So for each different manager, it would go to check their subtree then start climbing down.

I am self taught in Perl, since I did not go to college or a skill training class to learn it, there is much I have not learned about Perl.

I think there is a way to do this:

$_thisRoutine = sub {

};


Then I could put the whole of the contents in that code and then call it this way, I think:


$sth = $dbh->prepare(qq{select * from `members` where `rankedManager` = "1"});
$sth->execute;
while($_mbrInfo = $sth->fetchrow_hashref()) {
&$_thisRoutine($_mbrInfo->{MemberId},$_mbrInfo->{MemberId},'manager',0);
}
$sth->finish();


I think I have seen code like that in my books or somewhere.
Not sure.

anyhow, I still cannot figure out why my code does not complete, it gets through like 2014 people in the database then for some reason just stops.
So then I made some changes and now it only gets like 414 people then stops for some unknown reason.

So I scrapped the code and am going to start over.

Sixtease
05-21-2010, 09:04 AM
Then you just need this little modification of my original code:


sub get_referred_persons_of {
my ($start) = @_; # this is the person whose subtree you want to get
my @result;
my %people_found;
my @buffer = ({ person => $start, level => 0 });

PERSON:
while (@buffer) {
my $entry = shift @buffer;
my $person = $entry->{person};
my $new_level = $entry->{level}+1;
push @result, $entry;
%people_found{ $person } = 1;
my @batch = get_referred($person);
CANDIDATE:
for my $candidate (@batch) {
next CANDIDATE if exists $people_found{ $candidate };
next CANDIDATE if is_manager($candidate);
push @buffer, { person => $candidate, level => $new_level };
$people_found{ $candidate } = 1;
}
}

return \@result
}


The thing to do is implement the is_manager and get_referred subroutines. They will be something like this:


sub is_manager {
my ($person) = @_;
my $rank = $dbh->do('SELECT rank1 FROM members WHERE id = ?', $person);
return $rank eq 'manager'
}

sub get_referred {
my ($person) = @_;
my @rv = $dbh->do('SELECT members_id FROM members WHERE referrer_id = ?');
return @rv
}

ukndoit
05-21-2010, 09:25 AM
I got it to work. What I did, was first loop through each person referred, updating all of them to be coded to the manager, then before I go to the next if they are not a manager I add their id to an array...

after it finishes updating all of their coding, I then do a foreach on the array and run into a new subroutine and repeat the process.

Doing that fixed it.
It updated nearly all of them to the correct manager. I verified it. Awesome.

Thank you for all you did.