package StdLib;

use 5.006;
use strict;
use warnings;

require Exporter;

our @ISA = qw(Exporter);

# Items to export into callers namespace by default. Note: do not export
# names by default without a very good reason. Use EXPORT_OK instead.
# Do not simply export all your public functions/methods/constants.

# This allows declaration	use StdLib ':all';
# If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
# will save memory.

our %EXPORT_TAGS = ( 'all' => [ qw() ] );

our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );

our @EXPORT = qw(
&comb &div &divisors &equals &evens &fact
&fact &fib &gcm &hash2list &iseven &isfact
&isfib &isin &isodd &isprime &justletters &justsmallletters
&list2hash &lucas &max &maxdif &min &minmax
&mod &odds &prod &randlist &seq
&shuffle &smallletters &sortbyletters &sum &uniques
);

our $VERSION = '0.01';

# FUNCTIONS

# f :: int -> int

sub fact {             # factorial
  prod(1..$_[0])
}

sub fib {              # fibonacci sequence
  seq($_[0],2);
}

sub lucas {            # lucas sequence
  seq($_[0],3);
}

sub seq {              # used for the creation of fibonacci and lucas sequences
  return undef if $_[0] < 1;
  return $_[0] if $_[0] < 3;
  my @l = (1,$_[1]);
  my $c = 2;
  while ($c < $_[0])
  {
    push @l,$l[$c-2]+$l[$c-1];
    $c++
  }
  return wantarray ? @l : $l[$c-1]
}

# f :: (int,int) -> int

sub div {              # common division returning 0 if second argument is 0
  $_[1] ? $_[0] / $_[1] : 0
}

sub mod {              # rest of division
  my ($a,$b) = @_;
  return $a if $b == 0;
  $a-=$b while $a>=$b;
  $a
}

# f :: [int] -> int

sub equals {           # 1 if all numbers in a list are equal (1, if empty)
# ALTERAR PARA SUPORTAR FUNCOES DE ENTRADA (como o sort)
  $a = shift || return 1;
  for (@_) {return 0 unless $a == $_}
  return 1
}

sub gcm {              # greatest common measure (máximo divisor comuns)
  my @a = @_;
  my $a;
  until (equals @a)
  {
    $a = min(@a);
    @a = map {$_ == $a ? $_ : $_ - $a} @a;
  }
  $a[0]
}

sub max {              # maximum value of a list (undef, if empty)
  my $m = shift || return undef;
  for(@_){$m = $_ if $_>$m}
  $m
}

sub maxdif {           # difference between maximum and minimum value of a list
  my ($min,$max) = minmax(@_);
  defined $min || return undef;
  $max - $min
}

sub min {              # minimum value of a list (undef, if empty)
  my $m = shift || return undef;
  for(@_){$m=$_ if $_<$m}
  $m
}

sub prod {             # product of all numbers in a list (1, if empty)
  my $r = 1;
  while (@_) {$r *= shift}
  $r
}

sub sum {              # sum of all numbers in a list (0, if empty)
  my $r = 0;
  while (@_) {$r += shift}
  $r
}

# f :: [int] -> (int,int)

sub minmax {           # maximum and minimum values in a list (undef if empty)
  my $min = shift;
  my $max = $min;
  for(@_)
  {
    $max = $_ if $_ > $max;
    $min = $_ if $_ < $min
  }
  ($min,$max)
}

# f :: int -> [int]

sub divisors {         # dividers of a given number
  my @r = ();
  for (1..$_[0]) {push @r,$_ if mod($_[0],$_) == 0}
  @r
}

sub evens {            # list of even numbers lower than a given one
  map {$_*2} 1..$_[0]/2
}

sub odds {             # list of odd numbers lower than a given one
  map {$_*2-1} 1..$_[0]/2+.5
}

# f :: int -> bool (being bool a natural number comporting relevant information, if true, or 0)

