Brainstorming coding contest

Posté le Vendredi 24 avril 2009 à 13 h 51, Read it in english with Google

Histoire de faire chauffer encore plus vos cerveau, je vous propose un petit concours destiné à trouver des algo adaptés pour des situations courantes avec à la clef une fonera toute neuve version 1 à gagner.

Les règles sont simples, je vous prĂ©sente 2 problèmes PHP que je n’ai pas su rĂ©soudre. Celui qui pondra le meilleur algo tout problème confondu aura gagnĂ©. Naturellement je suis le seul juge (ba oui ce serait trop facile). Si personne trouve d’algo adaptĂ© pour aucun des problèmes ou ne propose pas de solution innovantes (qui permettrait d’avancer un peu), il y aura un tirage au sort parmi les commentateurs.

Le jeu se termine le 10 mai 2009 Ă  minuit. Toutes les solutions sont destinĂ©e Ă  ĂŞtre utilisĂ©e dans un modèle objet donc au cas oĂą vous seriez tentĂ© d’utiliser des variables globales ou d’autre abbĂ©ration cassant la structure, oubliez ! A vos cerveaux !

Problème n°1 :

Il faut factoriser au mieux ce code :

$data = array(
  array(
    'family' => 'Einstein',
    'name' => 'Albert'
  ),
  array(
    'family' => 'Einstein',
    'name' => 'Frank'
  ),
  array(
    'family' => 'Stallman',
    'name' => 'Richard'
  ),
  array(
    'family' => 'Zimmermann',
    'name' => 'Philip'
  )
);

function render ($family, $names) {
  echo $family.$names.'<br>';
}

$family = null;
$names = null;
foreach ($data as $person) {
  if ($family && $family != $person['family']) {
    render($family, $names);
    $names = '';
  }
  $family = $person['family'];
  $names .= ' '.$person['name'];
}
 
if ($family) {
  render($family, $names);
}

Il s’agit ici de ne pas rĂ©pĂ©ter le dernier block et de garder toujours une seule boucle pour n’effectuer qu’un seul parcours des donnĂ©es. Le format des donnĂ©es du tableau $data ne doivent pas changer et il faut Ă©galement garder la fonction render intacte pour afficher les donnĂ©es. Evidemment, on cherche Ă  factoriser donc utiliser echo est interdit hors de la fonction render.

Problème n°2 :

Il faut trouver un moyen de se passer du « new » habituel et de crĂ©er nouvelle instance de classe avec des paramètres variables.

function createInstance ($className) {
  return new $className();
}

class test {
  function __construct($param1 = null, $param2 = null) {
  }
};

$inst = createInstance('test');

Construire la même fonction que celle-ci mais en introduisant la possibilité de passer des paramètres au constructeur de classe. Sachez aussi que Eval is Evil ;)

Merci de votre participation, si je vois que vous ĂŞtes chaud et motivĂ©, j’hĂ©siterais pas Ă  vous en proposer d’autre Ă  l’avenir. Et oubliez pas de vous abreuver pour faire descendre la tempĂ©rature, vous avez le droits de coder en transat :D

Concours terminé

Félicitation à pioupiouM qui a gagner en résolvant le problème n°2 avec ce code :

function createInstance ($className)
{
    if (1 < func_num_args())
    {
        $reflectionObj = new ReflectionClass($className);
        return $reflectionObj->newInstanceArgs(array_slice(func_get_args(), 1));
    }
    return new $className;
}

class test
{
    function __construct($param1 = null, $param2 = null)
    {
        printf("\n[%s] %s %s",
            get_class($this),
            var_export($param1, true),
            var_export($param2, true)
        );
    }
}

$inst = createInstance('test');
$inst = createInstance('test', 'Foo', 'Bar');

Et bravo Ă  Thierry qui s’est battu comme un chef.

15 réponses à “Brainstorming coding contest”

  1. Julien

    Yep Martin !

    Si toi t’as pas trouvĂ©, je pense que je suis hors-course ;-)
    Mais je fais passer l’info pour ceux qui ont le niveau.
    Au passage, je m’inscris pour le tirage au sort, le règlement est chez un huissier ? xD

  2. XoraX

    oula nan pas d’huissier trop compliquĂ© :p

  3. Thierry

    Je pense ne jamais avoir pondu un truc aussi immonde :D
    M’enfin ça rĂ©pond Ă  la demande du challenge en respectant la non
    utilisation de eval()

    // Fonction de création d'une instance
    // Premier paramètre : Nom de la classe
    // Et x paramètres à passer au constructeur
    function createInstance()
    {
                   // On extrait les paramètres
           $args = array();
           foreach(func_get_args() as $arg)
                   if(!isset($class)) $class = $arg;
                                   // Entre quote, pour que les chaĂ®nes en paramètres fonctionnent
                                   // On peut faire plus de test sur le type de donnĂ©es pour ne mettre
                                   // les quote que lorsque c'est utile.
                   else $args[] = "'".$arg."'";

                   // On crĂ©Ă© une fonction anonyme pour l'occasion
           $func = create_function('','return new
    '
    .$class.'('.implode(',',$args).');');

                   // Et on renvoit l'instance que la fonction anonyme crĂ©Ă©e
           return $func();
    }

    // Class de test
    class MyClass
    {
           private $a,$b;

           public function __construct($a,$b)
           {
                   $this->a = $a;
                   $this->b = $b;
           }

           public function Test() {
                   echo $this->a,' and ',$this->b,' !!!';
           }
    }

    // Création de l'instance avec ses deux paramètres passés au
    constructeur de la class
    $a = createInstance('MyClass','My First Param','My Second Param');

    // Ceci affiche bien "My First Param and My Second Param !!!"
    $a->Test();

    Je me suis d’abord dis que le seul moyen d’appeler une fonction x avec
    ses paramètres Ă©tait d’utiliser call_user_func. Sauf qu’appeler un
    constructeur ça ne se fait, monsieur php n’est pas d’accord.

    On pourrait ajouter une méthode statique à la classe pour créer
    l’instance, un peu comme un singleton mais je pense qu’on sort du
    cadre du challenge qui est à mon avis de pouvoir faire ça avec les
    class telles qu’elles existent.

    Eval pas autorisĂ©…. ça commence Ă  se compliquer…
    Et puis lueur dans mon esprit : une fonction anonyme règle très bien
    le problème.

  4. XoraX

    Merci pour ton code !

    Cela dit, eval et create_function interviennent de la même manière au niveau de php : http://stackoverflow.com/questions/418674/phps-createfunction-versus-just-using-eval.
    Ce qui se traduit par une perte de performance (2.5 fois plus lent il me semble).

    Donc dĂ©solĂ© c’est pas bon :) mais j’ai pas dit qu’il existait une solution et je te fĂ©licite pour ton remue-mĂ©ninge.

    Le coup de la mĂ©thode static ne serait pas adaptĂ© en php < 6 car on serait obligĂ© de la dupliquer. En php 6 ce serait dĂ©jĂ  un peu plus adaptĂ© grâce Ă  l’utilisation de « static:: » Ă  la place de « self:: », mais il faudrait alors toujours faire hĂ©riter nos classes de la mĂŞme classe de base car php ne supporte pas l’hĂ©ritage multiple…

  5. Thierry

    Je n’avais pas vĂ©rifiĂ©, mais je me doutais un peu que eval et create_function fesaient intervenir les mĂŞme mĂ©canismes pour l’interprĂ©tation du code. J’ai postĂ© ça sans conviction :)

    Après concernant la perte de performances, il va forcĂ©ment y en avoir puisqu’on multiplie les Ă©tapes de crĂ©ation d’une instance en passant par une fonction.

    Par contre, pour l’hĂ©ritage Ă  une class mère commune, je pense que c’est possible. Je vais Ă©tudier cette possibilitĂ© si j’ai encore un peu de temps Ă  perdre. Cela-dit, plus possible d’hĂ©riter les class.

    De toute façon, je suis convaincu qu’il n’y a pas de solution ce problème, et pour tout dire je ne vois pas non plus l’intĂ©rĂŞt d’en trouver une. Bref, je m’inscris aussi pour le tirage au sort :)

  6. XoraX

    t’inquiète t’ai dans le tirage ;)

    A mon sens, il y a beaucoup d’intĂ©rĂŞts Ă  crĂ©er une fonction de ce type. On pourrait enfin se passer du « new », et donc de ne pas ĂŞtre obligĂ© de crĂ©er une variable Ă  chaque fois. On pourrait donc faire createIntance(‘maClass’, $params)->maFonction($value) ce qui serait super pratique ! Et encore plus pratique pour crĂ©er des singletons. Mais c’est vrai que je vois mal comment y arriver.

    Pense toi sur le premier problème si ça te dit, ça me semble réalisable.

  7. Thierry

    Petite proposition vite fait :

    $data= array(
      array(
        'family' =>  'Einstein',
        'name' => 'Albert'
      ),
      array(
        'family' => 'Einstein',
        'name' => 'Frank'
      ),
      array(
        'family' => 'Stallman',
        'name' => 'Richard'
      ),
      array(
        'family' => 'Zimmermann',
        'name' => 'Philip'
      )
    );

    function render ($family, $names) {
      echo $family.$names.'';
    }

    $family = $names = null;
    $data[] = array('family'=>false,'name' =>false);

    foreach ($data as $person) {
      if ($family !== null && $family != $person['family']) {
        render($family, $names);
        $names = '';
      }
      $family = $person['family'];
      $names .= ' '.$person['name'];
    }

  8. piouPiouM

    Petite solution pour le problème numéro 2. La réflexion est ton amie.

    function createInstance ($className)
    {
        if (1 < func_num_args())
        {
            $reflectionObj = new ReflectionClass($className);
            return $reflectionObj->newInstanceArgs(array_slice(func_get_args(), 1));
        }
        return new $className;
    }

    class test
    {
        function __construct($param1 = null, $param2 = null)
        {
            printf("\n[%s] %s %s",
                get_class($this),
                var_export($param1, true),
                var_export($param2, true)
            );
        }
    }

    $inst = createInstance('test');
    $inst = createInstance('test', 'Foo', 'Bar');

    RĂ©sultat :

    [test] NULL NULL
    [test] 'Foo' 'Bar'
  9. piouPiouM

    Le précédent code complet et avec une meilleur présentation : http://pastie.textmate.org/private/8ctyk43xo08k8pulsexw …

  10. piouPiouM

    Pour le problème 1, est-ce que les donnĂ©es sont toujours ordonnĂ©es de la sorte ? C’est-Ă -dire les personnes ayant un mĂŞme nom de famille sont-elles consĂ©cutives ?
    Si non, le code Ă  factoriser ne fonctionne pas correctement ^^’

    P.S. : le commentaire #94459 a explosé :/, un autre a été soumis (et en attente de modération certainement)

  11. XoraX

    jolie ton premier code :) je materais ce soir.
    Pour le problème 1, oui les donnĂ©es sont toujours organisĂ©es de la sorte (les noms de famille sont consĂ©cutif). A la base ça sort d’une base de donnĂ©e donc c’est triĂ©.

  12. XoraX

    piouPiouM pour l’instant tu est le gagnant. Ta mĂ©thode marche parfaitement.
    Si quelqu’un trouve la soluce au premier problème ce n’est pas impossible qu’il gagne.
    plus que 3 jours !

  13. XoraX

    Thierry dĂ©solĂ© je t’ai pas rep pour ta dernière proposition.
    Le fait d’ajoutĂ© une ligne dans les donnĂ©es serait valable si on avait pas Ă  redĂ©finir les propriĂ©tĂ© de celle-ci (les index). Si ma ligne de rĂ©sultat contenait 15 propriĂ©tĂ©s, on ne verrait pas ce code du mĂŞme oeil… Donc ça ne va pas dans le sens d’un code propre.

  14. piouPiouM

    Cool :)

    Pour l’autre problème, tu as dĂ©jĂ  une version qui consomme moins de mĂ©moire et plus vĂ©loce (ça ne va pas chercher loin bien Ă©videmment) que 5 autres versions que j’ai pu tester :D (j’en testerai ptre encore une version :p)

  15. XoraX

    Je me répond à moi-même. Après avoir cherché à tête reposé voici ce qui me semble être la solution au deuxième problème :

    ...
    while (count($data)) {
        $name = '';
      $family = null;
      while (null !== $person = array_shift($data)) {
        if (!$family || $family == $person['family']) {
          $family = $person['family'];
          $names .= ' '.$person['name'];
          continue;
        }
        break;
      }
      render($family, $names);
    }

    Pour faire encore plus optimisĂ© on peut jouer avec les index pour supprimer l’array_shift et la concatenation couteuse de $names :

    ...
    while (count($data)) {
      $i = 0;
      while (isset($data[$i])) {
        if (!$i || $data[$i-1]['family'] == $data[$i]['family']) {
          $i++;
          continue;
        }
        break;
      }
      render($data[$i-1]['family'], ' '.implode(' ', array_splice($data, 0, $i)));
    }

    Et lĂ  je dirais qu’on est pas mal :D

Laissez un commentaire :