All entries tagged "algorithmicadvent".

Page 1 of 1

Algorithmic Advent: 24 – Value object stack parser (PHP)

<?php

/**
 * A simple value object (VO)
 *
 */
class MyVO
{
    // Section title
    public $title;

    // Array of key->value mappings
    public $data;
}

/**
 * Simple tree structure to hold arbitrary data
 *
 */
class TreeNode
{
    protected $_data;
    protected $_children;

    public function __construct()
    {
        $this->_children = array();
        $this->_data = array();
    }

    public function addChild(TreeNode $child)
    {
        $this->_children[] = $child;
    }

    public function getChildren()
    {
        return $this->_children;
    }

    public function setValue($key, $value)
    {
        $this->_data[$key] = $value;
    }

    public function getValue($key)
    {
        return (isset($this->_data[$key]) ? $this->_data[$key] : null);
    }
}

/**
 * Standard PHP doesn't seem to contain a peek function for arrays
 *
 * @param array $array
 * @return mixed Last element from the array
 */
function array_peek(array &$array)
{
    $o = array_pop($array);
    array_push($array, $o);
    return $o;
}

/**
 * Creates a tree structure from file content
 *
 * @param string $file File to parse
 * @return TreeNode Root element of created tree
 */
function parseFileIntoTree($file)
{
    $lines = file($file);
    $root = new TreeNode();

    // Stack of TreeNode instances
    $stack = array();

    array_push($stack, $root);

    foreach ($lines as $line) {
        $line = trim($line);

        // Skip empty lines
        if ($line == '') {
            continue;
        }

        switch (substr($line, 0, 1)) {
            case '{':
                // Functionality is implemented in default case; ignore
                break;
            case '}':
                // Pop stack
                array_pop($stack);
                break;
            case '"':
                // Add new entry to last section on stack
                $node = array_peek($stack);
                $vo = $node->getValue('data');
                $parts = explode(' ', $line);
                $parts[0] = substr($parts[0], 1, strlen($parts[0]) - 2);
                $parts[1] = substr($parts[1], 1, strlen($parts[1]) - 2);
                $vo->data[$parts[0]] = $parts[1];
                break;
            default:
                // Initialize new section
                $vo = new MyVO();
                $vo->title = $line;
                $node = new TreeNode();
                $node->setValue('data', $vo);

                // Add new section to tree structure
                $parent = array_peek($stack);
                $parent->addChild($node);

                // Push new section on stack
                array_push($stack, $node);
        }
    }

    return $root;
}

/**
 * Displays the content of a tree structure
 *
 * @param TreeNode $node Root element
 * @param int $depth Recursion depth
 */
function showRec(TreeNode $node, $depth = 0)
{
    foreach ($node->getChildren() as $child) {
        $vo = $child->getValue('data');
        echo str_repeat('    ', $depth) . $vo->title . "\n";

        if ($vo->data != null) {
            foreach ($vo->data as $key => $value) {
                echo str_repeat('    ', $depth) . '  ' . $key . ' => ' . $value . "\n";
            }
        }

        showRec($child, $depth + 1);
    }
}

$root = parseFileIntoTree('input.txt');

echo '<pre>';
showRec($root);
echo '</pre>';

Example input:

Text1
{
   Colours
   {
     "1" "red"
     "2" "yellow"
     "3" "green"
   }
   
   Animals
   {
      "6" "Cat"
      "3" "Dog"
   }

}

Output:

Text1
    Colours
      1 => red
      2 => yellow
      3 => green
    Animals
      6 => Cat
      3 => Dog

Dec 24 2010 • by Marc Ermshaus • type=post language=en php algorithmicadvent0 comments

Algorithmic Advent: 23 – Frequency of words in a text

<?php

$text = <<<EOT
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur augue risus,
sollicitudin nec convallis sit amet, tristique sit amet elit. Nam mauris turpis,
pulvinar sed commodo ac, luctus quis erat. Nulla vitae velit tellus. Etiam non
eros quis quam fringilla dapibus. Cras nec risus lorem. Phasellus cursus, magna
nec elementum porttitor, risus mauris ultricies turpis, varius interdum nisi leo
vitae risus. Donec non justo vitae est condimentum varius at fermentum risus.
Proin volutpat dignissim arcu eu bibendum. Vivamus eget feugiat eros. Cum sociis
natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nam
varius sem sed mi pulvinar commodo. Ut lorem enim, tincidunt eu condimentum
vitae, gravida sit amet magna. Fusce blandit viverra convallis. Etiam egestas
neque vitae leo mattis placerat. Pellentesque hendrerit odio ac ligula eleifend
vel iaculis neque euismod. Nunc nec malesuada mauris. Duis non urna nec sem
porta bibendum sit amet in enim. Nam consequat elit vel purus porta ut euismod
sapien consectetur.

Donec venenatis pellentesque massa, eget mollis magna congue quis. Fusce eu
gravida magna. Phasellus quis eros enim. Nulla facilisi. In eu risus eu orci
sodales laoreet. Nunc tellus tellus, porttitor nec interdum in, suscipit et
risus. Ut adipiscing adipiscing molestie. Praesent nec nisl nulla, et congue
tellus. Etiam ornare, metus ac tristique euismod, elit orci pharetra neque,
consectetur feugiat orci nulla ac sem. Nullam suscipit, turpis quis mollis
cursus, ipsum ligula rutrum arcu, ac commodo eros neque non purus. Vestibulum
ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed
vestibulum, nunc non consectetur ullamcorper, justo mi pulvinar dolor, vel
viverra quam elit fringilla enim. Nam varius eros vel velit dignissim vitae
mollis risus aliquam. Sed accumsan, nunc sodales elementum consectetur, sapien
leo egestas orci, id commodo felis eros ac nisl. Nulla ut lectus eu felis
scelerisque ullamcorper pharetra at tellus. Nunc pellentesque rutrum velit id
pharetra. Etiam ac imperdiet nisi.
EOT;

header('Content-Type: text/plain; charset=UTF-8');

echo $text;

echo "\n--------------\n";

// Ignore case

$text = mb_strtolower($text);

// Everything consisting of 3+ letters shall be indexed as a word

$cleaned = trim(preg_replace('/(.+?)(?:(\p{L}{3,})|$)/su', ' $2', $text));

// Let's calculate some word frequencies

$words = explode(' ', $cleaned);

$map = array();

foreach ($words as $word) {
    if (array_key_exists($word, $map)) {
        $map[$word]++;
    } else {
        $map[$word] = 1;
    }
}

// Group by number of occurrences

$grouped = array();

foreach ($map as $word => $count) {
    $grouped[$count][] = $word;
}

krsort($grouped);

// Sort words per group alphabetically

foreach ($grouped as $count => &$words) {
    sort($words);
}
unset($words);

print_r($grouped);

Dec 23 2010 • by Marc Ermshaus • type=post language=en php algorithmicadvent0 comments

Algorithmic Advent: 22 – XSLT transformation of custom format to HTML

The following example generates HTML output from a custom XML format. In this case, the format describes some kind of a time-table. You should be able to view a rendered HTML document if you open the xml file in a capable browser (e.g. Firefox).

test.xml

<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="myformat_to_xhtml.xsl"?>
<myformat>
    <day>
        <course hour="8">Physics</course>
        <course hour="9">Physics</course>
        <course hour="10">Physics</course>
    </day>
    <day>
        <course hour="12">English</course>
        <course hour="13">English</course>
        <course hour="14">English</course>
    </day>
    <day>
        <course hour="14">Maths</course>
        <course hour="15">Maths</course>
        <course hour="16">Maths</course>
    </day>
    <day>
        <course hour="11">Computer Science</course>
        <course hour="12">Computer Science</course>
        <course hour="14">Computer Science</course>
    </day>
    <day>
        <course hour="8">Sports</course>
        <course hour="10">Sports</course>
        <course hour="16">Sports</course>
    </day>
</myformat>

