mtdowling/transducers

0.3.0 2015-01-12 21:00 UTC

This package is not auto-updated.

Last update: 2024-09-14 16:43:58 UTC


README

转换器(Transducers)是可组合的算法转换。它们与输入和输出源的上下文无关,仅以单个元素的形式指定转换的本质。由于转换器与输入或输出源解耦,因此它们可以用于许多不同的过程 - 集合、流、通道、可观察对象等。转换器直接组合,无需了解输入或创建中间聚合。

有关Clojure转换器和转换器语义的更多信息,请参阅介绍性博客文章和此视频

您可以将任何在foreach循环中可迭代的对象进行转换(例如,数组、\Iterator、Traversable、Generator等)。转换器可以通过transduce()、into()、to_array()、to_assoc()、to_string()进行强求转换;以及通过to_iter()、xform()或应用转换器流过滤器进行懒转换。

composer.phar require mtdowling/transducers

使用转换器定义转换

转换器与普通函数组合。转换器在其包裹的转换器决定何时以及多少次调用之前执行其操作。您可以将转换器轻松组合以创建转换器管道。组合转换器的推荐方法是使用transducers\comp()函数

use Transducers as t;

$xf = t\comp(
    t\drop(2),
    t\map(function ($x) { return $x + 1; }),
    t\filter(function ($x) { return $x % 2; }),
    t\take(3)
);

上面的组合转换器是一个创建数据转换管道的函数:它跳过集合的前两个元素,将每个值加1,过滤出偶数,然后从集合中取3个元素。此新转换函数可以与各种转换器应用函数一起使用,包括xform()。

$data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
$result = t\xform($data, $xf);

// Contains: [5, 7, 9]

转换器

转换器是返回接受一个减少函数数组$xf并返回一个新的包装$xf的减少函数数组的函数。

以下是如何创建将$n添加到每个值的转换器

$inc = function ($n = 1) {
    // Return a function that accepts a reducing function array $xf.
    return function (array $xf) use ($n) {
        // Return a new reducing function array that wraps $xf.
        return [
            'init'   => $xf['init'],
            'result' => $xf['result'],
            'step'   => function ($result, $input) use ($xf, $n) {
                return $xf['step']($result, $input + $n);
            }
        ];
    }
};

$result = t\xform([1, 2, 3], $inc(1));
// Contains: 2, 3, 4

减少函数数组

减少函数数组是PHP关联数组,包含映射到函数的'init'、'step'和'result'键。

使用转换器

转换器可以以多种方式使用。此库提供了一些方法,可用于应用转换器。

transduce()

function transduce(callable $xf, array $step, $coll, $init = null)

通过应用$xf($step)['step']到每个值来转换和减少$coll。

  • callable $xf: 要应用的转换器函数。
  • array $step: 包含映射到可调用函数的'init'、'result'和'step'键的转换器数组。
  • $coll: 要转换的数据。可以是数组、迭代器或PHP流资源。
  • $init: 减少的可选第一个初始化值。如果未提供此值,则调用$step['init']()函数以提供默认值。
use Transducers as t;

$data = [[1, 2], [3, 4]];
$xf = t\comp(
    t\flatten(),
    t\filter(function ($value) { return $value % 2; }),
);
$result = t\transduce($xf, t\array_reducer(), $data);

// Contains: [1, 3]

