例如,我们有一个数组,包含了一系列员工的姓名、性别、年龄等信息:
$employees = [
['name' => 'Alice', 'gender' => 'female', 'age' => 35],
['name' => 'Bob', 'gender' => 'male', 'age' => 29],
['name' => 'David', 'gender' => 'male', 'age' => 40],
['name' => 'Benjamin', 'gender' => 'male', 'age' => 32]
];
我们需要从中获取所有男性员工的姓名,按照他们的年龄倒序排列。用原生 PHP 函数我们可以这样实现:
$arr = array_filter($employees, function ($value) {
return $value['gender'] == 'male';
});
usort($arr, function ($v1, $v2) {
return $v2['age'] - $v1['age'];
});
$names = array_map(function ($value) {
return $value['name'];
}, $arr);
// $names == ['David', 'Benjamin', 'Bob']
如果能像下面这样实现,代码的可读性会得到明显提升:
$names = Collection::init($employees)
->filter(function ($value) {
return $value['gender'] == 'male';
})
->sortedByDescending(function ($value) {
return $value['age'];
})
->map(function ($value) {
return $value['name'];
})
->toArray();
ext-collections 扩展库提供了这样的能力。它包含大量的用于操作 PHP 数组的方法,当我们需要操作的数据非常复杂时,使用它来代替 PHP 原生的数组函数,有助于提高开发效率,写出更优雅、更易维护的代码。
同时,ext-collections 使用 C 语言实现,性能上不弱于 PHP 提供的原生函数,一定程度上优于 Laravel Collections 等 PHP 实现的同类的库(性能优势有限,因为绝大多数的开销在回调上)。相比一些 C 实现的同类的库(如 viest/php-ext-collection)也提供了更丰富的功能。
这个项目是我在 2018 年初学 Kotlin 的时候,第一次见到 kotlin.collections 中的写法(那会儿还没学 Java,不知道 Stream API ),觉得很 cool 。正巧也在学 PHP,发现 PHP 没有原生支持这样的写法,就头脑一热造了个轮子,零零散散大概用了半年多时间完成。试着投稿 PECL 被拒,原因是 PHP 已经有了 DS 扩展,建议在它的基础上扩充,而不是重新搞一个类出来。
后来忙着实习和毕设,就没怎么打理这个项目。去年下旬 PHP 7.4 发布后,适配了一下 PHP 7.4 。最近几天又接入了一下 Codecov 。之后可能不会主动为这个项目添加更多的 feature 了,因为我还在搞好几个其他的开源项目。发到这里主要是希望有需要的朋友能够看到这个项目,尝试使用它并反馈 bug 。有兴趣的朋友还可以为该项目增添更多的 feature,从而让它活跃得更久。
1
sagaxu 2020-03-31 01:25:22 +08:00 via Android
lambda 如果不能 inline,开销是极大的
|
2
ericgui 2020-03-31 02:46:19 +08:00 via Android
你不如干脆搞个 libpdk,把 java 的 jdk 都搬过来
|
3
yuzo555 2020-03-31 05:10:35 +08:00
不过,原生用法里第一步可以用 array_multisort,第三步可以用 array_column,会简洁一些...
|
4
oneisall8955 2020-03-31 06:40:16 +08:00 via Android
和 JAVA 的 stream 操作很像
|
5
zachlhb 2020-03-31 08:15:29 +08:00 via Android
这是借鉴了 laravel 的集合么
|
6
php01 2020-03-31 08:57:26 +08:00
楼主,你举的这个例子,三楼是最佳实践。
你的这个例子,路子走歪了。 |
7
PHPJit 2020-03-31 09:50:49 +08:00 via iPhone
不错,支持一下
|
8
heybuddy 2020-03-31 10:45:29 +08:00
laravel 和 tp 都 tp 都有操作集合
|
9
askfilm 2020-03-31 10:50:59 +08:00
不错,支持一下 +1
|
10
fancy111 2020-03-31 11:10:09 +08:00
foreach ($employees as $key => $value) {
if ($value['gender']=='male') { $arr[$value['name']]=$value['age']; } } arsort($arr); 论可读性,原生直写多好。论性能,也比你省了一次 O(n)。 |
11
skymei 2020-03-31 11:15:11 +08:00
还是要感谢楼主的奉献,社区就是靠大家的奉献才能更加完善
|
13
Junjunya 2020-03-31 11:26:55 +08:00
为楼主赞一个,感觉这个可以当做一个很好的 php 怎么写扩展的教程
|
15
CismonX OP 统一回复一下
@yuzo555 @php01 是的,借助这两个函数,代码确实可以更简洁一些。但个人认为代码的可读性主要在于“表达力”,而不是“简洁”。曾经我也热衷于写各种 one-liner,鼓捣一些“黑魔法”,为了让自己的代码短一些。但事实上它们往往是不易读懂、难以维护的。一段表达力强的代码,应当尽可能顺应人类的思维,而不是迫使人用计算机中的概念去理解。 对于我在上面举的这个简单例子,当我们阅读“获取所有男性员工的姓名,按照他们的年龄倒序排列”这段话时,我们的大脑会提炼出这三个关键词,“男性”、“年龄倒序”、“获取姓名”,对应的就是 filter 、sort 、map 三个函数,非常直观,不需要 second thought 就知道这段代码在做什么。基于 array_multisort 之类的实现却欠缺这一直观性。 在实际开发中也能体会到,对“表达力”的强调也深深融入在 Stream API 这些库的设计理念中。 @fancy111 首先这种写法得到的结果不符合预期,还需要一个 array_keys($arr) 可读性这块,可以参考我前面的回答。确实简洁,但我觉得表达力是有所欠缺的。阅读这段代码的人需要先记住第三行“维护了一个姓名和年龄的映射”,然后看到第六行“按值逆序,保留键”,从而推理出来这段代码在做什么。这是对问题的进一步抽象,而非直观认识。这个例子或许很简单,但在更复杂的场景中,这样的代码被别人阅读时,或是被未来的自己阅读时,都会带来额外的理解成本。 性能这块,其实是这些库为了实现其理念所作出的一些牺牲。首先回调带来了额外开销,对于同样的算法,它的性能往往是要弱于简单的 for loop 的,但这些性能损耗在实践中,相比提升可读性、可维护性的优势,完全可以忽略不计。 |