myformat_to_xhtml.xsl

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output method="html"/>

    <xsl:template name="row">
        <xsl:param name="count" select="1"/>

        <xsl:if test="$count &lt;= 16">
            <tr>
                <td><xsl:value-of select="$count" /></td>
                <xsl:for-each select="/myformat/day">
                    <td>
                    <xsl:choose>
                        <xsl:when test="course[@hour=$count]">
                            <xsl:value-of select="course[@hour=$count]" />
                        </xsl:when>
                        <xsl:otherwise>-</xsl:otherwise>
                    </xsl:choose>
                    </td>
                </xsl:for-each>
            </tr>
            <xsl:call-template name="row">
                <xsl:with-param name="count" select="$count + 1"/>
            </xsl:call-template>
        </xsl:if>
    </xsl:template>

    <xsl:template match="/">
        <html>
            <head>
                <title>myformat_to_xhtml.xsl</title>
            </head>
            <body>
                <table border="1">
                    <tr>
                        <th>#</th>
                        <th>Monday</th>
                        <th>Tuesday</th>
                        <th>Wednesday</th>
                        <th>Thursday</th>
                        <th>Friday</th>
                    </tr>

                    <xsl:call-template name="row">
                        <xsl:with-param name="count" select="8"/>
                    </xsl:call-template>
                </table>
            </body>
        </html>
    </xsl:template>

</xsl:stylesheet>

Dec 22 2010 • by Marc Ermshaus • type=post language=en algorithmicadvent0 comments

Algorithmic Advent: 20 – League tables with PHP

index.php

<?php

require_once './League.php';
require_once './Match.php';
require_once './Team.php';

$league = new League();

$teams = array(
    0 => $league->createTeam('Manchester City'),
    1 => $league->createTeam('Chelsea'),
    2 => $league->createTeam('Arsenal'),
    3 => $league->createTeam('Blackburn Rovers')
);

$league->addMatch(new Match($teams[0], $teams[1], 2, 0))
       ->addMatch(new Match($teams[0], $teams[2], 2, 1))
       ->addMatch(new Match($teams[0], $teams[3], 2, 0))

       ->addMatch(new Match($teams[1], $teams[0], 2, 0))
       ->addMatch(new Match($teams[1], $teams[2], 3, 5))
       ->addMatch(new Match($teams[1], $teams[3], 2, 0))

       ->addMatch(new Match($teams[2], $teams[0], 2, 0))
       ->addMatch(new Match($teams[2], $teams[1], 3, 0))
       ->addMatch(new Match($teams[2], $teams[3], 2, 0))

       ->addMatch(new Match($teams[3], $teams[0], 2, 0))
       ->addMatch(new Match($teams[3], $teams[1], 1, 1))
       ->addMatch(new Match($teams[3], $teams[2], 2, 0));

$table = $league->getTable();


echo '<table border="1">';

echo '<tr>';
echo '<td>Rank</td>';
echo '<td>Team</td>';
echo '<td>Games</td>';
echo '<td>W</td>';
echo '<td>D</td>';
echo '<td>L</td>';
echo '<td>Goals</td>';
echo '<td>Goals Diff</td>';
echo '<td>Points</td>';

echo '</tr>';
$i = 1;
foreach ($table as $team) {
    echo '<tr>';
    echo '<td>' . $i++ . '</td>';
    echo '<td>' . $team->teamname . '</td>';
    echo '<td>' . $team->gamesPlayed . '</td>';
    echo '<td>' . $team->wins . '</td>';
    echo '<td>' . $team->draws . '</td>';
    echo '<td>' . $team->losses . '</td>';
    echo '<td>' . ($team->hgoals + $team->agoals) . ':' . ($team->hgoalsAgainst + $team->agoalsAgainst) . '</td>';
    echo '<td>' . (($team->hgoals + $team->agoals) - ($team->hgoalsAgainst + $team->agoalsAgainst)) . '</td>';
    echo '<td>' . $team->points . '</td>';
    echo '</tr>';
}

echo '</table>';

Team.php

<?php

class Team
{
    public $teamname;
    public $agoals = 0;
    public $hgoals = 0;
    public $agoalsAgainst = 0;
    public $hgoalsAgainst = 0;

    public $gamesPlayed = 0;

    public $wins = 0;
    public $draws = 0;
    public $losses = 0;

    public $points = 0;

    public function __construct($teamname)
    {
        $this->teamname     = $teamname;
    }
}

Match.php

<?php

class Match
{
    /** @var Team Home team */
    public $hteam;

    /**@var Team Away team */
    public $ateam;

    /**@var int Home team goals */
    public $hgoals;

    /** @var int Away team goals */
    public $agoals;

    public function __construct(Team $hteam, Team $ateam, $hgoals, $agoals)
    {
        $this->hteam = $hteam;
        $this->ateam = $ateam;
        $this->hgoals = $hgoals;
        $this->agoals = $agoals;
    }
}

League.php

<?php

class League
{
    protected $_matches = array();
    protected $_teams   = array();
    
    public function addMatch(Match $match)
    {
        $this->_matches[] = $match;
        return $this;
    }

    public function createTeam($teamname)
    {
        $team = new Team($teamname);
        $this->_teams[] = $team;

        return $team;
    }

    protected function _sortTeams(Team $a, Team $b)
    {
        // Absolute number of points
        if ($a->points != $b->points) {
            return ($a->points < $b->points);
        }

        // Direct comparison and away goals

        // Goals first team (x=a)
        $xgoals = 0;

        // Goals second team (y=b)
        $ygoals = 0;

        // Away goals team a
        $xagoals = 0;

        // Away goals team b
        $yagoals = 0;

        foreach ($this->_matches as $match) {
            if ($match->hteam === $a && $match->ateam === $b) {
                $xgoals += $match->hgoals;
                $ygoals += $match->agoals;
                $yagoals = $match->agoals;
            } else if ($match->hteam === $b && $match->ateam === $a) {
                $ygoals += $match->hgoals;
                $xgoals += $match->agoals;
                $xagoals = $match->agoals;
            }
        }

        /*
        if ($xgoals != $ygoals) {
            return ($xgoals < $ygoals);
        }

        if ($xagoals != $yagoals) {
            return ($xagoals < $yagoals);
        }
        */

        // Goal difference
        $adiff = ($a->hgoals + $a->agoals) - ($a->hgoalsAgainst + $a->agoalsAgainst);
        $bdiff = ($b->hgoals + $b->agoals) - ($b->hgoalsAgainst + $b->agoalsAgainst);

        if ($adiff != $bdiff) {
            return ($adiff < $bdiff);
        }

        // Absolute number of goals scored during season
        if (($a->hgoals + $a->agoals) != ($b->hgoals + $b->agoals)) {
            return (($a->hgoals + $a->agoals) < ($b->hgoals + $b->agoals));
        }

        // Team order undefined (possibly same rank)
        //throw new Exception('Couldn\'t determine team order.');
    }

    public function getTable()
    {
        foreach ($this->_matches as $match) {
            if ($match->hgoals > $match->agoals) {
                $match->hteam->points += 3;

                $match->hteam->wins++;
                $match->ateam->losses++;
            } else if ($match->hgoals == $match->agoals) {
                $match->hteam->points += 1;
                $match->ateam->points += 1;

                $match->hteam->draws++;
                $match->ateam->draws++;
            } else {
                $match->ateam->points += 3;

                $match->hteam->losses++;
                $match->ateam->wins++;
            }

            $match->hteam->hgoals += $match->hgoals;
            $match->hteam->hgoalsAgainst += $match->agoals;

            $match->ateam->agoals += $match->agoals;
            $match->ateam->agoalsAgainst += $match->hgoals;

            $match->hteam->gamesPlayed++;
            $match->ateam->gamesPlayed++;
        }

        $teamsSorted = $this->_teams;

        usort($teamsSorted, array($this, '_sortTeams'));

        return $teamsSorted;
    }
}

Dec 20 2010 • by Marc Ermshaus • type=post language=en php algorithmicadvent0 comments

Algorithmic Advent: 19 – File extension filter iterator (PHP/SPL)

