Мобильная
версия

Массивы и итераторы (часть 2)

Дата: Категория: PHP

В прошлой части мы поговорили вкратце об итераторах - о том как и зачем ими пользоваться.
В этой части я расскажу как он устроен изнутри, как написать свой итератор, и о разных типах и множестве возможностей этого чудесного инструмента.

Итератор это интерфейс от которого мы наследуемся - вот он.

interface Iterator extends Traversable {
    abstract public mixed current ( void )
    abstract public scalar key ( void )
    abstract public void next ( void )
    abstract public void rewind ( void )
    abstract public boolean valid ( void )
}

Разберемся поподробнее зачем нужна каждый из этих методов.

  • current - Получает текущий элемент
  • key - получает ключ элемента
  • next - переходит к следующему элементу
  • rewind - метод вызывающийся в самом начале разбора (например первый цикл foreach)
  • valid - проверяет элемент на валидность (чаще всего используется для определения конца перебора)

Вроде все просто, но в примере из прошлой статьи у нас было

foreach($array as $key => $item){}
И никаких вызовов этих функций.

Все просто они вызываются автоматически в таком порядке.

  1. В первую итерацию цикла вызывается rewind.
    Эта функция вызывается самой первой (например устанавливает текущий индекс массива на 0)
  2. После идет valid - проверка на валидность данных
  3. current - присваивает переменной $item текущее значение итератора 
    После этого мы можем работать с $item как с текущим элементов
  4. key - присваивает текущий ключ переменной $key
    После этого мы можем работать с $key - например узнать текущий ключ массива
  5. При последующих итерация все тоже самое, но функция rewind заменяется на next

Если не понятно, то сейчас я опишу все это дело непосредственно в коде

foreach($array as $key => $item){ 
    // Если $key == 0 (первая итерация) $array->rewind() 
    // Если $key == 0 (первая итерация) $array->next()
    // $array->valid()
    // $array->current()
    // теперь мы можем получить $item
    var_dump($item);
    // $array->key()
    // теперь мы можем получить $key
    var_dump($key);
} 

Именно в таком порядке они и вызываются.

Ну что, проясняется?
Теперь попробуем написать свой итератор. Итератор будет простой - массив с диапазоном чисел (аналог range())

Для начала создадим класс и определим все необходимые функции.

class Range implements Iterator {
    public function current(){}
    public function key(){}
    public function next(){}
    public function rewind(){}
    public function valid(){}
}

А теперь приступим к их определению.
По нашей задумке итератор будет представлять собой массив из чисел начиная с минимального и заканчивая максимальным.

Для определения этих чисел будем использовать конструктор.

class Range implements Iterator {
    private $min = 0;
    private $max = 0;
    public function __construct($min, $max){
        $this->min = (int)$min;     
        $this->max = (int)$max;
    }
    public function current(){}
    public function key(){}
    public function next(){}
    public function rewind(){}
    public function valid(){}
}

Теперь, нам нужно определиться с ключом - добавим переменную которая будет отвечать за значение ключа.

private $index = 0;
Именно ее мы будем изменять в соответствии с текущим элементом.

Как вы помните, первая функция которая вызывается - rewind. Определим ее

public function rewind(){
    $this->index = 0;
}
Обнуляем индекс, и все. Больше нам от нее ничего не нужно.
Идем дальше - следующая функция valid. С помощью нее проверяем не превысили ли мы максимальное число ($this->max)
public function valid(){  
    // Прибавляем индекс (текущий шаг) к минимальному числу, чтобы узнать текущее число.
    // Это число не должно быть больше максимального.
    return $this->index + $this->min <= $this->max;
}

Следующая по списку функция - current.
Как вы помните, она возвращает текущее значение при переборе (задает переменную $item)

public function current(){
    // Добавляем к минимальному значению номер итерации (текущий ключ)
    return $this->min + $this->index;
}

С функцией key все просто - она возвращает значение ключа

public function key(){
    return $this->index;
}
Все. Первая итерация закончилась, а при последующих, в самом начале, функция rewind меняется на next. Осталось определить только ее.
В ней мы будем увеличивать текущий ключ на один.
public function next(){
    $this->index ++;
}
А теперь, если мы все это дело совместим, то получим следующий, рабочий код.
class Range implements Iterator {
    private $min = 0;
    private $max = 0;
    private $index = 0;

    public function __construct($min, $max){
        $this->min = (int)$min;
        $this->max = (int)$max;
    }
    public function current(){
        return $this->min + $this->index;
    }
    public function key(){
        return $this->index;
    }
    public function next(){
        $this->index ++;
    }
    public function rewind(){
        $this->index = 0;
    }
    public function valid(){
        return $this->index + $this->min <= $this->max;
    }
}
$obj = new Range(5,14);
foreach($obj as $key => $item){
    echo $key . ' => ' . $item . "\n";
}
Как видите мы создали массив из 10 элементов (5-14 включительно)
Этот код выведет нам следующее 
0 => 5
1 => 6
2 => 7
3 => 8
4 => 9
5 => 10
6 => 11
7 => 12
8 => 13
9 => 14

И, как видите, не нарушился ни порядок элементов, ни порядок ключей.
Конечно, этот код просто образовательный, и достаточно бесполезный, хотя бы потому что в нем нет проверок и во многих случаях скрипт будет падать или просто не работать, но он наглядно показывает что и как происходит внутри итераторов.

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

Теги: #php, #ООП, # Оптимизация

Ваша оценка:

Рейтинг: 10.0 (Оценок: 1)

Комментарий:

Copyright © DOC_tr 2015-2017 г. Все права защищены
Яндекс.Метрика
Перейти к мобильной версии