当使用此函数时,您可以使用任何内置的减少函数数组作为$step参数

  • transducers\array_reducer(): 创建将值追加到数组的减少函数数组。

    $data = [[1, 2], [3, 4]];
    $result = t\transduce(t\flatten(), t\array_reducer(), $data);
    
    // Results contains [1, 2, 3, 4]
  • transducers\stream_reducer(): 创建将值写入流资源的减少函数数组。如果没有提供transduce时的$init值,则将使用PHP临时流。

    $data = [[1, 2], [3, 4]];
    $result = t\transduce(t\flatten(), t\stream_reducer(), $data);
    fseek($result, 0);
    echo stream_get_contents($result);
    // Outputs: 1234
  • transducers\string_reducer(): 创建将每个值连接到字符串的减少函数数组。

    $xf = t\flatten();
    // use an optional joiner on the string reducer.
    $reducer = t\string_reducer('|');
    $data = [[1, 2], [3, 4]];
    $result = t\transduce($xf, $reducer, $data);
    
    // Result is '1|2|3|4'
  • transducers\assoc_reducer():创建一个将键值对添加到关联数组的减少函数数组。每个值必须是一个数组,其中包含数组的键作为第一个元素,数组的值作为第二个元素。

  • transducers\create_reducer():一个便捷函数,可以快速创建减少函数数组。第一个且仅有的必需参数是一个步骤函数,它接受累积结果和新值并返回一个单一的结果。下一个可选参数是初始化函数,它不接受任何参数并返回一个初始化后的结果。下一个可选参数是结果函数,它接受一个结果参数并预期返回一个最终结果。

    $result = t\transduce(
        t\flatten(),
        t\create_reducer(function ($r, $x) { return $r + $x; }),
        [[1, 2], [3, 4]]
    );
    
    // Result is equal to 10
  • transducers\operator_reducer():创建一个减少函数数组,使用提供的后缀运算符来减少集合(即 $result <运算符> $input)。支持:'.'、'+'、'-'、'*' 和 '/' 运算符。

    $result = t\transduce(
        t\flatten()
        t\operator_reducer('+'),
        [[1, 2], [[3], 4]]
    );
    
    // Result is equal to 10

xform()

function xform($coll, callable $xf)

返回与 $coll 相同的数据类型,并应用 $xf

xform() 在返回值时使用以下逻辑

  • array:使用提供的数组返回一个数组。
  • 关联数组:将提供的数组转换为索引数组,这意味着传递给 step 减少函数的每个值都是一个数组,其中第一个元素是键,第二个元素是值。完成后,xform() 返回一个关联数组。
  • \Iterator:返回一个迭代器,其中 $xf 以懒加载方式应用。
  • resource:从提供的值读取单个字节,并返回一个包含应用 $xf 后输入资源中的字节的新的 fopen 资源。
  • string:将字符串中的每个字符传递到每个步骤函数,并返回一个字符串。
// Give an array and get back an array
$result = t\xform([1, false, 3], t\compact());
assert($result === [1, 3]);

// Give an iterator and get back an iterator
$result = t\xform(new ArrayIterator([1, false, 3]), t\compact());
assert($result instanceof \Iterator);

// Give a stream and get back a stream.
$stream = fopen('php://temp', 'w+');
fwrite($stream, '012304');
rewind($stream);
$result = t\xform($stream, t\compact());
assert($result == '1234');

// Give a string and get back a string
$result = t\xform('abc', t\map(function ($v) { return strtoupper($v); }));
assert($result === 'abc');

// Give an associative array and get back an associative array.
$data = ['a' => 1, 'b' => 2];
$result = t\xform('abc', t\map(function ($v) {
    return [strtoupper($v[0]), $v[1]];
}));
assert($result === ['A' => 1, 'B' => 2]);

into()

function into($target, $coll, callable $xf)

$coll 中的项转换为给定的 $target,实质上是从一个数据源“倒”转换后的数据到另一个数据类型。

此函数不试图区分数组和关联数组。提供的任何数组或 ArrayAccess 对象都将被视为索引数组。当提供一个字符串时,每个值将无分隔符地连接到字符串的末尾。当提供一个 fopen 资源时,数据将无分隔符地写入流的末尾。

use Transducers as t;

// Compose a transducer function.
$transducer = t\comp(
    // Remove a single level of nesting.
    'transducers\cat',
    // Filter out even values.
    t\filter(function ($value) { return $value % 2; }),
    // Multiply each value by 2
    t\map(function ($value) { return $value * 2; }),
    // Immediately stop when the value is >= 15.
    t\take_while(function($value) { return $value < 15; })
);

$data = [[1, 2, 3], [4, 5], [6], [], [7], [8, 9, 10, 11]];

// Eagerly pour the transformed data, [2, 6, 10, 14], into an array.
$result = t\into([], $data, $transducer);

to_iter()

function to_iter($coll, callable $xf)

创建一个迭代器,该迭代器 懒加载地 将转换器 $xf 应用到 $input 迭代器。在处理大量数据或只想按需执行操作时使用此函数。

// Generator that yields incrementing numbers.
$forever = function () {
    $i = 0;
    while (true) {
        yield $i++;
    }
};