/**
 * Example:
 *
 * $path      = '/a/directory';
 * $whitelist = array('txt'); // List of file extensions to filter
 *
 * $iterator = new FileExtensionFilterIterator(
 *                 new RecursiveIteratorIterator(
 *                     new RecursiveDirectoryIterator($path)),
 *                 $whitelist);
 *
 * foreach ($iterator as $file) {
 *     echo $file, "<br />";
 * } * 
 */
class FileExtensionFilterIterator extends FilterIterator
{
    protected $whitelist;

    public function __construct(Iterator $iterator, array $whitelist)
    {
        parent::__construct($iterator);
        $this->whitelist = $whitelist;
    }

    public function accept()
    {
        $fileInfo = parent::current();

        // Allow only files
        if (!$fileInfo->isFile()) {
            return false;
        }

        // Only allow file extensinos from $whitelist
        $pi = pathinfo($fileInfo->getFilename());
        if (!in_array(strtolower($pi['extension']), $this->whitelist)) {
            return false;
        }

        return true;
    }
}

Dec 19 2010 • by Marc Ermshaus • type=post language=en php algorithmicadvent0 comments

Algorithmic Advent: 18 – Jonsole

This is a very basic PHP/jQuery console „emulator“. I had to publish this in a hurry, so it is not as polished as I wanted it to be. I'm sorry for that.

<?php // Filename has to be "index.php"

// Server side

if (count($_GET) > 0) {
    $ret   = implode(' ', $_GET['args']);
    $error = false;

    switch ($_GET['args'][1]) {
        case 'date':
            $args = array_slice($_GET['args'], 2);
            $args = implode(' ', $args);

            $ret = date($args);
            break;
        case 'md5':
            $args = array_slice($_GET['args'], 2);
            $args = implode(' ', $args);

            $ret = md5($args);
            break;
        case 'strtoupper':
            $args = array_slice($_GET['args'], 2);
            $args = implode(' ', $args);

            $ret = strtoupper($args);
            break;
        default:
            $error = true;
            break;
    }

    echo json_encode(array('ret' => $ret, 'error' => $error));

    exit;
}

?><!DOCTYPE html>

<html>

    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <title>Jonsole</title>
          <script type="text/javascript" src="http://code.jquery.com/jquery-latest.js"></script>
<script type="text/javascript">

function jonsole_handle_php(args)
{
    $.get('index.php', {'args': args, 'r': Math.floor(Math.random() * 100000)}, function (data) {
        if (data.error) {
            jonsole_println("<span class=\"error\">PHP: Error: \"" + data.ret + "\"</span>");
        } else {
            jonsole_println(data.ret);
        }
        $("#jonsole-wrapper").attr({ scrollTop: $("#jonsole-wrapper").attr("scrollHeight") });
    }, 'json');
}

function jonsole_println(line)
{
    $('#jonsole').html($('#jonsole').html() + line + "<br />");
}

function jonsole_set(line)
{
    $('#jonsole').html(line);
}

function jonsole_run(expr)
{
    var tmp     = expr.split(/ /),
        unknown = false;

    jonsole_println('<span class="system">$</span> ' + expr);

    if (tmp[0] == 'php') {
        jonsole_handle_php(tmp);
    } else if(tmp[0] == 'clear') {
        jonsole_set('');
    } else if(tmp[0] == 'help') {
        jonsole_println('<span class="system">This version of Jonsole supports the following commands:<br />\
- clear<br />\
- help<br />\
- version<br />\
- php<br />\
&nbsp;&nbsp;- md5 <em>string</em><br />\
&nbsp;&nbsp;- strtoupper <em>string</em><br />\
&nbsp;&nbsp;- date <em>format</em></span>');
    } else if(tmp[0] == 'version') {
        jonsole_println('<span class="system">Jonsole version: r1</span>');
    } else {
        unknown = true;
    }

    if (unknown) {
        jonsole_println("<span class=\"error\">Unknown command: \"" + expr + "\"</span>");
    }

    $("#jonsole-wrapper").attr({ scrollTop: $("#jonsole-wrapper").attr("scrollHeight") });
}

$('document').ready(function () {
    jonsole_println("<span class=\"system\">[Welcome to Jonsole] Type \"help\" for more info</span>");

    $('#jonsole-input').bind('keydown', function (key) {

        if (key.which == 13) {
            jonsole_run($(this).val());
            $(this).val('');
            return false;
        }
    }).focus();
});

</script>

<style type="text/css">

* {
    margin: 0;
    padding: 0;
}

#jonsole-wrapper {
    padding: 5px;
    background: #000;
    color: #fff;
    border: 5px solid #999;
    width: 600px;
    height: 400px;
    font-family: monospace;
    font-size: 11px;
    overflow: auto;
}

#jonsole {

}

#jonsole-wrapper .system {
    background: #666;
}

#jonsole-wrapper .error {
    background: #900;
}

#jonsole-input {
    background: #000;
    color: #fff;
    border: 1px solid #666;
    width: 550px;
    font-family: monospace;
    font-size: 11px;
    display: inline;
}

</style>
    </head>

    <body>

        <form action="" method="post">
            <div id="jonsole-wrapper">
                <div id="jonsole"></div>
                <p><span class="system">$</span> <input id="jonsole-input" type="text" autocomplete="off" /></p>
            </div>
        </form>

    </body>

</html>

Dec 18 2010 • by Marc Ermshaus • type=post language=en php algorithmicadvent0 comments

Algorithmic Advent: 17 – Displaying the structure of an XML document

<?php

function nodeTypeToString($nodeType)
{
    $map = array(
         1 => 'XML_ELEMENT_NODE',
         2 => 'XML_ATTRIBUTE_NODE',
         3 => 'XML_TEXT_NODE',
         4 => 'XML_CDATA_SECTION_NODE',
         5 => 'XML_ENTITY_REFERENCE_NODE',
         6 => 'XML_ENTITY_NODE',
         7 => 'XML_PROCESSING_INSTRUCTION_NODE',
         8 => 'XML_COMMENT_NODE',                 //
         9 => 'XML_DOCUMENT_NODE',
        10 => 'XML_DOCUMENT_TYPE_NODE',           //
        11 => 'XML_DOCUMENT_FRAGMENT_NODE',
        12 => 'XML_NOTATION_NODE'
    );

    if (isset($map[$nodeType])) {
        return $map[$nodeType];
    }

    return 'UNKNOWN';
}

header('Content-Type: text/html; charset=UTF-8');

$doc = new DOMDocument();
$doc->preserveWhiteSpace = false;

