

sub initial_array {

	check_prototype(@_, '$(\&|$|\@)?');
	
	my $size = shift;
	my $value = shift || undef;
	if(is_code_ref($value)) {
		return sapply([1..$size], $value);
	}
	else {
		return repeat($value, $size);
	}
}


sub initial_matrix {

	check_prototype(@_, '$$(\&|$)?');
	
	my $n_row = shift;
	my $n_col = shift;
	my $value = shift || undef;
	
	my $mat = initial_array($n_row);
	if(is_code_ref($value)) {
		return sapply($mat, sub {sapply([1..$n_col], $value)});
	}
	else {
		return sapply($mat, sub {repeat($value, $n_col)});
	}
}


sub order {

	check_prototype(@_, '\@(\&)?');
	
    my $array = shift;
    my $function = shift || sub {$_[0] <=> $_[1]}; 

    my $order = [];
	@$order = sort { $function->($array->[$a], $array->[$b]) } 0..$#$array;
    return $order;
}

sub rank {
	
	check_prototype(@_, '\@(\&)?');
	
	my $order = order(@_);
	my $array = shift(@_);
	my $rank = [];
	my $same = [];
	my $index = [];
	for($i = 0; $i < len($order); $i ++) {
		
		if($i != 0 and ($array->[$order->[$i]] != $array->[$order->[$i - 1]])) {
			foreach (@$index) {
				$rank->[$_] = mean($same);
			}
			$same = [];
			$index = [];
		}
		
		push(@$same, $i+1);
		push(@$index, $order->[$i]);
		
	}
	
	foreach (@$index) {
		$rank->[$_] = mean($same);
	}
	return $rank;
}


sub sort_array {
	
	check_prototype(@_, '\@(\&)?');
	
	my $array = shift;
    my $function = shift || sub {$_[0] <=> $_[1]}; 
    
	return [sort {$function->($a, $b)} @$array];
}


sub reverse_array {
	
	check_prototype(@_, '\@');
	
    my $array = shift;
    
	return [reverse(@$array)];
}


sub repeat {
	
	check_prototype(@_, '($|\$|\@|\%)$$?');
	
    my $value = shift;
    my $size = shift;
	my $need_copy = shift;
	$need_copy = defined($need_copy) ? $need_copy : 1;
    
    my $array = [];
	if(is_ref_ref(\$value)) {
		for(my $i = 0; $i < $size; $i ++) {
			if($need_copy) {
				push(@$array, copy($value));
			}
			else {
				push(@$array, $value);
			}
		}
	}
	else {
		for(my $i = 0; $i < $size; $i ++) {
			push(@$array, $value);
		}
	}
    return $array;
}

sub rep {

	check_prototype(@_, '($|\$|\@|\%)$$?');
	
	return repeat(@_);
}


sub copy {
	
	check_prototype(@_, '(\$|\@|\%)');
	
	my $value = shift;
	my $copy;
	if(is_ref_ref(\$value)) { 
		my $s = Dumper($value);
		$s =~s/^\$\w+/\$copy/ms;
		eval($s);
		if($@) {
			croak "ERROR: $@\n";
		}
	}
	else {
		croak "ERROR: must copy a reference\n";
	}
	return $copy;
}


sub paste {

	check_prototype(@_, '(\@|$)+');
	
	my $sep;
	if(is_scalar_ref(\$_[$#_])) {
		$sep = pop; 
	}
	else {
		$sep = "|";
	}
    my @args = @_;
	
    return mapply(@args, sub{join $sep, @_});
}


sub seq {

	check_prototype(@_, '$$$?');
	
    my $from = shift;
    my $to = shift;
    my $by = shift || 1;
    
    my $seq = [];
    if($from < $to) {
		for(my $i = $from; $i <= $to; $i += $by) {
            push(@$seq, $i);
        }
    }
    else {
		for(my $i = $from; $i >= $to; $i -= $by) {
            push(@$seq, $i);
        }
    }
    return $seq;
}


sub c {
    
	check_prototype(@_, '(\@|$|\$)+');
	
	my @array_refs = @_;
    my $c = [];
    foreach (@array_refs) {
		if(is_array_ref($_)) {
			push(@$c, @$_);
		}
		else {
			push(@$c, $_);
		}
    }
    return $c;
}


sub test {
	
	check_prototype(@_, '\@\&');
	
    my $array = shift;
    my $function = shift;

    return sapply($array, sub {$function->($_[0]) ? 1: 0});
}


sub unique {
	
	check_prototype(@_, '\@');
	
    my $array = shift;
    my $hash = {};
    return [grep {not $hash->{$_} ++} @$array];
}


sub subset {
	
	check_prototype(@_, '\@(\@|\&)');
	
    my $array = shift;
    my $index = shift;
    
    if(is_code_ref($index)) {
        my $function = $index;
        return subset($array, which(test($array, $function)));
    }
    else {
        my $subset = [];
		
		my $max_index = max($index);
		my $min_index = min($index);
		if($max_index * $min_index < 0) {
			croak "ERROR: index should be in the same sign\n";
		}
		if($min_index >= 0) {
			for(my $i = 0; $i < len($index); $i ++) {
				push(@$subset, $array->[$index->[$i]]);
			}
		}
		elsif($max_index < 0) {
			return subset($array, setdiff(seq(0, $#$array), sapply($index, sub {-$_[0]-1})));
		}
        return $subset;
    }
}


sub subset_value {

	check_prototype(@_, '\@(\@|\&)(\@|$)');
		
    my $array = shift;
    my $index = shift;
	my $value = shift;
    
    if(is_code_ref($index)) {
        my $function = $index;
        return subset_value($array, which(test($array, $function)), $value);
    }
    else {
		if(is_scalar_ref(\$value)) {
			$value = repeat($value, len($index));
		}
		if(len($value) != len($index)) {
			croak "ERROR: length of values must be equal to that of subset!\n";
		}
		
		my $max_index = max($index);
		my $min_index = min($index);
		if($max_index * $min_index < 0) {
			croak "ERROR: index should be in the same sign\n";
		}
		if($min_index >= 0) {
			for(my $i = 0; $i < len($index); $i ++) {
				$array->[$index->[$i]] = $value->[$i];
			}
			return $array;
		}
		elsif($max_index < 0) {
			return subset($array, complement(sapply($index, sub {-$_[0]-1}), seq(0, $#$index)), $value);
		}
    }
}


# usage: del_array_item( [ARRAY REF])
# return: ARRAY REF
sub del_array_item {

	check_prototype(@_, '\@($|\@)');
	
	my $array = shift;
	my $del_i = shift;
	
	if(is_array_ref($del_i)) {
		my $remain = setdiff([0..$#$array], $del_i);
		@$array = @$array[@$remain];
	}
	else {
		@$array = (@$array[0..($del_i-1)], @$array[($del_i+1)..$#$array]);
	}
	return $array;
}


sub which {
	
	check_prototype(@_, '\@');
	
    my $logical = shift;
    
    my $which = [];
	@$which = grep {$logical->[$_]} 0..$#$logical;
    return $which;
}

sub all {
    
    check_prototype(@_, '\@');
    
    my $l = shift;
    if(len($l)) {
        return sum(test($l, sub {$_[0]})) == len($l) ? 1 : 0;
    } else {
        return 0;
    }
}

sub any {

    check_prototype(@_, '\@');
    
    my $l = shift;
    return sum(test($l, sub {$_[0]})) ? 1 : 0;
}


sub dim {
	
	check_prototype(@_, '\@');
	
	my $mat = shift;
	if(all(sapply($mat, sub {is_array_ref($_[0])}))) {
		
		my $nc = len($mat->[0]);
		if(all(sapply($mat, sub {len($_[0]) == $nc}))) {
			return (len($mat), $nc);
		}
	}
	
	return undef;

}


sub t {
	
	check_prototype(@_, '\@');
	
    my $matrix = shift;
    
    my ($n_row, $n_col) = dim($matrix);

    my $t = [[]];
    for(my $i = 0; $i < $n_row; $i ++) {
        for(my $j = 0; $j < $n_col; $j ++) {
            $t->[$j]->[$i] = $matrix->[$i]->[$j];
        }
    }
    return $t;
}

sub matrix_prod {
	
	check_prototype(@_, '(\@)+');
	
	if(scalar(@_) > 2) {
		my $first = shift(@_);
		my $second = shift(@_);
		return matrix_prod(matrix_prod($first, $second), @_);
	}
	
	my $mat1 = shift;
	my $mat2 = shift;
	
	my ($I, $J1) = dim($mat1);
	my ($J2, $K) = dim($mat2);
	
	if($J1 == $J2) {
		my $J = $J1;
		my $product;
		for(my $i = 0; $i < $I; $i ++) {
			for(my $k = 0; $k < $K; $k ++) {
				my $sum = 0;
				for(my $j = 0; $j < $J; $j ++) {
					$sum += $mat1->[$i]->[$j]*$mat2->[$j]->[$k];
				}
				$product->[$i]->[$k] = $sum;
			}
		}
		return $product;
	}
	else {
		croak "ERROR: columns in the first matrix must be equal to the rows in the second matrix";
	}
}


sub is_array_identical {
	
	check_prototype(@_, '\@\@');
	
	my $array1 = shift;
	my $array2 = shift;
	
	if(len($array1) != len($array2)) {
		return 0;
	}
	else {
		if(sum(mapply($array1, $array2, sub{abs($_[0]-$_[1]) < EPS})) == len($array1)) {
			return 1;
		}
		else {
			return 0;
		}
	}
}


sub is_matrix_identical {

	check_prototype(@_, '\@\@');
	
	my $matrix1 = shift;
	my $matrix2 = shift;
	
	my ($d1, $d2) = dim($matrix1);
	my ($d3, $d4) = dim($matrix2);
	
	if(!defined($d1) or !defined($d2) or !defined($d3) or !defined($d4)) {
		return 0;
	}
	
	unless($d1 == $d3 and $d2 == $d4) {
		return 0;
	}
	
	my $v = mapply($matrix1, $matrix2, sub {is_array_identical($_[0], $_[1])});
	if(sum($v) == len($matrix1)) {
		return 1;
	}
	else {
		return 0;
	}
}


sub outer {
	
	check_prototype(@_, '\@\@(\&)?');
	
	my $vector1 = shift;
	my $vector2 = shift;
	my $function = shift || sub {$_[0]*$_[1]};
	
	my $outer = [];
	for(my $i = 0; $i < len($vector1); $i ++) {
		for(my $j = 0; $j < len($vector2); $j ++) {
			$outer->[$i]->[$j] = $function->($vector1->[$i], $vector2->[$j]);
		}
	}
	
	return $outer;
}

sub inner {
	
	check_prototype(@_, '\@\@(\&)?');
	
	my $vector1 = shift;
	my $vector2 = shift;
	my $function = shift || sub {$_[0]*$_[1]};
	
	my $inner = [];
	$inner = sum(mapply($vector1, $vector2, $function));
	
	return $inner;
}


sub len {
	
	my $array = shift;
	if(is_array_ref($array)) {
		return scalar(@$array);
	}
	elsif(is_hash_ref($array)) {
		return scalar(keys %$array);
	}
	elsif(!defined($array)) {
		return 0;
	}
	else {
		return 1;
	}
}

sub match {
	
	check_prototype(@_, '\@\@');
	
	my $array1 = shift;
	my $array2 = shift;
	
	my $h = {};
	foreach (@$array2) {
		$h->{$_} = 1;
	}
	
	my $index = [];
	for(my $i = 0; $i < len($array1); $i ++) {
		push(@$index, $i) if($h->{$array1->[$i]});
	}
	return $index;
}

sub is_empty {
	!len($_[0]) + 0;
}

sub plus {
	
	check_prototype(@_, '(\@|$)+');
	
	croak "ERROR: at least two array lists is required" if scalar(@_) < 2;
	
	my $array1 = shift;
	my $array2 = shift;
	my @arrays = @_;
	
	my $res = mapply($array1, $array2, sub {$_[0] + $_[1]});
	if(scalar(@arrays) == 0) {
		return $res;
	}
	else {
		return plus($res, @arrays);
	}
}

sub minus {
	
	check_prototype(@_, '(\@|$)+');
	
	croak "ERROR: at least two array lists is required" if scalar(@_) < 2;
	
	my $array1 = shift;
	my $array2 = shift;
	my @arrays = @_;
	
	my $res = mapply($array1, $array2, sub {$_[0] - $_[1]});
	if(scalar(@arrays) == 0) {
		return $res;
	}
	else {
		return minus($res, @arrays);
	}
}

sub divide {
	
	check_prototype(@_, '(\@|$)+');
	
	croak "ERROR: at least two array lists is required" if scalar(@_) < 2;
	
	my $array1 = shift;
	my $array2 = shift;
	my @arrays = @_;
	
	my $res = mapply($array1, $array2, sub {$_[0] / $_[1]});
	if(scalar(@arrays) == 0) {
		return $res;
	}
	else {
		return divide($res, @arrays);
	}
}

sub multiply {
	
	check_prototype(@_, '(\@|$)+');
	
	croak "ERROR: at least two array lists is required" if scalar(@_) < 2;
	
	my $array1 = shift;
	my $array2 = shift;
	my @arrays = @_;
	
	my $res = mapply($array1, $array2, sub {$_[0] * $_[1]});
	if(scalar(@arrays) == 0) {
		return $res;
	}
	else {
		return multiply($res, @arrays);
	}
}



1;
