############################################################################## # Set::Array # # See POD at end for documentation. ############################################################################## package Set::Array; use strict; use attributes qw(reftype); use subs qw(foreach pack push pop shift join rindex splice unpack unshift); use Want; use Carp; # Some not documented/implemented. Waiting for Want-0.06 to arrive. use overload "==" => "is_equal", "!=" => "not_equal", "+" => "union", "&" => "bag", "*" => "intersection", "-" => "difference", "%" => "symmetric_difference", "<<" => "push", ">>" => "shift", "<<=" => "unshift", ">>=" => "pop", "fallback" => 1; BEGIN{ use vars qw($VERSION); $VERSION = '0.14'; } sub new{ my($class,@array) = @_; @array = @$class if !@array && ref($class); return bless \@array, ref($class) || $class; } # Turn array into a hash sub as_hash{ my($self,%arg) = @_; $arg{key_order} = 'even' unless $arg{key_order}; if( (scalar(@$self) % 2) != 0 ){ Carp::croak "Odd number of elements in 'as_hash()' call"; } else{ my %hash; if($arg{key_order} eq 'odd'){ %hash = CORE::reverse(@$self) } else{ %hash = @$self } if(want('OBJECT')){ return $self } # This shouldn't happen return %hash if wantarray; return \%hash; } } *to_hash = \&as_hash; # Return element at specified index sub at{ my($self,$index) = @_; if(want('OBJECT')){ return bless \${$self}[$index] } return @$self[$index]; } # Delete (or undef) contents of array sub clear{ my($self,$undef) = @_; if($undef){ @{$self} = map{ undef } @{$self} } else{ @{$self} = () } if(want('OBJECT')){ return $self } if(wantarray){ return @$self } if(defined wantarray){ return \@{$self} } } # Remove all undef elements sub compact{ my($self) = @_; if( (want('OBJECT')) || (!defined wantarray) ){ @$self = grep defined $_, @$self; return $self; } my @temp; CORE::foreach(@{$self}){ CORE::push(@temp,$_) if defined $_ } if(wantarray){ return @temp } if(defined wantarray){ return \@temp } } # Return the number of times the specified value appears within array sub count{ my($self,$val) = @_; my $hits = 0; # Count undefined elements unless(defined($val)){ foreach(@$self){ $hits++ unless $_ } if(want('OBJECT')){ return bless \$hits } return $hits; } $hits = grep /^$val$/, @$self; if(want('OBJECT')){ return bless \$hits } return $hits; } # Delete all instances of the specified value within the array sub delete{ my($self,@vals) = @_; unless(defined($vals[0])){ Carp::croak "Undefined value passed to 'delete()' method"; } foreach my $val(@vals){ @$self = grep $_ !~ /^$val$/, @$self; } if(want('OBJECT')){ return $self } if(wantarray){ return @$self } if(defined wantarray){ return \@{$self} } } # Deletes an element at a specified index, or range of indices # I'm not sure I like the range behavior for this method and may change it # (or remove it) in the future. sub delete_at{ my($self,$start_index, $end_index) = @_; unless(defined($start_index)){ Carp::croak "No index passed to 'delete_at()' method"; } unless(defined($end_index)){ $end_index = 0 } if( ($end_index eq 'end') || ($end_index == -1) ){ $end_index = $#$self } my $num = ($end_index - $start_index) + 1; CORE::splice(@{$self},$start_index,$num); if(want('OBJECT') || !(defined wantarray)){ return $self } if(wantarray){ return @$self } if(defined wantarray){ return \@{$self} } } # Returns a list of duplicate items in the array. sub duplicates{ my($self) = @_; my(@dups,%count); if(want('OBJECT') || !(defined wantarray)){ my %count; CORE::foreach(@$self){ $count{$_}++; CORE::push(@dups,$_) if $count{$_} > 1; } @$self = @dups; return $self; } CORE::foreach(@$self){ $count{$_}++; if($count{$_} > 1){ CORE::push(@dups,$_) } } if(wantarray){ return @dups } if(defined wantarray){ return \@dups } } # Tests to see if value exists anywhere within array sub exists{ my($self,$val) = @_; # Check specifically for undefined values unless(defined($val)){ foreach(@$self){ unless($_){ return 1 } } return 0; } if(grep /^$val$/, @$self){ return 1 } return 0; } *contains = \&exists; # Fills the elements of the array. Does not create new elements sub fill{ my($self,$val, $start, $length) = @_; # Start may also be a range return unless(scalar(@{$self}) > 0); # Test for empty array unless(defined($start)){ $start = 0 } if($length){ $length += $start } else{ $length = $#$self + 1} if($start =~ /^(\d)\.\.(\d)$/){ CORE::foreach($1..$2){ @{$self}[$_] = $val } return $self; } CORE::foreach(my $n=$start; $n<$length; $n++){ @{$self}[$n] = $val } if(want('OBJECT')){ return $self } if(wantarray){ return @$self } if(defined wantarray){ return \@{$self} } } # Returns the first element of the array sub first{ my($self) = @_; if(want('OBJECT')){ return bless \@{$self}[0] } return @{$self}[0]; } # Flattens any list references into a plain list sub flatten{ my($self) = @_; if( (want('OBJECT')) || (!defined wantarray) ){ for(my $n=0; $n<=$#$self; $n++){ if( ref($$self[$n]) eq 'ARRAY' ){ CORE::splice(@$self,$n,1,@{$$self[$n]}); $n--; next; } if( ref($$self[$n]) eq 'HASH' ){ CORE::splice(@$self,$n,1,%{$$self[$n]}); --$n; next; } } return $self } my @temp = @$self; for(my $n=0; $n<=$#temp; $n++){ if( ref($temp[$n]) eq 'ARRAY' ){ CORE::splice(@temp,$n,1,@{$temp[$n]}); $n--; next; } if( ref($temp[$n]) eq 'HASH' ){ CORE::splice(@temp,$n,1,%{$temp[$n]}); --$n; next; } } if(wantarray){ return @temp } if(defined wantarray){ return \@temp } } # Loop mechanism sub foreach{ my($self,$coderef) = @_; unless(ref($coderef) eq 'CODE'){ Carp::croak "Invalid code reference passed to 'foreach' method"; } CORE::foreach (@$self){ &$coderef } if(want('OBJECT')){ return $self } if(wantarray){ return @$self } if(defined wantarray){ return \@{$self} } } # Append or prepend a string to each element of the array sub impose{ my($self,$placement,$string) = @_; # Set defaults unless($placement =~ /\bappend\b|\bprepend\b/i){ $string = $placement; $placement = 'append'; } unless(CORE::defined($string)){ Carp::croak "No string supplied to 'impose()' method"; } if(want('OBJECT') or !(defined wantarray)){ if($placement =~ /append/){ foreach(@$self){ $_ = $_ . $string } } if($placement =~ /prepend/){ foreach(@$self){ $_ = $string . $_ } } return $self; } my @copy = @$self; if($placement =~ /append/){ foreach(@copy){ $_ = $_ . $string } } if($placement =~ /prepend/){ foreach(@copy){ $_ = $string . $_ } } if(wantarray){ return @copy } if(defined wantarray){ return \@copy } } # Returns the index of the first occurrence within the array # of the specified value sub index{ my($self,$val) = @_; # Test for undefined value unless(defined($val)){ for(my $n=0; $n<=$#$self; $n++){ unless($self->[$n]){ if(want('OBJECT')){ return bless \$n } if(defined wantarray){ return $n } } } } for(my $n=0; $n<=$#$self; $n++){ next unless defined $self->[$n]; if( $self->[$n] =~ /$val/ ){ if(want('OBJECT')){ return bless \$n } if(defined wantarray){ return $n } } } return undef; } # Given an index, or range of indices, returns the value at that index # (or a list of values for a range). sub indices{ my($self,@indices) = @_; my @iArray; unless(defined($indices[0])){ Carp::croak "No index/indices passed to 'indices' (aka 'get') method"; } CORE::foreach(@indices){ if($_ =~ /(\d)\.\.(\d)/){ for($1..$2){ CORE::push(@iArray,@{$self}[$_]) }; next; } if(@{$self}[$_]){ CORE::push(@iArray,@{$self}[$_]) } else{ CORE::push(@iArray,undef) } } if(scalar(@iArray) == 1){ if(want('OBJECT')){ return bless \$iArray[0] } return $iArray[0]; } if(want('OBJECT')){ return bless \@iArray } if(wantarray){ return @iArray } if(defined wantarray){ return \@iArray } } # Alias for 'indices()' *get = \&indices; # Tests to see if array contains any elements sub is_empty{ my($self) = @_; if( (scalar @{$self}) > 0){ return 0 } return 1; } # Set a specific index to a specific value sub set{ my($self,$index,$val) = @_; unless(defined($index) && $val){ Carp::croak "No index or value passed to 'set()' method"; } if(want('OBJECT')){ $self->[$index] = $val; return $self; } my @copy = @$self; $copy[$index] = $val; if(wantarray){ return @copy } if(defined wantarray){ return \@copy } } # Joins the contents of the list with the specified character sub join{ my($self,$char) = @_; $char = ',' unless $char; my $string; if(want('OBJECT')){ $string = CORE::join($char,@$self); return bless \$string; } $string = CORE::join($char,@$self); return $string; } # Returns the last element of the array sub last{ my($self) = @_; if(want('OBJECT')){ return bless \@{$self}[-1] } return @$self[-1]; } # Returns the number of elements within the array sub length{ my($self) = @_ ; my $length = scalar(@$self); if(want('OBJECT')){ return bless \$length } return $length; } # Returns the maximum numerical value in the array sub max{ my($self) = @_; my $max; no warnings 'uninitialized'; CORE::foreach(@{$self}){ $max = $_ if $_ > $max } if(want('OBJECT')){ return bless \$max } return $max; } sub pack{ my($self,$template) = @_; Carp::croak "No template provided to 'pack()' method" unless $template; if(want('OBJECT') || !(defined wantarray)){ $self->[0] = CORE::pack($template, @$self); $#$self = 0; return $self; } return CORE::pack($template,@$self); } # Pops and returns the last element off the array sub pop{ my($self) = @_; my $popped = CORE::pop(@$self); if(want('OBJECT')){ return bless \$popped } return $popped; } # Prints the contents of the array as a flat list. Optional newline sub print{ my($self,$nl) = @_; if(reftype($self) eq 'ARRAY'){ if(wantarray){ return @$self } if(defined wantarray){ return \@{$self} } print @$self; if($nl){ print "\n" } } elsif(reftype($self) eq 'SCALAR'){ if(defined wantarray){ return $$self } print $$self; if($nl){ print "\n" } } else{ print @$self; if($nl){ print "\n" } } return $self; } # Pushes an element onto the end of the array sub push{ my($self,@list) = @_; CORE::push(@{$self},@list); if(want('OBJECT')){ return $self } if(wantarray){ return @$self } if(defined wantarray){ return \@{$self} }; } # Randomizes the order of the contents of the array # Taken from "The Perl Cookbook" sub randomize{ my($self) = @_; my($i,$ref,@temp); unless( (want('OBJECT')) || (!defined wantarray) ){ @temp = @{$self}; $ref = \@temp; } else{ $ref = $self } for($i = @$ref; --$i; ){ my $j = int rand ($i+1); next if $i == $j; @$ref[$i,$j] = @$ref[$j,$i]; } if(want('OBJECT')){ return $self } if(wantarray){ return @temp } if(defined wantarray){ return \@temp } } # Reverses the contents of the array sub reverse{ my($self) = @_; if( (want('OBJECT')) || (!defined wantarray) ){ @$self = CORE::reverse @$self; return $self; } my @temp = CORE::reverse @$self; if(wantarray){ return @temp } if(defined wantarray){ return \@temp } } # Same as index, except that it returns the position of the # last occurrence, instead of the first. sub rindex{ my($self,$val) = @_; # Test for undefined value unless(defined($val)){ for(my $n = $#$self; $n >= 0; $n--){ unless($self->[$n]){ if(want('OBJECT')){ return bless \$n } if(defined wantarray){ return $n } } } } for(my $n = $#$self; $n >= 0; $n--){ next unless defined $self->[$n]; if( $self->[$n] =~ /$val/ ){ if(want('OBJECT')){ return bless \$n } if(defined wantarray){ return $n } } } return undef; } # Moves the last element of the array to the front, or vice-versa sub rotate{ my($self,$dir) = @_; if( (want('OBJECT')) || (!defined wantarray) ){ unless(defined($dir) && $dir eq 'ftol'){ CORE::unshift(@$self, CORE::pop(@$self)); return $self; } CORE::push(@$self,CORE::shift(@$self)); return $self; } my @temp = @$self; unless(defined($dir) && $dir eq 'ftol'){ CORE::unshift(@temp, CORE::pop(@temp)); if(wantarray){ return @temp } if(defined wantarray){ return \@temp } } CORE::push(@temp,CORE::shift(@temp)); if(wantarray){ return @temp } if(defined wantarray){ return \@temp } } # Shifts and returns the first element off the array sub shift{ my($self) = @_; my $shifted = CORE::shift @$self; if(want('OBJECT')){ return bless \$shifted } return $shifted; } # Sorts the array alphabetically. sub sort{ my($self,$coderef) = @_; if($coderef){ # Complements of Sean McAfee my $caller = caller(); local(*a,*b) = do{ no strict 'refs'; (*{"${caller}::a"},*{"${caller}::b"}); }; if( (want('OBJECT')) || (!defined wantarray) ){ @$self = CORE::sort $coderef @$self; return $self; } my @sorted = CORE::sort $coderef @$self; if(wantarray){ return @sorted } if(defined wantarray){ return \@sorted } } else{ if( (want('OBJECT')) || (!defined wantarray) ){ @$self = CORE::sort @$self; return $self; } my @sorted = CORE::sort @$self; if(wantarray){ return @sorted } if(defined wantarray){ return \@sorted } } } # Splices a value, or range of values, from the array sub splice{ my($self,$offset,$length,@list) = @_; no warnings 'uninitialized'; my @deleted; unless(defined($offset)){ @deleted = CORE::splice(@$self); if(want('OBJECT')){ return $self } if(wantarray){ return @deleted } if(defined wantarray){ return \@deleted } } unless(defined($length)){ @deleted = CORE::splice(@$self,$offset); if(want('OBJECT')){ return $self } if(wantarray){ return @deleted } if(defined wantarray){ return \@deleted } } unless(defined($list[0])){ @deleted = CORE::splice(@$self,$offset,$length); if(want('OBJECT')){ return $self } if(wantarray){ return @deleted } if(defined wantarray){ return \@deleted } } @deleted = CORE::splice(@$self,$offset,$length,@list); if(want('OBJECT')){ return $self } if(wantarray){ return @deleted } if(defined wantarray){ return \@deleted } } # Returns a list of unique items in the array sub unique{ my($self,$nc) = @_; my %item; CORE::foreach(@$self){ $item{$_}++ } if(want('OBJECT') || !(defined wantarray)){ @$self = keys %item; %item = (); return $self; } my @temp = keys %item; %item = (); if(wantarray){ return @temp } if(defined wantarray){ return \@temp } } # Unshifts a value to the front of the array sub unshift{ my($self,@list) = @_; CORE::unshift(@$self,@list); if(want('OBJECT')){ return $self } if(wantarray){ return @$self } if(defined wantarray){ return \@{$self} }; } #### OVERLOADED OPERATOR METHODS #### # Really just a 'push', but needs to handle ops sub append{ my($op1, $op2, $reversed) = @_; ($op2,$op1) = ($op1,$op2) if $reversed; CORE::push(@{$op1},@{$op2}); if(want('OBJECT')){ return $op1 } return @$op1 if wantarray; return \@{$op1} if defined wantarray; } # A union that includes non-unique values (i.e. everything) sub bag{ my($op1, $op2, $reversed) = @_; ($op2,$op1) = ($op1,$op2) if $reversed; if(want('OBJECT') || !(defined wantarray)){ CORE::push(@$op1,@$op2); return $op1; } my @copy = (@$op1,@$op2); return @copy if wantarray; return \@copy if defined wantarray; } # Needs work sub complement{ my($op1, $op2, $reversed) = @_; ($op2,$op1) = ($op1,$op2) if $reversed; my(%item1,%item2,@comp); CORE::foreach(@$op1){ $item1{$_}++ } CORE::foreach(@$op2){ $item2{$_}++ } CORE::foreach(keys %item2){ if($item1{$_}){ next } CORE::push(@comp,$_); } if(want('OBJECT')){ return bless \@comp } if(wantarray){ return @comp } if(defined wantarray){ return \@comp } } # Returns elements in left set that are not in the right set sub difference{ my($op1, $op2, $reversed) = @_; ($op2,$op1) = ($op1,$op2) if $reversed; my(%item1,%item2,@diff); CORE::foreach(@$op1){ $item1{$_}=$_ } CORE::foreach(@$op2){ $item2{$_}=$_ } CORE::foreach(keys %item1){ if(exists $item2{$_}){ next } CORE::push(@diff,$item1{$_}); } if(want('OBJECT') || !(defined wantarray)){ @$op1 = @diff; return $op1; } if(wantarray){ return @diff } if(defined wantarray){ return \@diff } } # Returns the elements common to both arrays sub intersection{ my($op1, $op2, $reversed) = @_; ($op2,$op1) = ($op1,$op2) if $reversed; my(%count,@int); @count{@$op1} = (1) x @$op1; if(want('OBJECT') || !(defined wantarray)){ @$op1 = CORE::grep{CORE::delete $count{$_}} @$op2; return $op1; } @int = CORE::grep{CORE::delete $count{$_}} @$op2; if(wantarray){ return @int } if(defined wantarray){ return \@int } } # Tests to see if arrays are equal (regardless of order) sub is_equal{ my($op1, $op2, $reversed) = @_; ($op2,$op1) = ($op1,$op2) if $reversed; my(%count1, %count2); if(scalar(@$op1) != scalar(@$op2)){ return 0 } CORE::foreach(@$op1){ $count1{$_}++ } CORE::foreach(@$op2){ $count2{$_}++ } CORE::foreach my $key(keys %count1){ return 0 unless CORE::defined($count1{$key}); return 0 unless CORE::defined($count2{$key}); if($count1{$key} ne $count2{$key}){ return 0 } } return 1; } # Tests to see if arrays are not equal (order ignored) sub not_equal{ my($op1, $op2, $reversed) = @_; ($op2,$op1) = ($op1,$op2) if $reversed; my(%count1, %count2); if(scalar(@$op1) != scalar(@$op2)){ return 1 } CORE::foreach(@$op1){ $count1{$_}++ } CORE::foreach(@$op2){ $count2{$_}++ } CORE::foreach my $key(keys %count1){ return 1 unless CORE::defined($count1{$key}); return 1 unless CORE::defined($count2{$key}); if($count1{$key} ne $count2{$key}){ return 1 } } return 0; } # Returns elements in one set or the other, but not both sub symmetric_difference{ my($op1, $op2, $reversed) = @_; ($op2,$op1) = ($op1,$op2) if $reversed; my(%count1,%count2,%count3,@symdiff); @count1{@$op1} = (1) x @$op1; @count2{@$op2} = (1) x @$op2; CORE::foreach(CORE::keys %count1,CORE::keys %count2){ $count3{$_}++ } if(want('OBJECT') || !(defined wantarray)){ @$op1 = CORE::grep{$count3{$_} == 1} CORE::keys %count3; return $op1; } @symdiff = CORE::grep{$count3{$_} == 1} CORE::keys %count3; if(wantarray){ return @symdiff } if(defined wantarray){ return \@symdiff } } *sym_diff = \&symmetric_difference; # Returns the union of two arrays, non-unique values excluded sub union{ my($op1, $op2, $reversed) = @_; ($op2,$op1) = ($op1,$op2) if $reversed; my %union; CORE::foreach(@$op1, @$op2){ $union{$_}++ } if(want('OBJECT') || !(defined wantarray)){ @$op1 = CORE::keys %union; return $op1; } my @union = CORE::keys %union; if(wantarray){ return @union } if(defined wantarray){ return \@union } } 1; __END__ =head1 NAME Set::Array - Arrays as objects with lots of handy methods (including Set comparisons) and support for method chaining. =head1 SYNOPSIS C<< my $sao1 = Set::Array->new(1,2,4,"hello",undef); >> C<< my $sao2 = Set::Array->new(qw(a b c a b c)); >> C<< print $sao1->length; # prints 5 >> C<< $sao2->unique->length->print; # prints 3 >> =head1 PREREQUISITES Perl 5.6 or later The 'Want' module by Robin Houston. Available on CPAN. =head1 DESCRIPTION Set::Array allows you to create arrays as objects and use OO-style methods on them. Many convenient methods are provided here that appear in the FAQ's, the Perl Cookbook or posts from comp.lang.perl.misc. In addition, there are Set methods with corresponding (overloaded) operators for the purpose of Set comparison, i.e. B<+>, B<==>, etc. The purpose is to provide built-in methods for operations that people are always asking how to do, and which already exist in languages like Ruby. This should (hopefully) improve code readability and/or maintainability. The other advantage to this module is method-chaining by which any number of methods may be called on a single object in a single statement. =head1 OBJECT BEHAVIOR The exact behavior of the methods depends largely on the calling context. B: * If a method is called in void context, the object itself is modified. * If the method called is not the last method in a chain (i.e. it's called in object context), the object itself is modified by that method regardless of the 'final' context or method call. * If a method is called in list or scalar context, a list or list refererence is returned, respectively. The object itself is B modified. Here's a quick example: C<< my $sao = Set::Array->new(1,2,3,2,3); >> C<< my @uniq = $sao->unique(); # Object unmodified. '@uniq' contains 3 values. >> C<< $sao->unique(); # Object modified, now contains 3 values >> B: * Methods that report a value, such as boolean methods like I or other methods such as I or I, never modify the object. * The methods I, I, I, and I will B modify the object. It seemed much too counterintuitive to call these methods in any context without actually deleting/clearing/substituting the items! * The methods I and I will modify the object B return the value that was shifted or popped from the array. Again, it seemed much too counterintuitive for something like C<$val = $sao-Eshift> to return a value while leaving the object's list unchanged. If you really want the first or last value without modifying the object, you can always use the I or I method, respectively. * The I method always returns a string and is really meant for use in conjunction with the I method. =head1 BOOLEAN METHODS BIB<)> - Returns 1 if I exists within the array, 0 otherwise. If no value (or I) is passed, then this method will test for the existence of undefined values within the array. B - Returns 1 if the array is empty, 0 otherwise. Empty is defined as having a length of 0. =head1 STANDARD METHODS BIB<)> - Returns the item at the given index (or I). A negative index may be used to count from the end of the array. If no value (or I) is specified, it will look for the first item that is not defined. B - Empties the array (i.e. length becomes 0). You may pass a '1' to this method to set each element of the array to I rather than truly empty it. B - Removes undefined elements from the array. BIB<)> - Returns the number of instances of I within the array. If I is not specified (or is I), the method will return the number of undefined values within the array. BIB<)> - Deletes all items within I from the array that match. This method will crash if I is not defined. If your goal is to delete undefined values from your object, use the I method instead. BIB<)> - Deletes the item at the specified index. If a second index is specified, a range of items is deleted. You may use -1 or the string 'end' to refer to the last element of the array. B - Returns a list of N-1 elements for each element N in the set. For example, if you have set "X X Y Y Y", this method would return a the list "X Y Y". BIB<)> - Sets the selected elements of the array (which may be the entire array) to I. The default value for start is 0. If length is not specified the entire array, however long it may be at the time of the call, will be filled. Alternatively, a quoted integer range may be used. e.g. C<< $sao->fill('x','3-5'); >> The array length/size may not be expanded with this call - it is only meant to fill in already-existing elements. B - Returns the first element of the array (or undef). B - Causes a one-dimensional flattening of the array, recursively. That is, for every element that is an array (or hash, or a ref to either an array or hash), extract its elements into the array. e.g. C<< my $sa = Set::Array-Enew([1,3,2],{one=>'a',two=>'b'},x,y,z); >> C<< $sao-Eflatten->join(',')->print; # prints "1,3,2,one,a,two,b,x,y,z" >> BIB<)> - Iterates over an array, executing the subroutine for each element in the array. If you wish to modify or otherwise act directly on the contents of the array, use B<$_> within your sub reference. e.g. To increment all elements in the array by one... C<< $sao-Eforeach(sub{ ++$_ }); >> B - Alias for the B method. BIB<)> - Returns the index of the first element of the array object that contains I. Returns I if no value is found. Note that there is no dereferencing here so if you're looking for an item nested within a ref, use the I method first. BIB<)> - Returns an array consisting of the elements at the specified indices or I if the element is out of range. A range may also be used. It must be a quoted string in '0..3' format. BIB<)> - Joins the individual elements of the list into a single string with the elements separated by the value of I. Useful in conjunction with the I method. If no character is specified, then I defaults to a comma. e.g. C<< $sao-Ejoin('-')-Eprint; >> B - Returns the last element of the array (or I). B - Returns the number of elements within the array. B - Returns the maximum value of an array. No effort is made to check for non-numeric data. BI