$doc->loadXML('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
     <!DOCTYPE html [
 <!ELEMENT test (#PCDATA) >
 <!ENTITY % xx "&#37;zz;">
 <!ENTITY % zz "&#60;">
 ]>
    <html>
    <div>Some text<!--a comment-->
    <strong class="test">some <![CDATA[<tag>]]> more text</strong>
    </div></html>');

function rec(DOMNode $node, $indent = 0)
{
    if ($node->hasChildNodes()) {
        foreach ($node->childNodes as $child) {
            echo '<tr>';
            echo '<td>' . str_repeat('&nbsp;', $indent)
                    . $child->nodeName . '</td>';
            echo '<td>' . nodeTypeToString($child->nodeType) . '</td>';

            $nv = htmlspecialchars($child->nodeValue);

            $nv = str_replace(array("\n", "\r", "\t", ' '),
                              array('\n', '\r', '\t', '&nbsp;'), $nv);

            if ($nv === '') {
                $nv = '{empty}';
            } else {
                $nv = '"' . $nv . '"';
            }

            echo '<td>' . $nv . '</td>';
            echo '</tr>';
            rec($child, $indent + 4);
        }
    }
}

echo '<table border="1">';
echo '<tr><th>nodeName</th><th>nodeType</th><th>nodeValue</th></tr>';
rec($doc);
echo '</table>';

Dec 17 2010 • by Marc Ermshaus • type=post language=en php algorithmicadvent0 comments

Algorithmic Advent: 16 – Expandable tree menu with PHP and jQuery

<?php
/**
 * This is an example on how to create an expandable tree menu structure with
 * PHP, CSS and jQuery.
 *
 * @version 2010-Dec-16
 *
 * @author Marc Ermshaus <http://www.ermshaus.org/>
 * @license GNU General Public License <http://www.gnu.org/licenses/gpl.html>
 */

define('X_BASEURL', '/nb/menutest');
define('X_CHARSET', 'UTF-8');

/**
 * Assembles an internal URL
 *
 * @param string $path
 * @param array $queryPart
 * @return string The assembled URL
 */
function url($path, $queryPart = array())
{
    $baseUrl = X_BASEURL;
    
    $url = $baseUrl . $path;

    if (count($queryPart) > 0) {
        $url .= '?' . http_build_query($queryPart);
    }

    return $url;
}

/**
 * Escapes a string for HTML display
 *
 * @param strng $s
 * @param int $quoteStyle
 * @param string $charset
 * @return string
 */
function escape($s, $quoteStyle = ENT_QUOTES, $charset = X_CHARSET)
{
    return htmlspecialchars($s, $quoteStyle, $charset);
}

/**
 * Arranges the input data into a tree structure
 *
 * @param array $data
 * @param int|null $parentId
 * @return array
 */
function toTree(array $data, $parentId = null)
{
    $rec = function (array $data, array &$root, $parent_id = null) use (&$rec)
    {
        $root['children'] = array();

        foreach ($data as $item) {
            if ($item['parent_id'] === $parent_id) {

                $newChild = array('data' => $item['data']);

                $root['children'][] = &$newChild;

                $rec($data, $newChild, $item['id']);
                unset($newChild);
            }
        }
    };

    $root = array('title' => 'root');

    $rec($data, $root, $parentId);

    return $root;
}

/**
 * Creates the HTML output for a navigation tree
 *
 * @param array $root Tree root (see toTree function)
 * @return string HTML code of navigation
 */
function menuHelper($root)
{
    $s = '';

    $s .= '<ul id="navigation">' . "\n";

    $recm = function (array $node, $depth = 0) use (&$recm)
    {
        $pad = '    ';

        foreach ($node['children'] as $child) {
            $spanClasses = 'title ';

            if (count($child['children']) > 0) {
                $spanClasses .= 'hasChildren ';
            }

            $spanClasses = trim($spanClasses);

            $s .= str_repeat($pad, $depth) . '<li>';
            
            $s .= '<span class="'.$spanClasses.'">';

            if ($child['data']['path'] !== null) {
                $s .= '<a href="'.url($child['data']['path']).'">'
                    . escape($child['data']['title']) . '</a>';
            } else {
                $s .= escape($child['data']['title']);
            }

            $s .= '</span>';

            if (count($child['children']) > 0) {
                $s .= "\n";

                $depth++;

                $s .= str_repeat($pad, $depth) . '<ul>' . "\n";
                $s .= $recm($child, $depth + 1);
                $s .= str_repeat($pad, $depth) . '</ul>' . "\n";

                $depth--;
                
                $s .= str_repeat($pad, $depth);
            }

            $s .= '</li>' . "\n";
        }

        return $s;
    };

    $s .= $recm($root, 1);

    $s .= '</ul>' . "\n";

    return $s;
}

$menuData = array(
    array('id'        => 1,
          'parent_id' => null,
          'data'      => array('title' => 'Item 1',
                               'path'  => '/item1')),
    array('id'        => 2,
          'parent_id' => null,
          'data'      => array('title' => 'Item 2',
                               'path'  => '/item2')),
    array('id'        => 3,
          'parent_id' => 1,
          'data'      => array('title' => 'Item 1.1',
                               'path'  => '/item1/item1')),
    array('id'        => 4,
          'parent_id' => 2,
          'data'      => array('title' => 'Item 2.1',
                               'path'  => '/item2/item1')),
    array('id'        => 5,
          'parent_id' => 2,
          'data'      => array('title' => 'Item 2.2',
                               'path'  => '/item2/item2')),
    array('id'        => 6,
          'parent_id' => 1,
          'data'      => array('title' => 'Item 1.2',
                               'path'  => '/item1/item2')),
    array('id'        => 7,
          'parent_id' => 4,
          'data'      => array('title' => 'Item 2.1.1',
                               'path'  => '/item2/item1/item1')),
    array('id'        => 8,
          'parent_id' => null,
          'data'      => array('title' => 'Item 3',
                               'path'  => '/item3'))
);

?><!DOCTYPE html>

<html>

    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <title>Expandable tree menu</title>
        <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js"></script>

        <style type="text/css">
        #navigation .title {
            cursor: pointer;
        }

        #navigation .hasChildren {
            background: #9ff;
        }

        #navigation .hasChildren:after {
            content: " (expand)";
        }

        #navigation .hidden {
            display: none;
        }

        #navigation .open-category {
            
        }

        /* "#navigation .open-category > .title" or
           "#navigation .title.open" won't work in IE6 */
        #navigation .open-title {
            color: #f00;
        }
        </style>

        <script type="text/javascript">
        $(document).ready(function () {
            $('#navigation ul').addClass('hidden');
            $('#navigation li').click(function (event) {
                // Entry has children?
                if ($(this).children('ul').length > 0) {
                    $(this).toggleClass('open-category');
                    $(this).children('.title').toggleClass('open-title');
                    $(this).children('ul').toggleClass('hidden');
                }
                event.stopPropagation();
            });
        });
        </script>
    </head>

    <body>

        <?php echo menuHelper(toTree($menuData)); ?>

    </body>

</html>

Dec 16 2010 • by Marc Ermshaus • type=post language=en php algorithmicadvent4 comments

Algorithmic Advent: 15 – C64 Koala Painter file converter

This class is meant to convert images created or edited with the C64 Koala Painter application to a more modern file format. It is basically a port of Peter Krefting's koalatoppm project. The class is also part of his repository (see last link), so you might want to use that version rather than the following one which might be dated.

/**
 * Koala Painter image converter
 *
 * The Commodore 64 version of Koala Painter used a fairly simple file format
 * corresponding directly to the way bitmapped graphics are handled on the
 * computer: A two-byte load address, followed immediately by 8000 bytes of raw
 * bitmap data, 1000 bytes of raw "Video Matrix" data, 1000 bytes of raw "Color
 * RAM" data, and a one-byte Background Color field.
 *
 *   (Source: http://en.wikipedia.org/wiki/KoalaPad#File_Format)
 *
 * This class is based on the koalatoppm application written by Peter Karlsson.
 * <http://git.debian.org/?p=users/peterk/koalatoppm.git>
 *
 * @version 2010-Dec-11
 *
 * @author Marc Ermshaus <http://www.ermshaus.org/>
 * @license GNU General Public License <http://www.gnu.org/licenses/gpl.html>
 */
class KoalaConverter
{
    protected static $pixelmask = array(0xC0, 0x30, 0x0C, 0x03);

    protected static $pixeldisplacement = array(6, 4, 2, 0);

    /**
     * @var array Mappings: C64 colour index -> RGB
     */
    protected static $c64colours = array(
        array(  0,   0,   0), // Black
        array(255, 255, 255), // White
        array(189,  24,  33), // Red
        array( 49, 231, 198), // Cyan
        array(181,  24, 231), // Purple
        array( 24, 214,  24), // Green
        array( 33,  24, 173), // Blue
        array(222, 247,   8), // Yellow
        array(189,  66,   0), // Orange
        array(107,  49,   0), // Brown
        array(255,  74,  82), // Light red
        array( 66,  66,  66), // Gray 1
        array(115, 115, 107), // Gray 2
        array( 90, 255,  90), // Light green
        array( 90,  82, 255), // Light blue
        array(165, 165, 165)  // Gray 3
    );

    /**
     * @var array Stores the image's data as an array of RGB triples
     */
    protected $triples = array();

    /**
     * Returns the RGB value of a colour index
     *
     * @param int $index The colour's index
     * @return array RGB colour triplet
     */
    protected function getColour($index)
    {
        if ($index >= 0 && $index <= 15) {
            return self::$c64colours[$index];
        } else {
            throw new Exception('Unknown colour index: ' . $index);
        }
    }

