Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Что всеобщего у gamedev-а с космонавтикой?

Anna | 29.05.2014 | нет комментариев
Здравствуй, Прогр.Так сложилось, что незадолго мне в руки попала восхитительная книжка Pro PHP, в которой целый раздел посвящен итераторам. Да, я знаю что на Прогре эта тема теснее подымалась (и наверно не раз), но все же дозволю себе дописать данную статью, т.к. огромная часть примеров в вышуепомянутых статьях довольно оторваны от действительности. И так — если Вам увлекательно какую же рельную задачу мы собираемся решать с поддержкой итераторов — благо пожаловать под кат.

Что же это за зверь такой — итератор?

По сути, итератор — это определенный объект, тот, что разрешает упростить особенный обход дочерних элементов. В php существует интерфейс Iterator, реализуюя тот, что дозволено добиться нужного результата. В SPL (Standart PHP Library) так же включены несколько классов реализующих особенно распространенные и востребованные итераторы. Их список дозволено посмотреть тут.

Так для чего нам тогда итераторы — дозволено же легко массивы применять?

В php как-то исторически сложилось что перечисление объектов либо данных легко «складывают» в массив, элеметы которого потом дозволено перебирать. Представьте себе обстановку, в которй у Вас есть данные некоторого поля элементов, представленного квадратом, поделенным на 9 равных частей (скажем карту). И Вам нужно обойти все квадраты по часовой стрелке, а в массиве они сложены случайным образом. Не дюже комфортно, правда?

Так вот — именно в этом случае нам помогут итераторы. Взамен добавления элементов в массив, добавим их в итератор и потом сложем комфортно их перебирать. Пример кода, реализующего перебор соседних элементов карты дозволено обнаружить ниже:

/**
 * @link https://bitbucket.org/t1gor/strategy/src/242e58cdcd60c61d02ae26d420da9d415117cb0d/application/model/map/MapTileNeighboursIterator.php?at=default
 */
class TileIterator implements Iterator
{
    private $_side = 'north_west';
    private $_neighbours = array();
    private $_isValid = true;

    public function __construct($neighboursArray)
    {
        $this->_side = 'north_west';
        $this->_neighbours = $neighboursArray;
    }

    /**
     * @return void
     */
    function rewind() {
        $this->_side = 'north_west';
    }

    /**
     * @return MapTile
     */
    function current() {
        return $this->_neighbours[$this->_side];
    }

    /**
     * @return string
     */
    function key() {
        return $this->_side;
    }

    /**
     * Loop through neighbours clock-wise
     *
     * @return void
     */
    function next()
    {
        switch ($this->_side)
        {
            case 'north_west':
                $this->_side = 'north';
            break;

            case 'north':
                $this->_side = 'north_east';
            break;

            case 'north_east':
                $this->_side = 'east';
            break;

            case 'east':
                $this->_side = 'south_east';
            break;

            case 'south_east':
                $this->_side = 'south';
            break;

            case 'south':
                $this->_side = 'south_west';
            break;

            case 'south_west':
                $this->_side = 'west';
            break;

            // this is the end of a circle
            case 'west':
                $this->_isValid = false;
            break;
        }
    }

    function valid() {
        return $this->_isValid;
    }
} 

А сейчас собственно вызов:

// запрос не рассматриваем, т.к. это каждого лишь пример
$tilesStmt = PDO::prepare("SELECT * FROM tiles ... LIMIT 9");
$tilesStmt->execute();
$tiles = new TileIterator($tilesStmt->fetchAll());

Ну а дальше — знакомый каждому перебор, только теснее в положительном порядке:

foreach ($tiles as $tile) {
    ...
}

Да, подлинно не нехорошо. А что еще дозволено делать с итератором?

Так как тема довольно обширна, разгляжу только свои любимые примеры:

LimitIterator дюже комфортно применять при отладке либо тестировании кода. В частности, при работе сPHPExcel, в переборе строк, библиотека использует класс RowIterator, имя которого подразумевает что это Iterator. Дабы при разборе документа не «таскать» всякий раз все строки, дозволено обернуть RowIterator вLimitIterator и трудиться только с десятком строк:

// возьмем документ ...
$inputFileType = PHPExcel_IOFactory::identify('example.xlsx');
$objReader = PHPExcel_IOFactory::createReader($inputFileType);
$document = $objReader->load($inputFile);
$sheet = $document->getSheet(0);

// ... и получим только первые 10 строк
$dataForDebug = new LimitIterator($sheet->getRowIterator(), 0, 10);

Класс FilterIterator разрешает легко фильтровать данные на лету. В каком-то роде это схоже на WHERE часть SQL запроса. Представим, Вы трудитесь со сторонним API, скажем BaseCamp Classic API, SDK которого возвращает Вам объекты пользователей. И Вам необходимо уведомить некоторых из них по emial об изменениях в плане. А исключать Вам необходимо будет по 3-м парамертам: email, ID и имя. Сделать это легко и поддерживаемо разрешает вышуепомянутый класс:

/**
 * @link http://ua2.php.net/FilterIterator
 */
class NotificationFilter extends FilterIterator
{
    /**
     * Массив для хранения параметров фильтра
     */
    private $_skip;