// Create a transducer that multiplies each value by two and takes
// ony 100 values.
$xf = t\comp(
    t\map(function ($value) { return $value * 2; }),
    t\take(100)
);

foreach (t\to_iter($forever(), $xf) as $value) {
    echo $value;
}

to_array()

function to_array($iterable, callable $xf)

将值转换为数组并应用转换器函数。$iterable 通过 to_traversable() 传递,以便将输入值转换为数组。

.. code-block:: php

$result = t\to_array(
    'abc',
    t\map(function ($v) { return strtoupper($v); })
);

// Contains: ['A', 'B', 'C']

to_assoc()

function to_assoc($iterable, callable $xf)

使用提供的输入创建一个关联数组,同时将 $xf 应用到每个值。值被转换为包含数组键作为第一个元素和数组值作为第二个元素的数组。

$result = t\to_assoc(
    ['a' => 1, 'b' => 2],
    t\map(function ($v) { return [$v[0], $v[1] + 1]; })
);

assert($result == ['a' => 2, 'b' => 3]);

to_string()

function to_string($iterable, callable $xf)

将值转换为字符串并将转换器函数应用到每个字符。$iterable 通过 to_traversable() 传递,以便将输入值转换为数组。

echo t\to_string(
    ['a', 'b', 'c'],
    t\map(function ($v) { return strtoupper($v); })
);

// Outputs: ABC

to_fn()

function to_fn(callable $xf, callable|array $builder = null)

将转换器转换为可以与现有减少实现(例如,array_reduce)一起使用的函数。

$xf = t\map(function ($x) { return $x + 1; });
$fn = t\to_fn($xf); // $builder is optional
$result = array_reduce([1, 2, 3], $fn);
assert($result == [2, 3, 4]);

$fn = t\to_fn($xf, t\string_reducer());
$result = array_reduce([1, 2, 3], $fn);
assert($result == '234');

流过滤器

您可以使用 流过滤器 将转换器应用于 PHP 流。此库注册了一个 transducers 流过滤器,可以使用 transducers\append_stream_filter()transducers\prepend_stream_filter() 函数将其附加或附加到 PHP 流。

use transducers as t;

$f = fopen('php://temp', 'w+');
fwrite($f, 'testing. Can you hear me?');
rewind($f);

$xf = t\comp(
    // Split by words
    t\words(),
    // Uppercase/lowercase every other word.
    t\keep_indexed(function ($i, $v) {
        return $i % 2 ? strtoupper($v) : strtolower($v);
    }),
    // Combine words back together into a string separated by ' '.
    t\interpose(' ')
);

// Apply a transducer stream filter.
$filter = t\append_stream_filter($f, $xf, STREAM_FILTER_READ);
echo stream_get_contents($f);
// Be sure to remove the filter to flush out any buffers.
stream_filter_remove($filter);
echo stream_get_contents($f);

fclose($f);

// Echoes: "testing. CAN you HEAR me?"

可用的转换器

map()

function map(callable $f)

将映射函数 $f 应用到集合中的每个值。

$data = ['a', 'b', 'c'];
$xf = t\map(function ($value) { return strtoupper($value); });
assert(t\xform($data, $xf) == ['A', 'B', 'C']);

filter()

函数 filter(callable $pred)

过滤不满足谓词函数 $pred 的值。

$data = [1, 2, 3, 4];
$odd = function ($value) { return $value % 2; };
$result = t\xform($data, t\filter($odd));
assert($result == [1, 3]);

remove()

函数 remove(callable $pred)

从序列中删除满足 $pred 的任何内容。

$data = [1, 2, 3, 4];
$odd = function ($value) { return $value % 2; };
$result = t\xform($data, t\remove($odd));
assert($result == [2, 4]);

cat()

函数 cat()

Transducer 将嵌套列表中的项连接起来。请注意,与其它 transducer 不同,使用 cat 时使用的是函数名称的字符串值(例如,'transducers\cat');

$xf = 'transducers\cat';
$data = [[1, 2], [3], [], [4, 5]];
$result = t\xform($data, $xf);
assert($result == [1, 2, 3, 4, 5]);

mapcat()

函数 mapcat(callable $f)

将映射函数应用于集合,并将它们连接到更少的一层嵌套中。