    /**
     * Loads a Koala Painter (.koa) image
     *
     * @param string $file Path to image file
     */
    public function load($file)
    {
        $tmp = file_get_contents($file);
        $l   = strlen($tmp);
        $ret = array();

        if ($l !== 10003) {
            throw new Exception('Input data of wrong length');
        }

        $loadaddress = substr($tmp,     0,    2);
        $image       = substr($tmp,     2, 8000);
        $colour1     = substr($tmp,  8002, 1000);
        $colour2     = substr($tmp,  9002, 1000);
        $background  = substr($tmp, 10002,    1);

        if ("\x00\x60" !== $loadaddress) {
            throw new Exception('Input data is not a .koa file');
        }

        // Image
        for ($y = 0; $y < 200; $y++) {
            for ($x = 0; $x < 160; $x++) {
                
                // Get value of pixel at (x,y)
                /** @todo not sure how this is supposed to work */
                $index = (int) (floor($x / 4) * 8
                                + ($y % 8)
                                + floor($y / 8) * 320);
                
                $pixel = (ord($image[$index]) & self::$pixelmask[$x % 4])
                         >> self::$pixeldisplacement[$x % 4];

                // Colour index
                /** @todo not sure how this is supposed to work */
                $ci = (int) (floor($x / 4) + floor($y / 8) * 40);
                $k = 0;

                switch ($pixel) {
                    /** @todo I am not sure whether the bitwise operation for
                     *    background is correct (see: C original) */
                    case 0: $k = ord($background) & 0x0F;   break;
                    case 1: $k = ord($colour1[$ci]) >> 4;   break;
                    case 2: $k = ord($colour1[$ci]) & 0x0F; break;
                    case 3: $k = ord($colour2[$ci]) & 0x0F; break;
                    default:
                        throw new Exception('Internal error');
                        break;
                };

                $this->triples[] = $this->getColour($k);
            }
        }
    }

    /**
     * Returns a PNG version of the image
     *
     * @param bool $expand Double every pixel in horizontal direction?
     * @return resource Image resource identifier
     */
    public function exportPng($expand = true)
    {
        $width  = ($expand) ? 320 : 160;
        $height = 200;

        $gd = imagecreatetruecolor($width, $height);

        for ($i = 0; $i < $width * $height; $i++) {
            if ($expand) {
                $rgb = $this->triples[(int) floor($i / 2)];
            } else {
                $rgb = $this->triples[$i];
            }

            $x = $i % $width;
            $y = (int) floor($i / $width);

            $colour = imagecolorallocate($gd, $rgb[0], $rgb[1], $rgb[2]);
            imagesetpixel($gd, $x, $y, $colour);
        }

        return $gd;
    }
}



$koa = new KoalaConverter();
$koa->load('./pic_b_turrican.koa');

header('Content-Type: image/png');

imagepng($koa->exportPng());

Dec 15 2010 • by Marc Ermshaus • type=post language=en php algorithmicadvent0 comments

Algorithmic Advent: 14 – PHP/jQuery Ajax testing template

I wrote this template because I was getting tired of always having to piece together all of the different components and the exact syntax of the jQuery .ajax() method whenever I wanted to build a simple testing environment for Ajax calls.

<?php

if (isset($_GET['ajax'])) {
    if (!isset($_POST['action'])) {
        exit;
    }

    switch ($_POST['action']) {
        case 'demo1':            
            echo json_encode(array('test' => 'Hello world!'));
            break;
        default:
            break;
    }

    exit;
}

?><!DOCTYPE html>

<html>

    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <title>Ajax testing template</title>
        <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js"></script>

        <script type="text/javascript">

            function ajaxCallDemo1()
            {
                $.ajax({
                    type     : "POST",
                    async    : true,
                    url      : "?ajax",
                    cache    : false,
                    data     : {action      : 'demo1'},
                    dataType : 'json',
                    success: function(data) {
                        $('#demo1').html(data['test']);
                    },
                    error: function() {
                        alert("Something went wrong");
                    }
                });
            }

            $(document).ready(function() {                
                ajaxCallDemo1();
            });

        </script>
    </head>

    <body>
        <div id="demo1"></div>
    </body>

</html>

Dec 14 2010 • by Marc Ermshaus • type=post language=en php algorithmicadvent0 comments

Algorithmic Advent: 13 – PHP Caesar cipher generator/cracker

Most people should be familiar with the Caesar cipher, a single alphabet substitution cipher. It is very easy to crack, even by hand. Writing an application to do this is a little harder, though. Here's my attempt at it.

/**
 * A tool to crack Caesar cyphers
 * 
 *   - http://en.wikipedia.org/wiki/Caesar_cipher
 *
 * This article on language detection might come in handy at some point in the
 * future:
 *
 *   - http://phpir.com/language-detection-with-n-grams
 *
 * @version 2010-Dec-13
 * @author Marc Ermshaus <http://www.ermshaus.org/>
 * @license GNU General Public License (version 3 or later)
 *          <http://www.gnu.org/licenses/gpl.html>
 */
class CaesarCracker
{
    /**
     * Taken from http://de.wikipedia.org/wiki/Buchstabenhäufigkeit
     * 
     * Here's the same thing for English:
     *     http://en.wikipedia.org/wiki/Letter_frequency
     *         #Relative_frequencies_of_letters_in_the_English_language
     *
     * @var Relative frequency of letters in the German language
     */
    protected $probabilityMap = array(
        'a' =>  0.0651,
        'b' =>  0.0189,
        'c' =>  0.0306,
        'd' =>  0.0508,
        'e' =>  0.1740,
        'f' =>  0.0166,
        'g' =>  0.0301,
        'h' =>  0.0476,
        'i' =>  0.0755,
        'j' =>  0.027,
        'k' =>  0.0121,
        'l' =>  0.0344,
        'm' =>  0.0253,
        'n' =>  0.0251,
        'o' =>  0.0978,
        'p' =>  0.0079,
        'q' =>  0.0002,
        'r' =>  0.0700,
        's' =>  0.0727,
        't' =>  0.0615,
        'u' =>  0.0435,
        'v' =>  0.0067,
        'w' =>  0.0189,
        'x' =>  0.0003,
        'y' =>  0.0004,
        'z' =>  0.0113,
    );

    /**
     * 
     */
    public function __construct()
    {
        arsort($this->probabilityMap);
    }

    /**
     * Calculates a table of relative letter frequencies for the input
     *
     * @param string $input Data to create table for
     * @return array Letter distribution table
     */
    protected function createDistributionTable($input)
    {
        $tmp = array();

        $l = strlen($input);
        $realLength = 0;

        $charlist = range('a', 'z');

        for ($i = 0; $i < $l; $i++) {
            $c = substr($input, $i, 1);

            if (!in_array($c, $charlist)) {
                continue;
            }

            $realLength++;

            if (isset($tmp[$c])) {
                $tmp[$c]++;
            } else {
                $tmp[$c] = 1;
            }
        }

        foreach ($tmp as $char => &$amount) {
            $amount /= $realLength;
        }
        unset($amount);

        arsort($tmp);

        return $tmp;
    }

    /**
     * Calculates by which amount the passed test distribution differs from the
     * expected distribution
     *
     * @param array $dt Test distribution
     * @return float Amount of "badness". Lower values are *better*
     */
    protected function calculateWeightedBadness(array $dt)
    {
        $pm = $this->probabilityMap;
        $badness = 0;

        $i = 0;

        foreach ($dt as $char => $probability) {
            $badness += abs($pm[$char] - $probability);
            $i++;
        }

        return $badness;
    }

    /**
     * Shifts the input by specified offset
     *
     * abcdefghijklmnopqrstuvwxyz shifted by 4 becomes
     * efghijklmnopqrstuvwxyzabcd
     *
     * @param string $input String to encrypt
     * @param int $offset Offset to shift by
     * @return string Encrypted string
     */
    public function caesarShift($input, $offset)
    {
        $ret = '';

        $shiftingTable = implode(range('a', 'z')) . implode(range('a', 'z'));

        $charlist = range('a', 'z');

        $chars = str_split($input);

        foreach ($chars as $char) {

            if (in_array($char, $charlist)) {
                $ret .= substr($shiftingTable,
                    strpos($shiftingTable, $char) + $offset, 1);
            } else {
                $ret .= $char;
            }

            
        }

        return $ret;
    }