    /**
     * Build filter
     *
     * @param Iterator $iterator
     * @param array    $filter  - массив данных о пользователях, которых нужно исключить
     * @throws InvalidArgumentException
     */
    public function __construct(Iterator $iterator, $filter)
    {
        if (!is_array($filter)) {
            throw new InvalidArgumentException("Filter should be an array. ".gettype($filter)." given.");
        }

        parent::__construct($iterator);
        $this->_skip = $filter;
    }

    /**
     * Check user data and make sure we can notify him/her
     *
     * Filtering by 2 params:
     *  - Does the user belong to your company (avoid spamming clients)?
     *  - Should we skipp the user based on the user ID
     *  - Should we skipp the user based on the user email
     *
     * @link http://php.net/manual/filteriterator.accept.php
     * @link https://github.com/sirprize/basecamp/blob/master/example/basecamp/person/get-by-id
     *
     * @return bool
     */
    public function accept()
    {
        // get current user from the Iterator
        $bcUser = $this->getInnerIterator()->current();

        // check if skipped by ID
        $skippedById = in_array($bcUser->getId(), $this->_skip['byID']);

        // or by email
        $skippedByEmail = in_array($bcUser->getEmailAddress(), $this->_skip['byEmail']);

        // check that he/she belongs to your company
        $belongsToCompany = $yourCompanyBaseCampID === (int) $bcUser->getCompanyId()->__toString();

        // notify only if belongs to your company and shouldn't be skipped
        return $belongsToCompany && !$skippedById && !$skippedByEmail;
    }
}

Таким образом в способе NotificationFilter::accept() мы трудимся только с одним пользователем.

А еще дозволено легко приводить многомерные массивы к одномерным с поддержкой RecursiveIteratorIterator, комфортно получать файловые листинги директорий с поддержкой RecursiveDirectoryIterator и еще дюже много каждого.

А причем здесь космонавтика?

Да, чуть не позабыл. Пока я «игрался» с итераторами, пытаясь для себя осознать как же их применять, у меня появилась дальнейшая идея — как бы мне на Прогре читать только посты, которые находятся и в хабеGameDev и в Веб-разработка? В ленте дозволено читать посты из обоих хабов, но не пересечение постов, если вы понимаете о чем я. В результате у меня получился маленький проектик с применением итераторов.

Каждый код плана дозволено обнаружить в репозитории на BitBucket, а тут же я опубликую только самую интереснюу часть. Код ниже:

/**
 * Basic post class
 */
class HabraPost {

    public $name = '';
    public $url = '';
    public $hubs = null;

    public static $baseUrl = 'http://habrahabr.ru/hub/';

    /**
     * Some hubs links
     */
    protected static $fullHubList = array(
        'infosecurity' => 'Информационная безопасность',
        'webdev' => 'Веб-разработка',
        'gdev' => 'Game Development',
        'DIY' => 'DIY либо Сделай сам',
        'pm' => 'Управление планами',
        'programming' => 'Программирование',
        'space' => 'Космонавтика',
        'hardware' => 'Сталь',
        'algorithms' => 'Алгорифмы',
        'image_processing' => 'Обработка изображений',
    );

    public function __construct($name, $url, $hubs = array())
    {
        $this->name = $name;
        $this->url = $url;
        $this->hubs = $hubs;
    }

    public static function getFullHubsList()
    {
        $list = self::$fullHubList;
        asort($list);
        return $list;
    }
}

/**
 * Post storage object
 *
 * @link http://php.net/manual/class.splobjectstorage.php
 */
class PostsStorage
{
    private $_iterator;

    public function __construct()
    {
        $this->_iterator = new SplObjectStorage();
    }

    /**
     * Add new post
     *
     * @param HabraPost $post
     * @return void
     */
    public function save(HabraPost $post)
    {
        // reduce duplicates
        if (!$this->_iterator->contains($post)) {
            $this->_iterator->attach($post);
        }
    }

    /**
     * Get internal iterator
     *
     * @return SplObjectStorage
     */
    public function getIterator()
    {
        return $this->_iterator;
    }
}

/**
 * Posts filtering class
 *
 * @link http://php.net/manual/class.filteriterator.php
 */
class HabraPostFilter extends FilterIterator
{
    /**
     * Hubs to filter by
     */
    private $_filterByHubs = array();

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

    /**
     * Accept
     *
     * @link   http://php.net/manual/filteriterator.accept.php
     * @return bool
     */
    public function accept()
    {
        $object = $this->getInnerIterator()->current();
        $aggregate = true;

        foreach ($this->_filterByHubs as $filterHub) {
            $aggregate = $aggregate && in_array($filterHub, $object->hubs);
        }

        return $aggregate;
    }
}

Выходит — идея дюже примитивна:

  1. Пользователь выбирает один либо несколько хабов,
  2. Мы перебираем доступные страницы Програ и собираем ссылки на контент,
  3. Загоняем все это в PostsStorage,
  4. И фильтруем с поддержкой HabraPostFilter

В результате получаем что-то сходственное скриншоту:

GameDev Веб разработка

Буду рад выложить план в вольный доступ если кто-нибудь вежливо предоставит хостинг, способный вынести Програ-результат.

Каждому спасибо за внимание.

P.S. С удовольствием приму правки/замечания в комментариях к посту либо в индивидуальной переписке.

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB