如何在PHP中使用array_filter()进行函数式编程?

3

假设我有一个标签数组

$all_tags = array('A', 'B', 'C');

我想创建一组带有 $_GET 变量的 URL。我希望链接如下所示:
'A' 链接到 "index.php?x[]=B&x[]=C"
'B' 链接到 "index.php?x[]=A&x[]=C"
等等($_GET 是一个数组,除了“current”元素外,所有元素都包含在内)。 (我知道实现这个有更简单的方法:实际上我正在简化一个更加复杂的情况)
我想使用 array_filter() 来解决这个问题。这是我的尝试:
function make_get ($tag) { return 'x[]=' . $tag; }
function tag_to_url ($tag_name) {
   global $all_tags;

   $filta = create_function('$x', 'global $all_tags; return ($x != $tag_name);'); 
   return 'index.php?' . implode('&', array_map("make_get", array_filter($all_tags, "filta")));
}
print_r(array_map("", $all_tags));

但这并不能产生作用。我怀疑这可能与PHP中的映射和过滤器如何改变数据结构本身以及返回布尔值有关,而不是使用函数式编程风格,在那里它们不会改变数据结构并返回新列表。
我也对使此代码更加简洁的其他方法感兴趣。

那么......无论链接是什么,都从链接集合中删除该特定条目,然后使用剩余的值构建URL? - Brad Christie
也许可以像这样:http://www.ideone.com/vVnWe 这个链接吗? - Brad Christie
@BradChristie 评论#2:这不够“函数式”,但它完美地工作并且简洁明了。为什么不把它作为一个答案呢? - amindfv
我不确定你是朝着那个方向走还是我是否正确地理解了问题。 - Brad Christie
顺便提一句,在您的 create_function 中, $tag_name 未定义(这可能是问题所在)。我不认为 PHP 像 C# (或类似语言)那样将作用域传递给 create_function 。但我也可能错了。 - Brad Christie
4个回答

2

这里提供一种替代方案:

// The meat of the matter
function get_link($array, $tag) {
    $parts = array_reduce($array, function($result, $item) use($tag)
                          {
                              if($item != $tag) $result[] = 'x[]='.$tag;
                              return $result;
                          });
    return implode('&', $parts);
}

// Test driver

$all_tags = array('A', 'B', 'C');

echo get_link($all_tags, 'A');
echo "\n";
echo get_link($all_tags, 'B');
echo "\n";
echo get_link($all_tags, 'C');
echo "\n";

只需一次调用array_reduce,然后使用implode将结果组合成查询字符串。


+1 意味着没有副作用。尽管如此,我仍然渴望一个像 map ("x[]=" ++) (filter (/= tag_name) all_tags) 这样简短的答案 :) - amindfv

1

根据我在评论中给出的答案(此处显示):

<?php

  $all_tags = array('A', 'B', 'C');

  function tag_to_url($tag_name)
  {
    global $all_tags;

    $remaining_tags = array_diff($all_tags, array($tag_name));
    return sprintf('index.php?%s', 
             http_build_query(array('x'=>array_values($remaining_tags))));
  }

  echo tag_to_url('B'); // index.php?x%5B0%5D=A&x%5B1%5D=C
                        // basically: index.php?x[0]=A&x[1]=C

基本上,使用array_diff从数组中删除条目(而不是过滤),然后将其传递给http_build_query以生成有效的URL。


+1,非常好的方法。我真诚地希望你没有在其中使用全局变量。 :) - Jon
@Jon:根据OP提供的内容进行工作。;-) - Brad Christie

1

我只是要回答“为什么它不起作用”的部分。

$filta = create_function('$x', 'global $all_tags; return ($x != $tag_name);'); 

在您的lambda函数作用域中未定义tag_name变量。现在,在创建函数作用域(tag_to_url)中定义了它。您也不能在这里使用全局关键字,因为$tag_name不在全局作用域中,而是在本地tag_to_url作用域中。您可以在全局作用域中声明变量,然后它可以工作,但考虑到您喜欢功能方法,我怀疑您是否喜欢全局变量 :)

如果您愿意,您可以通过字符串连接和var_export($tag_name)来进行技巧处理,并将其传递给create_function(),但还有其他更好的方法来实现您的目标。

另外,顺便说一下,我猜您在开发时可能会受益于提高php的错误报告级别。php会向您抛出未定义变量通知,这有助于调试和理解。

// ideally set these in php.ini instead of in the script
error_reporting(E_ALL);
ini_set('display_errors', 1);

1

PHP中实现函数式编程风格的真正支持是非常新的,直到PHP 5.3才成为可能,函数成为一等公民并且匿名函数也变得可行。

顺便说一下,您永远不应该使用create_function()。它实际上是在全局命名空间中定义一个新函数(永远不会被垃圾回收!),并且在幕后使用eval()

如果您有PHP 5.3或更高版本,则可以执行此操作:

$all_tags = array('A', 'B', 'C');

function is_not_equal($a, $b) {
    return $a != $b;
}

function array_filter_tagname($alltags, $name) {
    $isNotEqualName = function($item) use ($name){
        return is_not_equal($item, $name);
    };
    // array_merge() is ONLY to rekey integer keys sequentially.
    // array_filter() preserves keys.
    return array_merge(array_filter($alltags, $isNotEqualName));
}

function make_url($arr) {
    return 'input.php?'.http_build_query(array('x'=>$arr));
}
$res = array_filter_tagname($all_tags, 'B');
print_r($res);
print_r(make_url($res));

如果您使用的是 PHP < 5.3 版本,建议使用类和对象来代替 create_function()

class NotEqualName {
    protected $otheritem;
    function __construct($otheritem) { // with PHP 4, use "function NotEqualName($otheritem) {"
        $this->otheritem = $otheritem;
    }
    function compare($item) {
        return $item != $this->otheritem;
    }
}

function array_filter_tagname_objectcallback($alltags, $name) {
    $isNotEqualName = new NotEqualName($name);
    return array_merge(array_filter($alltags, array($isNotEqualName,'compare')));
}

一般来说,PHP并不非常适合函数式编程风格,对于您的特定任务,使用array_filter()并不是PHP的惯用方式。array_diff()是更好的方法。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接