Login   Register  
PHP Classes
elePHPant
Icontem

File: Calculus.php

Recommend this page to a friend!
Stumble It! Stumble It! Bookmark in del.icio.us Bookmark in del.icio.us
  Classes of Martin Lacroix  >  PHP Minifier  >  Calculus.php  >  Download  
File: Calculus.php
Role: Auxiliary data
Content type: text/plain
Description: Example : Class to minify
Class: PHP Minifier
Reduce the size of PHP source files
Author: By
Last change:
Date: 2011-09-29 12:22
Size: 8,972 bytes
 

Contents

Class file image Download
<?php

/**
 * Copyright (C) 2011+ Martin Lacroix
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * @license GNU General Public License Version 3 (GPLv3)
 * @link http://www.gnu.org/licenses/
 */

/**
 * PHP_VER   : 5.3+
 * LIBRARIES : BCMATH
 * KEYWORDS  : NUMBER DISTINCT UNIQUE DIGIT RANGE LIMIT LEADING ZERO
 *             NOMBRE DISTINCT UNIQUE DIGIT CHIFFRE BORNE LIMITE ZERO SIGNIFICATIF
 *
 * Class mainly computing the number of numbers with unique digits over a range.
 * Include the option of leading zero
 *
 * Classe en charge principalement du calcul du nombre de nombres
 * constitués de digits uniques qu'il est possible de créer entre deux limites
 * Gère l'option des zéros significatifs du début
 *
 * @package tools
 * @version 1.0.1
 * @author Martin Lacroix
 */
class Calculus {

   const GREATEST_NUMBER_WITH_UNIQUE_DIGITS = '9876543210';

   /**
    * Calcule et renvoie le nombre de nombres constitués uniquement de digits uniques
    * qu'il est possible de créer entre deux limites
    * Si les zéros du début sont significatifs alors la limite ayant la longueur la plus courte
    * sera complétée par des 0 afin d'atteindre la longueur de la limite la plus longue
    * Ex : si min = 000000 et max = 999 => $max = 000999
    * @param mixed $min Tout entier positif ou nul
    * @param mixed $max Tout entier positif ou nul - Automatiquement plafonné à 9876543210
    * @param bool $leadingZeros Tenir compte des zéros du début
    * @return int
    */
   static function nbOfNumbersWithUniqueDigits($min, $max = self::GREATEST_NUMBER_WITH_UNIQUE_DIGITS,
                                               $leadingZeros = TRUE)
   {
      // valeurs numériques obligatoires
      if ( ! (ctype_digit("$min") || ctype_digit("$max"))) {
         return 0;
      }

      // plafonnement de la limite supérieure
      if (bccomp($max, self::GREATEST_NUMBER_WITH_UNIQUE_DIGITS) == 1) {
         $max = self::GREATEST_NUMBER_WITH_UNIQUE_DIGITS;
      }

      $comp = bccomp($min, $max);
      if ($comp === 1) {        // minimum > maximum
         return 0;
      }
      else
      if ($comp === 0) {         // minimum = maximum
         return (self::hasUniqueDigits($min)) ? 1 : 0;
      }

      $nb   = 0;
      $min = "$min";
      $max = "$max";

      // si zéros du début significatifs => égalise les longueurs des limites (complétion avec 0)
      $equalizeLength =
         function() use (&$min, &$max) {
            if (strlen($min) < strlen($max)) {
               $min = str_pad($min, strlen($max), '0', STR_PAD_LEFT);
            }
            else
            if (strlen($min) > strlen($max)) {
               $max = str_pad($max, strlen($min), '0', STR_PAD_LEFT);
            }
         };

      // si les zéros du début sont significatifs, on égalise les longueurs des limites
      if ($leadingZeros) {
         $equalizeLength();
      }

      // traitement des effets de bord (limite inférieure) : si dernier chiffre = 9
      // vérification manuelle de la validité puis on rajoute 1
      if ($min[strlen($min) - 1] == 9) {
         if (self::hasUniqueDigits($min)) {
            ++$nb;
         }
         $min = bcadd($min, 1);
      }

      // traitement des effets de bord (limite supérieure): si dernier chiffre = 0
      // vérification manuelle de la validité puis on retranche 1
      if ($max[strlen($max) - 1] == 0) {
         if (self::hasUniqueDigits($max)) {
            ++$nb;
         }
         $max = bcsub($max, 1);
      }

      // pour le traitement on égalise les longueurs des limites si cela n'a pas déjà été fait
      if (FALSE === $leadingZeros) {
         $equalizeLength();
      }

      // détermination des paliers pour le comptage des valeurs
      $steps = self::steps($min, $max);

      foreach($steps as $step) {
         $nb += self::countAllowedNumbers($step[0], $step[1], $leadingZeros);
      }
      return $nb;
   }

   /**
    * Indique si le nombre en paramètre n'est composé que de digits uniques
    * @param mixed $p Tout entier positif
    * @return bool
    */
   static function hasUniqueDigits($p) {
      return (ctype_digit("$p")) ? (count(array_flip(str_split($p, 1))) === strlen($p)) : FALSE;
   }

   /**
    * Décompose un nombre en paliers successifs de type x000->y999
    * @param mixed $mixRange Tout entier positif ou nul
    * @param mixed $maxRange Tout entier positif ou nul
    * @return array Array([] => array(0 => min, 1 => max))
    */
   static private function steps($minRange, $maxRange) {
      /*
      2 sens de parcours : croissant et décroissant
      Paliers si $pMin = 245600 et $pMax = 322799
      245600 -> 245609 :: sens croissant
      245610 -> 245699 ::       "
      245700 -> 245999 ::       "
      246000 -> 249999 ::       "
      250000 -> 299999 ::       "
      300000 -> 319999 :: sens décroissant (300000 = base minimum pour l'itération décroissante)
      320000 -> 321999 ::       "
      322000 -> 322699 ::       "
      322700 -> 322789 ::       "
      322790 -> 322799 ::       "
      */

      $descendingSearch = FALSE;

      $len   = strlen($minRange);
      $i     = $len;
      $min   = $minRange;
      $base  = $minRange;   // utilisé comme minimum pour la décomposition décroissante
      $steps = array();

      while (--$i >= 0) {
         $left  = substr($min, 0, $i);
         $right = str_repeat('9', $len - $i);
         $max   = $left . $right;
         $comp  = bccomp($maxRange, $max);

         if ($comp >= 0) {
            $steps[] = array($min, $max);
            $min     = bcadd($max, 1);
            $min     = str_pad($min, $len, '0', STR_PAD_LEFT);
            $base    = $min;

            if ($comp === 0) {
               break;
            }
         }
         else {
            $descendingSearch = TRUE;
            break;
         }
      }

      if ($descendingSearch) {
         $i   = $len;
         $max = $maxRange;

         while (--$i >= 0) {
            $left  = substr($max, 0, $i);
            $right = str_repeat('0', $len - $i);
            $min   = $left . $right;
            $comp  = bccomp($min, $base);

            if ($comp >= 0) {
               $steps[] = array($min, $max);

               if ($comp === 0) {
                  break;
               }

               $max = bcsub($min, 1);
            }
            else  {
               $steps[] = array($base, $max);
               break;
            }
         }
      }
      return $steps;
   }

   /**
    * Routine de comptage du nombre de nombres à digits uniques
    * qu'il est possible de créer entre deux limites
    * @param mixed $min Tout entier positif ou nul
    * @param mixed $max Tout entier positif ou nul
    * @param mixed $leadingZeros Zéros de début significatifs
    * @return int
    */
   static private function countAllowedNumbers($min, $max, $leadingZeros) {
      $nbAllowed = array();

      // suppression des zéros du début non significatifs
      if (FALSE === $leadingZeros) {
         $min = ($min == 0) ? '0' : ltrim($min, '0');
         $max = ($max == 0) ? '0' : ltrim($max, '0');
      }

      $iMax = strlen($min) - 1;

      // préparation du tableau d'analyse pour chaque position de digit
      for($i = 0 ; $i <= $iMax ; ++$i) {
         $structure[$i] = array('possible' => array(), 'forbidden' => array(), 'substract' => 0);
      }

      $i = -1;

      while(++$i <= $iMax) {
         $structure[$i]['possible'] = range($min[$i], $max[$i]);

         if ($min[$i] == $max[$i]) {
            // propagation de la valeur interdite à toutes les positions postérieures
            for($j = $i + 1 ; $j <= $iMax ; ++$j) {
               $structure[$j]['forbidden'][] = $min[$i];
            }
         }
         else {
            // propagation de la valeur choisie à toutes les positions postérieures
            for($j = $i + 1 ; $j <= $iMax ; ++$j) {
               ++$structure[$j]['substract'];
            }
         }
      }

      // comptage du nombre de valeurs possibles par position de digit
      foreach($structure as $value) {
         $nbAllowed[] = count(array_diff($value['possible'], $value['forbidden'])) - $value['substract'];
      }

      return (($nb = array_product($nbAllowed)) > 0) ? $nb : 0;
   }
}
?>