February 15, 2008
Lecture 19
- Quiz 1 sample solutions are online
- Grading will be later (don't worry about it, the quiz was too long)
- Homework 2 grading is in progress
- Homework 3 will be out soon
- Today: array and hash problems

We're continuing to think about arrays.  On Monday, we did the union problem (combining two lists without duplicates).
Another common problem is the intersection problem (combining two lists to find the common items between the two).

Intersection: 
	We have arrays @A1 and @A2.
	We want to produce @A3 with elements that are common to both @A1 and @A2.

This is useful biology-wise when we want to know if, say, the same gene has come up in two separate assays.

How do we do it?

We could use: 
foreach $V(@A1) {
	$Intersect = 0; 
	foreach $V2 (@A2) {
		if ($V eq $V2) {
			$Intersect = 1;
	}
}
if ($Intersect) {
	push @A3, $V;
}
}

This checks the list for each element of @A1, comparing them to each element of @A2.  When they are the same, we change the $Intersect variable to say that it should be in the list.  If $Intersect equals 1 at end of list, we know that $V should be in the list.

@A1: ted, ann, ted
@A2: george, ted
@A3 should be ted, but it will come out as: ted, ted
This is because it finds a match for the first element of @A1, so ted is added to the list.  On the second loop, nothing is added to the list because $Intersect remains 0 (since there are no common elements).  In the third loop, ted matches ted again, so $Intersect is once again put to 1, so ted is again added to the list.

To solve this problem, we could add a check before the "foreach" loop or within the first "if" to check if the element of @A1 is already in @A3, or we could let the duplicates be put into @A3 and remove them at a later time.

Removing duplicates:

Ex:  If @A1 is an array, potentially with duplicates, produce @A2 without duplicates.

@A1 is ted, ann, ted, george, frank

We could do:
@A2 = ();
for ($Count1 = 0; $Count1 < $A1; $Count) {
	$V = $A1[$Count];
# Check that $V not on list @A1 earlier
	$NotEarlier = 1;
	for ($Count2 = 0; $Count2 < $Count1; $Count2++) {
		if ($V eq $A1[$Count2]) {
			$NotEarlier = 0;
			}
		}
# Add $V to @A2 if NotEarlier
	if ($NotEarlier) {
		push @A2, $V;
	}
} 

$A1 is the length of the array (assigning an array to a scalar produces its length). 
Count counts through elements and for each of them, $V is assigned to the string.
For the first "ted", the test is false so the second "for" loop is never executed.  "ted" is put on the list.  "ann" is put on the list next, but the next "ted" is not put on the list.  "george" and "frank" are both put on the list.

We could also loop through every element of @A1 and add to @A2 only if the element is not already on @A2 (done with two "foreach"s).  This was written in RemDup2.pl.  Another method was shown in RemDup3.pl.
$IsInA2 = $IsInA2 || ($V eq $V2) is a briefer way of writing the code.

You could also use hashes to remove duplicates very efficiently.

Removing duplicates with hashes:

@A1 = (ted, ann, ted, frank);
%H = ();
foreach $V (@A1) {
	$H { $V } = 1;
# The 1 is a random value and we use $H rather than %H because we're assigning a scalar value to it

Hash table:

Key	Value	
ted	1	
ann	1	
frank	1	

The second "ted, 1" will not be added because "ted" is already a key.  Even if you tried "ted, 2" it would not work because the key is still "ted".

Now we can take the keys out an print an array (though they will come out in a different order than they went in with).  This was put into a program in class.

Problems to think about over the weekend:

- Given an array of numbers @A1, find the second largest number of the array.
OR
- Given @A1 and any integer $N, find the $Nth biggest number in @A1.