    /**
     * Attempts to crack an enciphered string
     *
     * @param string $input The enciphered string (all lower-case)
     * @return string Original string (hopefully)
     */
    public function crack($input)
    {
        print_r($pm);

        $badnesses = array();

        $shiftingTable = implode(range('a', 'z')) . implode(range('a', 'z'));

        $dt = $this->createDistributionTable($input);

        for ($offset = 0; $offset <= 25; $offset++) {
            $tmp = array();

            foreach ($dt as $char => $probability) {
                $tmp[substr($shiftingTable,
                    strpos($shiftingTable, $char) + $offset, 1)] = $probability;
            }

            $badnesses[$offset] = $this->calculateWeightedBadness($tmp);
        }

        asort($badnesses);

        $keys = array_keys($badnesses);

        return $this->caesarShift($input, $keys[0]);
    }
}



// Test string (in German)
$input = 'Aufklaerung ist der Ausgang des Menschen aus seiner selbst'
       . ' verschuldeten Unmuendigkeit.';

// Cracker works only with lower-case letters
$input = strtolower($input);

$c = new CaesarCracker();

// Shift input by a random offset
$shifted = $c->caesarShift($input, mt_rand(1, 13));

echo 'Shifted: ' . $shifted . '<br/>';
echo 'Cracked: ' . $c->crack($shifted);

Dec 13 2010 • by Marc Ermshaus • type=post language=en php algorithmicadvent0 comments

Algorithmic Advent: 12 – Clean URLs with mod_rewrite

.htaccess:

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} -s [OR]
RewriteCond %{REQUEST_FILENAME} -l [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^.*$ - [NC,L]
RewriteRule ^.*$ index.php [NC,L]

This forwards every non-existing resource to index.php.

index.php:

$baseUrl = ''; // Use this if your application resides in a sub directory
$path    = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);

$path = substr($path, strlen($baseUrl));

/*
 * Associate URL path with logic code. (In a real world scenario, this would be
 * done much more sophisticatedly. Take a look at Zend_Router for instance.)
 */

switch ($path) {
    case '/':         echo 'Index page';   break;
    case '/contact':  echo 'Contage page'; break;
    case '/projects': echo 'Project page'; break;
    default:          echo 'Not mapped';   break;
}

Dec 12 2010 • by Marc Ermshaus • type=post language=en php algorithmicadvent0 comments

Algorithmic Advent: 11 – Raphaël JS demo

Raphaël is a JavaScript vector drawing library that uses SVG and VML to render its object tree. Thanks to VML, the library even supports Internet Explorer 6.0. There are some great demos of Raphaël's capabilities on the project's homepage.

I had some fun with it a few weeks ago. Here's the result. It's nothing special but it might give you an idea on how to work with Raphaël.

You might want to take a look into the official documentation to fully comprehend what's going on. But please keep in mind that this is mere messing around than writing good code.

<!DOCTYPE html>

<html>

    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <title>New</title>

        <!-- Available at http://raphaeljs.com/ -->
        <script type="text/javascript" src="./raphael-min.js"></script>

        <script type="text/javascript">

        window.onload = function () {

            createBox = function (paper, label) {
                var rect   = paper.rect(0, 0, 80, 20),
                    rectbb = rect.getBBox(),
                    text   = paper.text(0, 0, label),
                    group  = null;

                rect.attr({fill: '#eee'});
                text.translate(rectbb.width / 2, rectbb.height / 2);

                group = paper.set();
                group.push(rect);
                group.push(text);

                group.mouseover(function () {
                    rect.attr({fill: '#fcc'});
                });

                group.mouseout(function () {
                    rect.attr({fill: '#eee'});
                });

                group.attr({cursor: 'pointer'});

                return group;
            };

            connect = function(paper, obj1, obj2) {
                var obj1bb    = obj1.getBBox(),
                    obj2bb    = obj2.getBBox(),
                    path      = '',
                    connector = null;

                path = "M " + (obj1bb.x + obj1bb.width / 2) + " "
                       + (obj1bb.y + obj1bb.height / 2)
                       + "L " + (obj2bb.x + obj2bb.width / 2) + " "
                       + (obj2bb.y + obj2bb.height / 2);

                connector = paper.path(path);
                connector.toBack();
                return connector;
            };

            var paper = Raphael(0, 0, 400, 400),
                box1  = createBox(paper, "Hello World!"),
                box2  = createBox(paper, "A Test"),
                box3  = createBox(paper, "Another Test");

            box1.translate(Math.random() * 350, Math.random() * 380);
            box2.translate(Math.random() * 350, Math.random() * 380);
            box3.translate(Math.random() * 350, Math.random() * 380);

            connect(paper, box1, box2);
            connect(paper, box2, box3);
        };

        </script>
    </head>

    <body>

    </body>

</html>

Dec 11 2010 • by Marc Ermshaus • type=post language=en javascript algorithmicadvent0 comments

Algorithmic Advent: 10 – Hexadecimal representation

/**
 * Displays data in hexadecimal encoding
 *
 * @param string $data Data to display in hex viewer
 * @return string Hex representation of input data
 */
function hexView($data)
{
    $data = (string) $data;

    $chunklen = 16;
    $l = strlen($data);
    
    if ($l === 0) {
        return '';
    }

    $ret = '00000000  ';

    $origLine = '';

    $lc = 0; // line counter
    $rc = 0; // row counter

    for ($i = 0; $i < $l; $i++) {
        $ret .= sprintf('%02X', ord(substr($data, $i, 1)));

        $n = ord(substr($data, $i, 1));

        if ($n <= 32 || $n === 127) {
            $origLine .= '.';
        } else {
            $origLine .= substr($data, $i, 1);
        }

        $rc++;
        if ($rc === 2) {
            $origLine .= '';
            $ret .= ' ';
            $rc = 0;
        }

        if (($i + 1) % $chunklen === 0) {

            $ret .= ' ' . $origLine;
            $origLine = '';

            $ret .= "\n";
            $lc++;

            if ($lc === 4) {
                $ret .= "\n";
                $lc = 0;
            }
            $rc = 0;

            if ($i < $l - 1) {
                $ret .= sprintf('%08X  ', $i + 1);
            }
        }        
    }

    if ($origLine !== '') {
        $k = $chunklen - ($i) % $chunklen;

        for ($i = 0; $i < $k; $i++) {
            $ret .= '  ';

            if ($i % 2 === 0) {
                $ret .= ' ';
            }
        }

        $ret .= ' ' . $origLine;
    }

    return $ret;
}



header('Content-Type: text/plain;');

$data = '';

for ($i = 0; $i <= 255; $i++) {
    $data .= chr($i);
}

echo hexView($data);

Output:

00000000  0001 0203 0405 0607 0809 0A0B 0C0D 0E0F  ................
00000010  1011 1213 1415 1617 1819 1A1B 1C1D 1E1F  ................
00000020  2021 2223 2425 2627 2829 2A2B 2C2D 2E2F  .!"#$%&'()*+,-./
00000030  3031 3233 3435 3637 3839 3A3B 3C3D 3E3F  0123456789:;<=>?