$data = [[1, 2], [3], [], [4, 5]];
$xf = t\mapcat(function ($value) { return array_sum($value); });
$result = t\xform($data, $xf);
assert($result == [3, 3, 0, 9]);

flatten()

函数 flatten()

将任何嵌套的序列组合转换为单个平面序列。

$data = [[1, 2], 3, [4, new ArrayObject([5, 6])]];
$xf = t\flatten();
$result = t\to_array($data, $xf);
assert($result == [1, 2, 3, 4, 5, 6]);

partition()

函数 partition($size)

将源数据分割成大小为 $size 的数组。当减少函数数组完成后,数组将使用任何剩余的项目进行递增。

$data = [1, 2, 3, 4, 5];
$result = t\xform($data, t\partition(2));
assert($result == [[1, 2], [3, 4], [5]]);

partition_by()

函数 partition_by(callable $pred)

根据输入值是否通过传入的谓词函数(true/false)与当前列表保持不同条件来分割输入列表。

$data = [['a', 1], ['a', 2], [2, 3], ['c', 4]];
$xf = t\partition_by(function ($v) { return is_string($v[0]); });
$result = t\into([], $data, $xf);

assert($result == [
    [['a', 1], ['a', 2]],
    [[2, 3]],
    [['c', 4]]
]);

take()

函数 take($n);

从集合中取出 $n 个值。

$data = [1, 2, 3, 4, 5];
$result = t\xform($data, t\take(2));
assert($result == [1, 2]);

take_while()

函数 take_while(callable $pred)

只要谓词函数 $pred 返回 true,就从集合中取出。

$data = [1, 2, 3, 4, 5];
$xf = t\take_while(function ($value) { return $value < 4; });
$result = t\xform($data, $xf);
assert($result == [1, 2, 3]);

take_nth()

函数 take_nth($nth)

从值序列中取出每第 n 个项。

$data = [1, 2, 3, 4, 5, 6];
$result = t\xform($data, t\take_nth(2));
assert($result == [1, 3, 5]);

drop()

函数 drop($n)

从输入序列的开始处删除 $n 个项。

$data = [1, 2, 3, 4, 5];
$result = t\xform($data, t\drop(2));
assert($result == [3, 4, 5]);

drop_while()

函数 drop_while(callable $pred)

只要谓词函数 $pred 返回 true,就从序列中删除值。

$data = [1, 2, 3, 4, 5];
$xf = t\drop_while(function ($value) { return $value < 3; });
$result = t\xform($data, $xf);
assert($result == [3, 4, 5]);

replace()

函数 replace(array $smap)

给定一个替换对的映射和集合,返回一个序列,其中任何等于 $smap 键的元素都被相应的 $smap 值替换。

$data = ['hi', 'there', 'guy', '!'];
$xf = t\replace(['hi' => 'You', '!' => '?']);
$result = t\xform($data, $xf);
assert($result == ['You', 'there', 'guy', '?']);

keep()

函数 keep(callable $f)

保留 $f 不返回 null 的项。

$result = t\xform(
    [0, false, null, true],
    t\keep(function ($value) { return $value; })
);

assert($result == [0, false, true]);

keep_indexed()

函数 keep_indexed(callable $f)

返回非空结果的序列 $f($index, $input)

$result = t\xform(
    [0, false, null, true],
    t\keep_indexed(function ($index, $input) {
        echo $index . ':' . json_encode($input) . ', ';
        return $input;
    })
);

assert($result == [0, false, true]);

// Will echo: 0:0, 1:false, 2:null, 3:true,

dedupe()

函数 dedupe()

删除有序重复项(保留重复值序列中的第一个)。

$result = t\xform(
    ['a', 'b', 'b', 'c', 'c', 'c', 'b'],
    t\dedupe()
);

assert($result == ['a', 'b', 'c', 'b']);

interpose()

函数 interpose($separator)

在序列中的每个项之间添加分隔符。

$result = t\xform(['a', 'b', 'c'], t\interpose('-'));
assert($result == ['a', '-', 'b', '-', 'c']);

tap()

函数 tap(callable $interceptor)

对每个结果和项调用拦截器,然后继续不变。

此方法的主要目的是“连接”方法链,以便在链内对中间结果执行操作。使用当前结果和项执行拦截器。

