<?php
error_reporting(-1);

// reader
// reads n per call starting from last position til EOF
$offset = 0;
$reader = function ($n = 100) use (&$offset) {
    $limit = $offset + $n;
    $ln = 0;
    $content = array();

    $fp = fopen(__DIR__ . '/txt/data.txt', 'r');
    while (($line = fgets($fp)) && $ln < $limit) {
        $ln++;

        if($ln < $offset) {
            continue;
        }

        $content[$ln] = (int) trim($line);
    }
    fclose($fp);

    $offset+=$n;
    return $content;
};

// transformer
// calculates mediane from set block
$median = function ($block) {
    sort($block);
    $m = (int) floor(count($block) / 2);
   return $m % 2 ? $block[$m] : ($block[$m] + $block[$m-1]) / 2;
};

// transformer
// calculates average value from set block
$average = function($block) {
    return array_sum($block) / count($block);
};

// combiner
// adds transformer result to array
$combiner = function(array $data, $block) {
    $data[] = $block;
    return $data;
};

// reductor
// uses reader, transformer and combiner to reduce input data
function reductor($reader, $transformer, $combiner) {
    if (!is_callable($reader)) {
        throw new \InvalidArgumentException('Reader must be callable');
    }

    if (!is_callable($transformer)) {
        throw new \InvalidArgumentException('Transformer must be callable');
    }

    if (!is_callable($combiner)) {
        throw new \InvalidArgumentException('Combiner must be callable');
    }

    $data = array();

    while ($block = $reader()) {
        $block = $transformer($block);
        $data = $combiner($data, $block);
    }

    return $data;
}

var_dump(reductor($reader, $median, $combiner));