00000040  4041 4243 4445 4647 4849 4A4B 4C4D 4E4F  @ABCDEFGHIJKLMNO
00000050  5051 5253 5455 5657 5859 5A5B 5C5D 5E5F  PQRSTUVWXYZ[\]^_
00000060  6061 6263 6465 6667 6869 6A6B 6C6D 6E6F  `abcdefghijklmno
00000070  7071 7273 7475 7677 7879 7A7B 7C7D 7E7F  pqrstuvwxyz{|}~.

00000080  8081 8283 8485 8687 8889 8A8B 8C8D 8E8F  €‚ƒ„…†‡ˆ‰Š‹ŒŽ
00000090  9091 9293 9495 9697 9899 9A9B 9C9D 9E9F  ‘’“”•–—˜™š›œžŸ
000000A0  A0A1 A2A3 A4A5 A6A7 A8A9 AAAB ACAD AEAF   ¡¢£¤¥¦§¨©ª«¬­®¯
000000B0  B0B1 B2B3 B4B5 B6B7 B8B9 BABB BCBD BEBF  °±²³´µ¶·¸¹º»¼½¾¿

000000C0  C0C1 C2C3 C4C5 C6C7 C8C9 CACB CCCD CECF  ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏ
000000D0  D0D1 D2D3 D4D5 D6D7 D8D9 DADB DCDD DEDF  ÐÑÒÓÔÕÖרÙÚÛÜÝÞß
000000E0  E0E1 E2E3 E4E5 E6E7 E8E9 EAEB ECED EEEF  àáâãäåæçèéêëìíîï
000000F0  F0F1 F2F3 F4F5 F6F7 F8F9 FAFB FCFD FEFF  ðñòóôõö÷øùúûüýþÿ

Dec 10 2010 • by Marc Ermshaus • type=post language=en php algorithmicadvent0 comments

Algorithmic Advent: 09 – Run-length encoding

Use this functions to encode data that contains long sequences of the same byte such as barcodes or black-and-white images.

/**
 * Encodes the input using a byte-wise run-length algorithm
 *
 * Example:
 *
 *   $input = str_repeat('A', 1)
 *          . str_repeat('B', 32)
 *          . str_repeat('C', 17)
 *          . str_repeat('D', 517);
 *
 *   echo rle_encode($input);
 *
 * Outputs a string containing the following bytes:
 *
 *   01 41 20 42 11 43 FF 44 FF 44 07 44
 *
 * This translates to:
 *
 *   Amount  Byte
 *   ============
 *      1    "A"
 *     32    "B"
 *     17    "C"
 *    255    "D"
 *    255    "D"
 *      7    "D"
 *
 * @param string $data String to encode
 * @return string Encoded string
 */
function rle_encode($data)
{
    $data = (string) $data;
    $ret  = '';
    $len  = strlen($data);
    $char = '';
    $cnt  = 0;

    for ($i = 0; $i < $len; $i++) {
        $c = substr($data, $i, 1);
        if ($c !== $char || $cnt === 255) {
            if ($char !== '') {
                $ret .= chr($cnt) . $char;
            }
            $char = $c;
            $cnt = 1;
        } else {
            $cnt++;
        }
    }

    if ($char !== '') {
        $ret .= chr($cnt) . $char;
    }

    return $ret;
}

/**
 * Decodes a run-length encoded string
 *
 * See description of rle_encode for details.
 *
 * @param string $data Run-length encoded string
 * @return string Decoded string
 */
function rle_decode($data)
{
    $data = (string) $data;
    $ret  = '';
    $len  = strlen($data);

    if ($len % 2 !== 0) {
        throw new Exception('Input data is malformed');
    }

    for ($i = 0; $i < $len; $i += 2) {
        $cnt = substr($data, $i    , 1);
        $c   = substr($data, $i + 1, 1);

        $ret .= str_repeat($c, ord($cnt));
    }

    return $ret;
}



// Test

$input = str_repeat('A', 1)
       . str_repeat('B', 32)
       . str_repeat('C', 17)
       . str_repeat('D', 517);

$rle = rle_encode($input);

if ($input === rle_decode($rle)) {
    echo 'Success';
}

Dec 9 2010 • by Marc Ermshaus • type=post language=en php algorithmicadvent0 comments

Algorithmic Advent: 08 – Namespace autoloading

If you're not using PHP namespaces or if you're not using autoloading, now would be a good time to start. Loading dependencies won't get easier that that.

/* This code should be placed in your index file */

// Set library path (put your classes in this directory)
$libraryPath = realpath('./library');

// Add to include path
set_include_path($libraryPath . PATH_SEPARATOR . get_include_path());

// Autoloader
spl_autoload_register(function ($class) {
    require_once str_replace('\\', '/', $class) . '.php';
});



use Demo\Foo,
    Some\Other\Name\Space\Bar;

$f = new Foo(); // The files './library/Demo/Foo.php' and
$b = new Bar(); // './library/Some/Other/Name/Space/Bar.php'
                // will be autoloaded

Dec 8 2010 • by Marc Ermshaus • type=post language=en php algorithmicadvent0 comments

Algorithmic Advent: 07 – A very basic templating solution

One major design goal when building a web application is the separation of business logic and presentation. A simple way to achieve this are „View“ classes which act as an interface between the application's data model and for instance HTML template scripts that are used to render the output.

The following example shows a very basic implementation of this concept.

/**
 * A very simple View class
 */
class SimpleView
{
    /**
     * @var array Stores data for the template script
     */
    protected $vars = array();

    /**
     * Sets a template variable
     *
     * @param string $name
     * @param string $value
     */
    public function __set($name, $value)
    {
        $this->vars[$name] = $value;
    }

    /**
     * Gets a template variable
     *
     * @param string $name
     * @return string|null
     */
    public function __get($name)
    {
        return (isset($this->vars[$name])) ? $this->vars[$name] : null;
    }

    /**
     * Renders a template script
     *
     * @param string $template
     */
    public function render($template)
    {
        include $template;
    }
}

Usage example:

$view = new SimpleView();

$view->heading = 'Hello World!';
$view->content = 'Here\'s some content.';

$tmp = array();
for ($i = 0; $i < 10; $i++) {
    $tmp[] = 'Test ' . $i;
}
$view->someMoreData = $tmp;

$view->render('test.phtml');

The corresponding template file (test.pthml):

<h1><?php echo $this->heading; ?></h1>
<p><?php echo $this->content; ?></p>

<ul>
<?php foreach ($this->someMoreData as $data) : ?>
    <li><?php echo $data; ?></li>
<?php endforeach; ?>
</ul>

Dec 7 2010 • by Marc Ermshaus • type=post language=en php algorithmicadvent0 comments

Algorithmic Advent: 06 – Transform DOMNode to array

/**
 * Transforms the contents of a DOMNode to an associative array
 * 
 * XML node names become array keys, attributes will be discarded. If $node
 * doesn't contain child nodes, a string will be returned.
 *
 * For more information about DOMNode and related classes see the documentation
 * of DOMDocument <http://php.net/manual/en/class.domdocument.php>.
 *
 * @param DOMNode $node DOMDocument node
 * @return string|array Associative array or string with node content
 */
function domNodeToArray(DOMNode $node)
{
    $ret = '';

    if ($node->hasChildNodes()) {
        if ($node->firstChild === $node->lastChild
            && $node->firstChild->nodeType === XML_TEXT_NODE
        ) {
            // Node contains nothing but a text node, return its value
            $ret = trim($node->nodeValue);
        } else {
            // Otherwise, do recursion
            $ret = array();
            foreach ($node->childNodes as $child) {
                if ($child->nodeType !== XML_TEXT_NODE) {
                    // If there's more than one node with this node name on the
                    // current level, create an array
                    if (isset($ret[$child->nodeName])) {
                        if (!is_array($ret[$child->nodeName])
                            || !isset($ret[$child->nodeName][0])
                        ) {
                            $tmp = $ret[$child->nodeName];
                            $ret[$child->nodeName] = array();
                            $ret[$child->nodeName][] = $tmp;
                        }
                        
                        $ret[$child->nodeName][] = domNodeToArray($child);
                    } else {
                        $ret[$child->nodeName] = domNodeToArray($child);
                    }
                }
            }
        }
    }

    return $ret;
}

Usage example:

$xmlCode = <<<EOT
<?xml version="1.0" encoding="utf-8"?>
<characters>
  <character>
    <name>Steve</name>
    <role>King</role>
    <stats>
      <age>52</age>
      <weight>85</weight>
    </stats>
  </character>
  <character>
    <name>Johnny</name>
    <role>Knight</role>
    <role>Jester</role>
  </character>
</characters>
EOT;

$dom = new DOMDocument();
$dom->loadXML($xmlCode);

$array = array();

$xpath = new DOMXPath($dom);
foreach ($xpath->query('/characters', $dom) as $node) {
    $array = domNodeToArray($node);
}

echo '<pre>';
print_r($array);

This will return the following array structure:

Array
(
    [character] => Array
        (
            [0] => Array
                (
                    [name] => Steve
                    [role] => King
                    [stats] => Array
                        (
                            [age] => 52
                            [weight] => 85
                        )

                )

            [1] => Array
                (
                    [name] => Johnny
                    [role] => Array
                        (
                            [0] => Knight
                            [1] => Jester
                        )

                )

        )

)

Dec 6 2010 • by Marc Ermshaus • type=post language=en php algorithmicadvent0 comments

Algorithmic Advent: 05 – Inserting gnuplot images

Here's an example on how to generate a gnuplot graph and integrate it as an image into an HTML document on the fly without using any additional output files.

$gnuplotCommands = <<<EOT
set term png
set output           # output to stdout

set   autoscale      # scale axes automatically
unset log            # remove any log-scaling
unset label          # remove any previous labels
set xtic auto        # set xtics automatically
set ytic auto        # set ytics automatically
set xr [0:7]
set yr [-1.5:1.5]
plot sin(x)
EOT;

$output    = array();
$errorCode = 0;

exec('echo "' . $gnuplotCommands . '" | gnuplot | base64', $output, $errorCode);

if ($errorCode !== 0) {
    echo 'Oops, something went wrong. Return code: ' . $errorCode;
    exit;
}

$imgData = implode("\n", $output);

echo '<img src="data:image/png;base64,' . $imgData . '">';

Dec 5 2010 • by Marc Ermshaus • type=post language=en php algorithmicadvent0 comments

Algorithmic Advent: 04 – Easy grouping with array_reduce and closures

The built-in array_reduce function (documented here) can be used to group data so that it will be easier to work with in a view script. The same functionality can be achieved with a foreach loop, but I think it's a good possibility to demonstrate a non-obvious application for one of PHP's lesser-used array functions. Apart from that, the usage of closures and generally a more function-centered approach to programming always feel nicer to me.

$a = array (
    array('date' => '2010-06-11', 'title' => 'The Shawshank Redemption'),
    array('date' => '2010-06-11', 'title' => 'The Godfather'),
    array('date' => '2011-07-02', 'title' => 'Il buono, il brutto, il cattivo'),
    array('date' => '2011-07-02', 'title' => 'Pulp Fiction'),
    array('date' => '2011-03-28', 'title' => 'Inception'),
    array('date' => '2011-02-15', 'title' => '12 Angry Men'),
);

// Sort by date
usort($a, function($v, $w) {
    return strcmp($w['date'], $v['date']);
});

// Group by year, month and day
$new = array_reduce($a, function($newArray, $tmp) {
    list($y, $m, $d) = sscanf($tmp['date'], '%d-%d-%d');
    $newArray[$y][$m][$d][] = $tmp['title'];
    return $newArray;
}, array());

print_r($new);

Dec 4 2010 • by Marc Ermshaus • type=post language=en php algorithmicadvent0 comments

Algorithmic Advent: 03 – Addressing a multi-dimensional array index by string

This function might come in handy when loading settings from a configuration file.

/**
 * Sets a field of a multi-dimensional array by specifying the key in a
 * flat string format
 *
 * Usage example:
 *
 *   $demo = array();
 *   arraySetByFlatKey($demo, 'this.is.a.test', 'Hello World!');
 *   arraySetByFlatKey($demo, 'this.is.a.monster', 'Brwaaa!');
 *
 * Sets:
 *
 *   $demo['this']['is']['a']['test'] = 'Hello World!';
 *   $demo['this']['is']['a']['monster'] = 'Brwaaa!';
 *
 * @param array $a Array to set the index of
 * @param string $key Flat key representation
 * @param mixed $value Value to set the index to
 */
function arraySetByFlatKey(array &$a, $key, $value, $delimiter = '.')
{
    $keyParts = explode($delimiter, $key);
    $leaveKey = array_pop($keyParts);

    $parent = &$a;

    foreach ($keyParts as $part) {
        if (!isset($parent[$part])) {
            $parent[$part] = array();
        }

        $parent = &$parent[$part];
    }

    $parent[$leaveKey] = $value;
}

Dec 3 2010 • by Marc Ermshaus • type=post language=en php algorithmicadvent0 comments

Algorithmic Advent: 02 – Longest intersection of two strings

Hint: If no support for varying character lengths is needed (e. g. when not working with UTF-8), the mb_* function calls should be replaced with their non-multibyte pendants. Especially mb_strpos is very inefficient when used in loops.

/**
 * Returns the longest intersection of two strings
 *
 * Usage example:
 *
 *   $a = 'This is a wonderful test string.';
 *   $b = 'This is another wonderful string.';
 *   var_dump(getLongestIntersection($a, $b)); // string(11) " wonderful "
 * 
 * @param string $haystack First string
 * @param string $needle Second string
 * @return string Longest intersection of both strings
 */
function getLongestIntersection($haystack, $needle)
{
    // Length of needle string
    $nLen      = mb_strlen($needle);

    // Starting point (in needle) of longest matching substring
    $maxAnchor = 0;

    // Length of (so far) longest matching substring
    $maxLen    = 0;

    // Starting point (in needle) of current substring to test
    $sAnchor   = 0;

    // Length of current substring to test
    $sLen      = 0;

    // Do checks until either substring anchor runs out of needle string or
    // until there is no room left for a longer match
    while ($sAnchor < $nLen
        && $sAnchor + $sLen <= $nLen
    ) {
        // Increase substring length
        $sLen++;

        if (mb_strpos($haystack,
                      mb_substr($needle, $sAnchor, $sLen)) !== false
        ) {
            /* Substring found. A new match is always the longest match */

            $maxLen    = $sLen;
            $maxAnchor = $sAnchor;
        } else {
            /* Substring not found */

            // Shift anchor one position to the right
            $sAnchor++;

            // Don't bother looking for substrings shorter than current best
            // match. This makes sure that a new match is always the longest
            // match up to this point
            $sLen--;
        }
    }

    return mb_substr($needle, $maxAnchor, $maxLen);
}

Dec 2 2010 • by Marc Ermshaus • language=en php type=link algorithmicadvent0 comments

Algorithmic Advent: 01 – Weighted picks

This is the first entry of a small advent calendar series featuring PHP snippets. Hopefully one or two of them will be useful to anyone.

Today, we start with a function to pick a random entry from a weighted array.

/**
 * Picks an element from a weighted array
 *
 * @param  array $map "Element to weight factor" mappings (<mixed> => <int>)
 * @return mixed|null Picked element or null if empty array
 */
function pick(array $map)
{
    $sum  = array_sum($map);
    $rand = mt_rand(0, $sum - 1);
    $k    = 0;
    $pick = null;

    foreach ($map as $element => $weight) {
        $k += $weight;
        if ($rand < $k) {
            $pick = $element;
            break;
        }
    }

    return $pick;
}

This example will return „c“ in 60 % of the time, „b“ in 30 % and „a“ in 10 %.

$weights = array(
     'a' => 10,
     'b' => 30,
     'c' => 60,
);

for ($i = 0; $i < 50; $i++) {
    echo pick($weights) . ", ";
}

This example simulates the rolling of two 6-sided dice (2d6).

$twoDice = array(
     '2' => 1,
     '3' => 2,
     '4' => 3,
     '5' => 4,
     '6' => 5,
     '7' => 6,
     '8' => 5,
     '9' => 4,
    '10' => 3,
    '11' => 2,
    '12' => 1
);

$results = array();

for ($i = 0; $i < 100; $i++) {
    $pick =  pick($twoDice);
    $results[$pick] .= '*';
}

ksort($results);

foreach ($results as $pick => $amount) {
    echo $pick . ': ' . $amount . '<br />';
}

Dec 1 2010 • by Marc Ermshaus • language=en php type=link algorithmicadvent0 comments