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 😀

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 😀
    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 😀 (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 😀

Laissez un commentaire :