// echo each value as it passes through the tap function.
$tap = t\tap(function ($r, $x) { echo $x . ', '; });

t\xform(
    ['a', 'b', 'c'],
    t\comp(
        $tap,
        t\map(function ($v) { return strtoupper($v); }),
        $tap
    )
);

// Prints: a, A, b, B, c, C,

compact()

函数 compact()

删除所有假值。

$result = t\xform(['a', true, false, 'b', 0], t\compact());
assert($result == ['a', true, 'b']);

words()

函数 words($maxBuffer = 4096)

按单词拆分输入。您可以提供一个可选的最大缓冲区长度,以确保用于查找单词的缓冲区大小永远不会超过该值。默认最大缓冲区长度为 4096。要使用无界缓冲区,提供 INF

$xf = t\words();
$data = ['Hi. This is a test.'];
$result = t\xform($data, $xf);
assert($result == ['Hi.', 'This', 'is', 'a', 'test.']);

$data = ['Hi. ', 'This is',  ' a test.'];
$result = t\xform($data, $xf);
assert($result == ['Hi.', 'This', 'is', 'a', 'test.']);

lines()

函数 lines($maxBuffer = 10240000)

按行拆分输入。您可以提供一个可选的最大缓冲区长度,以确保用于查找行的缓冲区大小永远不会超过该值。默认最大缓冲区长度为 10MB。要使用无界缓冲区,提供 INF

$xf = t\lines();
$data = ["Hi.\nThis is a test."];
$result = t\xform($data, $xf);
assert($result == ['Hi.', 'This is a test.']);

$data = ["Hi.\n", 'This is',  ' a test.', "\nHear me?"];
$result = t\xform($data, $xf);
assert($result == ['Hi.', 'This is a test.', 'Hear me?']);

实用函数

identity()

函数 indentity($value)

返回提供的值。这在编写不需要修改'init'或'result'函数的reducing函数数组时非常有用。在这种情况下,您只需将字符串'transducers\identity'用作'init'或'result'函数,即可继续代理到其他reducers。

assoc_iter()

函数 assoc_iter($iterable)

将可迭代对象转换为索引数组迭代器,其中每个yield的值都是一个包含键后跟值的数组。

$data = ['a' => 1, 'b' => 2];
assert(t\assoc_iter($data) == [['a', 1], ['b', 2]];

这可以与assoc_reducer()结合使用,以生成关联数组。

$result = t\transduce(
    t\map(function ($v) { return [$v[0], $v[1] + 1]; }),
    t\assoc(),
    t\assoc_iter(['a' => 1, 'b' => 2])
);

assert($result == ['a' => 2, 'b' => 3]);

如果您知道您正在减少关联数组,则应仅使用t\to_assoc()函数。

$result = t\to_assoc(
    ['a' => 1, 'b' => 2],
    t\map(function ($v) { return [$v[0], $v[1] + 1]; })
);

assert($result == ['a' => 2, 'b' => 3]);

stream_iter()

函数 stream_iter($stream, $size = 1)

创建一个迭代器,使用给定的$size参数从流中读取。

$s = fopen('php://temp', 'w+');
fwrite($s, 'foo');
rewind($s);

// outputs: foo
foreach (t\stream_iter($s) as $char) {
    echo $char;
}

rewind($s);

// outputs: fo-o
foreach (t\stream_iter($s, 2) as $char) {
    echo $char . '-';
}

to_traversable()

函数 to_traversable($value)

将输入值转换为可遍历的对象(例如,数组或\Iterator)。此函数接受数组、\Traversable、PHP流和字符串。数组保持不变。关联数组返回为迭代器,其中每个值是一个数组,包含数组的键作为第一个元素和数组的值作为第二个元素。迭代器按原样返回。字符串通过字符分割使用str_split()。PHP流转换为迭代器,每次yield一个字节。

is_traversable()

函数 is_traversable($coll)

如果提供的$coll是可以在foreach循环中遍历的对象,则返回true。此函数将数组、\Traversable的实例和stdClass视为可迭代。

reduce()

函数 reduce(callable $fn, $coll, $accum = null)

使用提供的reduce函数$fn减少给定的可迭代对象。如果$fn返回Reduced的实例,则中断减少。