sub iseven {           # 1 if the number is even, 0 otherwise
  not (isodd $_[0])
}

sub isfact {           # number for each the argument is a factorial or 0
  my $f = 1;
  for (1..$_[0]) {$f *= $_;return $_ if $f == $_[0];return 0 if $f > $_[0]}
}

sub isfib {            # number for each the argument is a fibonacci or 0
  my $n = 0;
  while ((my $f = fib(++$n)) <= $_[0])
  {
    return $n if $f == $_[0]
  }
  0
}

sub isodd {            # 1 if the number is odd, 0 otherwise
  mod ($_[0],2)
}

sub isprime {          # 1 if the number is prime, 0 otherwise
  return 0 if $_[0] < 2;
  return 1 if $_[0] == 2;
  for (2..$_[0]-1)
  {
    return 0 unless mod($_[0],$_)
  }
  return 1
}

# f :: (int,[int]) -> int

sub isin {             # 1 if the first element is among the others
  my ($a,@a) = @_;
  for (@a) {return 1 if $a == $_}
  return 0
}

# f :: [..] -> [..]

sub uniques {          # returns the input list without repeated elements
  my %v = ();
  return grep { ! $v{$_} ++ } @_
}

sub shuffle {          # the input list, with arguments randomly re-ordered
  my @a = @_;
  my ($a,$b,$t);
  for (1..(1+$#a)*2)
  {
    $a = int rand(1+$#a);
    $b = int rand(1+$#a);
    $t = $a[$a];
    $a[$a] = $a[$b];
    $a[$b] = $t
  }
  @a
}

# outras funções

sub comb {             # given a list of lists, returns a list with possible lists in which the first element is contained in the first input list, the second in the second one and so on
  my @data = @_;
  if ($#data == 0)
  {
    map {[$_]} @{$data[0]}
  }
  else
  {
    my @p = comb(@data[1..$#data]);
    my @r = ();
    for my $a (@{$data[0]})
    {
      for my $b (@p) {push @r,[$a,@$b]}
    }
    @r
  }
}

#############

#sub hashas {
#  my %a = %{$_[0]};
#  for (keys %a) {$a{$_} = $_[1]}
#  %a
#}

#sub intersect {
#  my %c;
#  my %a = list2hash(%{$_[0]});
#  my %b = list2hash(%{$_[1]});
#  for (keys %a) {$c{$_} = min($a{$_},$b{$_})}
#  hash2list(%c)
#}

sub list2hash {        # converts a list into a hash
  my %a;
  for (@_) {$a{$_}++}
  %a
}

#sub uniques2 {
#  keys (list2hash @_)
#}

sub hash2list {        # converts a hash into a list
  my @a;
  my %b = @_;
  for my $k (keys %b)
  {
    for (1..$b{$k})
    {
      push @a,$k
    }
  }
  @a
}

sub randlist {         # random list
  my @a;
  for (1..$_[0])
  {
    push @a, int rand ($_[1] ? $_[1] : 1)
  }
  @a  
}

##############

sub sortbyletters {
  sort {justsmallletters($a) cmp justsmallletters($b)} @_
}

sub smallletters {     # lowercases input strings
  my @a;
  for (@_)
  {
    my $a = $_;
    $a =~ y/A-Z/a-z/;
    push @a,$a
  }
  @a
}

sub justsmallletters { # lowercases input strings, also removing non-chars
  my @a;
  for (@_)
  {
    my $a = $_;
    $a =~ y/A-Z/a-z/;
    $a =~ y/a-z //cd;
    push @a,$a
  }
  @a
}

sub justletters {      # removes non-chars in input strings
  my @a;
  for (@_)
  {
    my $a = $_;
    $a =~ y/a-zA-Z //cd;
    push @a,$a
  }
  @a
}

##########

#

1;

__END__

=head1 NAME

StdLib - Standard functions usable now and then...

=head1 SYNOPSIS

use StdLib;

# and then any of the available functions

=head1 FUNCTIONS BY CATEGORY

Here is a list of all the available functions, arranged by category.

f :: int -> int

	`fact', `fib'

f :: (int,int) -> int

	`div', `mod'

f :: [int] -> int

	`equals', `gcm', `max', `maxdif', `min', `prod', `sum'

f :: [int] -> (int,int)

	`minmax'

f :: int -> [int]

	`divisors', `evens', `odds'

f :: int -> bool (int)

	`iseven', `isfact', `isfib', `isodd', `isprime'

f :: (int,[int]) -> int

	`isin'

f :: [int] -> [int]

	`uniques', `shuffle'

OTHER

	`comb'

=head1 ALPHABETICAL LISTING OF FUNCTIONS BY CATEGORY

	`uniques', `shuffle'

OTHER

	`comb'

=head1 ALPHABETICAL LISTING OF FUNCTIONS BY CATEGORY

These are the currently available functions. For more information on how to use each of them, refer to the section 'USAGE EXAMPLES'

=head2 FUNCTIONS OF TYPE f :: int -> int

=head3 fact

	factorial

	fact (1) --> 1
	fact (4) --> 24

=head3 fib

	fibonacci

	fib (3) --> 3
	fib (5) --> 8

=head3 lucas

	lucas

	lucas (3) --> 4
	lucas (5) --> 11

=head3 seq

	sequences (fibonacci, lucas, etc. ; fibonacci with 2, lucas with 3)

	seq (3,2) --> 1,2,3
	seq (5,3) --> 1,3,4,7,11
	seq (3,5) --> 1,5,6

=head3 isfib


	if the number is a fibonacci, returns the number for which it is one (returns 0 elsewise)

=head3 div

	divides a number by another, but returns 0 if the second one is 0

	div (3,2) --> 1.5
	div (3,0) --> 0

=head3 mod

	rest of division

	mod (3,2) --> 1
	mod (7,0) --> 7

=head2 FUNCTIONS OF TYPE f :: [int] -> int

=head3 equals

	returns a 1 if all numbers in a list are equal (1, if empty)

	equals (1,2,3) --> 0
	equals (2,2,2) --> 1
	equals () --> 0

=head3 gcm

	returns the greatest common measure for the input list (máximo divisor comum)

=head3 fact

	factorial

	fact (1) --> 1
	fact (4) --> 24

=head3 fib

	fibonacci

	fib (3) --> 3
	fib (5) --> 8

=head2 FUNCTIONS OF TYPE f :: (int,int) -> int

=head3 div

	divides a number by another, but returns 0 if the second one is 0

	div (3,2) --> 1.5
	div (3,0) --> 0

=head3 mod

	rest of division

	mod (3,2) --> 1
	mod (7,0) --> 7

=head2 FUNCTIONS OF TYPE f :: [int] -> int

=head3 equals

	returns a 1 if all numbers in a list are equal (1, if empty)

	equals (1,2,3) --> 0
	equals (2,2,2) --> 1
	equals () --> 0

=head3 gcm

	returns the greatest common measure for the input list (máximo divisor comum)

	gcm (1,2,3,4,5) -> 1
	gcm (2,4) -> 2
	gcm (246,327) -> 3

=head3 max

	returns the maximum value of a list (undef, if empty)

	max (1..9) --> 9
	max () --> undef

=head3 maxdif

	returns the difference between the maximum and the minimum value of a list (undef, if empty)

	maxdif (1..9) --> 8
	maxdif (2,1) --> 1
	maxdif () --> undef

=head3 min

	returns the minimum value of a list (undef, if empty)

	min (1..9) --> 1
	min () --> undef

=head3 prod

	return the product of all numbers in a list (1, if empty)

	prod (1..4) --> 24
	prod () --> 1

=head3 sum

	returns the sum of all numbers in a list (0, if empty)

	sum (1,2,3) --> 6
	sum () --> 0

	divisors (1) --> (1)
	divisors (5) --> (1,5)
	divisors (10) --> (1,2,5,10)
	divisors (-4) --> ()

=head3 evens

	returns a pair containing the maximum and minimum values in a list (undef, if empty)

	minmax (1..3) --> (1,3)
	minmax () --> undef

=head3 odds

	returns the list of odd numbers lower than a given one

=head3 divisors

	returns the divisors of a given number

	divisors (1) --> (1)
	divisors (5) --> (1,5)
	divisors (10) --> (1,2,5,10)
	divisors (-4) --> ()

=head3 evens

	odds (-2) --> ()
	odds (0) --> ()
	odds (5) --> (1,3,5)
	returns the list of even numbers lower than a given one

	evens (-2) --> ()
	evens (0) --> ()
	evens (5) --> (2,4)

=head2 FUNCTIONS OF TYPE f :: int -> bool (int)
=head3 odds

	returns the list of odd numbers lower than a given one

	odds (-2) --> ()
	odds (0) --> ()
	odds (5) --> (1,3,5)

=head2 FUNCTIONS OF TYPE f :: int -> bool (int)

=head3 iseven

	returns 1 if the number is even, 0 elsewise

	iseven (3) --> 0
	iseven (4) --> 1

=head3 isfact

	if the number is a factorial, returns the number for which it is one (returns 0 elsewise)

	isfact (6) --> 3
	isfact (4) --> 0

=head3 isfib

	if the number is a fibonnaci, returns the number for which it is one (returns 0 elsewise)

	isfib (8) --> 5
	isfib (9) --> 0

=head3 isodd

	returns 1 if the number is odd, 0 elsewise

	isodd (3) --> 1
	isodd (4) --> 0

=head3 isprime

	return 1 if the number is prime, 0 elsewise

	isprime (5) --> 1
	isprime (10) --> 0

=head2 FUNTIONS OF TYPE f :: (int,[int]) -> int

=head3 isin

	returns 1 if the first element is among the others, 0 otherwise

	isin (1,1,2,3,4) -> 1
	isin (1,2,3,4,5) -> 0
	isin (1,4,3,2,1) -> 1

=head2 FUNTIONS OF TYPE f :: [int] -> [int]

=head3 uniques

	returns a list containing unique copies of the elements in the iput list

	uniques (1,2,3,2,5,4) -> (1,2,3,5,4)
	uniques (1,2,3,4,5) -> (1,2,3,4,5)
	uniques () -> ()

=head3 shuffle

	returns the input list, with its arguments randomly re-ordered

	shuffle (1,2) -> (1,2) OR (2,1)
	shuffle (1,2,3) -> (1,2,3) OR (1,3,2) OR ...

=head2 other functions

=head3 comb

	given a list of lists, returns a list with possible lists in which the first element is contained in the first input list, the second in the second one and so on (comb stands for 'combine')

	comb ((1,2),(3,4)) --> ((1,3),(1,4),(2,3),(2,4))

=head1 DESCRIPTION

StdLib is currently being used to provide those tiny little functions everybody needs once in a while

=head1 KNOWN BUGS

Some things should be prevented, such as invocations of functions without parameters, for example

divisors should give the dividers of a negative number (when it does, its usage example must be changed!)

=head1 TO DO

New functions:

scm - smallest common multiple (menor múltiplo comum)

Without documentation:

list2hash, hash2list, randlist
New functions:

scm - smallest common multiple (menor múltiplo comum)

=head1 SEE ALSO

perl(1)

=head1 AUTHORS

Alberto Manuel Brandão Simões, E<lt>albie@alfarrabio.di.uminho.ptE<gt>

José Alves de Castro, E<lt>jac@natura.di.uminho.ptE<gt>

=head1 COPYRIGHT AND LICENSE

Copyright 2002 by us!

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.

=cut
