【问题标题】:PHP's array_map including keysPHP 的 array_map 包括键
【发布时间】:2012-10-13 17:20:24
【问题描述】:

有没有办法做这样的事情:

$test_array = array("first_key" => "first_value", 
                    "second_key" => "second_value");

var_dump(array_map(function($a, $b) { return "$a loves $b"; }, 
         array_keys($test_array), 
         array_values($test_array)));

但不是调用array_keys和array_values,而是直接传递$test_array变量?

想要的输出是:

array(2) {
  [0]=>
  string(27) "first_key loves first_value"
  [1]=>
  string(29) "second_key loves second_value"
}

【问题讨论】:

标签: php functional-programming


【解决方案1】:

不适用于array_map,因为它不处理键。

array_walk 会:

$test_array = array("first_key" => "first_value",
                    "second_key" => "second_value");
array_walk($test_array, function(&$a, $b) { $a = "$b loves $a"; });
var_dump($test_array);

// array(2) {
//   ["first_key"]=>
//   string(27) "first_key loves first_value"
//   ["second_key"]=>
//   string(29) "second_key loves second_value"
// }

它确实改变了作为参数给出的数组,所以它不完全是函数式编程(因为你有这样标记的问题)。此外,正如评论中所指出的,这只会更改数组的值,因此键不会是您在问题中指定的键。

如果你愿意,你可以编写一个函数来修复上面的点,如下所示:

function mymapper($arrayparam, $valuecallback) {
  $resultarr = array();
  foreach ($arrayparam as $key => $value) {
    $resultarr[] = $valuecallback($key, $value);
  }
  return $resultarr;
}

$test_array = array("first_key" => "first_value",
                    "second_key" => "second_value");
$new_array = mymapper($test_array, function($a, $b) { return "$a loves $b"; });
var_dump($new_array);

// array(2) {
//   [0]=>
//   string(27) "first_key loves first_value"
//   [1]=>
//   string(29) "second_key loves second_value"
// }

【讨论】:

  • 除非在这种情况下,您希望 $a = "$b loves $a" 匹配 OP 所需的输出。
  • 正确,已更改 :) 很高兴他们使 array_map 与 array_walk 不同。
  • 很好,谢谢。为了避免弄乱原始数组,这就是我最终所做的(在下面查看我的答案)
  • 这不是“函数式编程”,因为array_walk() 不返回结果数组,而是返回一个布尔值。
  • @mae 是的,就像我在答案中所写的那样 - 它不是返回值而是更改参数
【解决方案2】:

这可能是最短且最容易推理的:

$states = array('az' => 'Arizona', 'al' => 'Alabama');

array_map(function ($short, $long) {
    return array(
        'short' => $short,
        'long'  => $long
    );
}, array_keys($states), $states);

// produces:
array(
     array('short' => 'az', 'long' => 'Arizona'), 
     array('short' => 'al', 'long' => 'Alabama')
)

【讨论】:

  • 我才意识到问题中明确表示不要使用array_keys()。不过,这似乎是一个愚蠢的要求。
  • 该问题提供了一个使用 array_keys() 的解决方案,提供一个与当前解决方案相比没有优势(例如,调用更少的函数)的答案将是愚蠢的。
  • 原问题的答案是否定的,这是最合适的解决方案。
【解决方案3】:

这是我非常简单、兼容 PHP 5.5 的解决方案:

function array_map_assoc(callable $f, array $a) {
    return array_column(array_map($f, array_keys($a), $a), 1, 0);
}

您提供的可调用对象本身应该返回一个包含两个值的数组,即return [key, value]。因此,对array_map 的内部调用会产生一个数组数组。然后通过array_column 将其转换回一维数组。

用法

$ordinals = [
    'first' => '1st',
    'second' => '2nd',
    'third' => '3rd',
];

$func = function ($k, $v) {
    return ['new ' . $k, 'new ' . $v];
};

var_dump(array_map_assoc($func, $ordinals));

输出

array(3) {
  ["new first"]=>
  string(7) "new 1st"
  ["new second"]=>
  string(7) "new 2nd"
  ["new third"]=>
  string(7) "new 3rd"
}

部分应用

如果你需要多次使用不同数组但映射函数相同的函数,你可以做一个叫做partial function application(与'currying'相关)的东西,它只允许你传入数据数组调用时:

function array_map_assoc_partial(callable $f) {
    return function (array $a) use ($f) {
        return array_column(array_map($f, array_keys($a), $a), 1, 0);
    };
}

...
$my_mapping = array_map_assoc_partial($func);
var_dump($my_mapping($ordinals));

鉴于$func 和$ordinals 与之前相同,这会产生相同的输出。

注意:如果您的映射函数为两个不同的输入返回 相同的键,则与 后一个键关联的值将获胜。 反转输入数组和输出结果array_map_assoc 允许较早的密钥获胜。 (在我的示例中,返回的键不能冲突,因为它们包含源数组的键,而源数组的键又必须是唯一的。)


另类

以下是上述的变体,对某些人来说可能更合乎逻辑,但需要 PHP 5.6:

function array_map_assoc(callable $f, array $a) {
    return array_merge(...array_map($f, array_keys($a), $a));
}

在此变体中,您提供的函数(数据数组在其上映射)应该返回一个具有一行的关联数组,即return [key => value]。 映射可调用的结果然后简单地解包并传递给array_merge。如前所述,返回重复键将导致后面的值获胜。

n.b. Alex83690 在评论中指出,在此处使用 array_replace 代替 array_merge 将保留整数键。 array_replace 不会修改输入数组,因此对于函数式代码是安全的。

如果您使用的是 PHP 5.3 到 5.5,则以下内容是等效的。它使用array_reduce 和二进制+ 数组运算符将生成的二维数组转换为一维数组,同时保留键:

function array_map_assoc(callable $f, array $a) {
    return array_reduce(array_map($f, array_keys($a), $a), function (array $acc, array $a) {
        return $acc + $a;
    }, []);
}

用法

这两种变体都可以这样使用:

$ordinals = [
    'first' => '1st',
    'second' => '2nd',
    'third' => '3rd',
];

$func = function ($k, $v) {
    return ['new ' . $k => 'new ' . $v];
};

var_dump(array_map_assoc($func, $ordinals));

注意=> 中的,,而不是$func。

输出和之前一样,每一个都可以和之前一样部分应用。


 摘要

最初问题的目标是使调用的调用尽可能简单,代价是调用更复杂的函数;特别是,能够将数据数组作为单个参数传入,而无需拆分键和值。使用此答案开头提供的函数:

$test_array = ["first_key" => "first_value",
               "second_key" => "second_value"];

$array_map_assoc = function (callable $f, array $a) {
    return array_column(array_map($f, array_keys($a), $a), 1, 0);
};

$f = function ($key, $value) {
    return [$key, $key . ' loves ' . $value];
};

var_dump(array_values($array_map_assoc($f, $test_array)));

或者,仅针对这个问题,我们可以简化为删除输出键的array_map_assoc() 函数,因为问题不要求它们:

$test_array = ["first_key" => "first_value",
               "second_key" => "second_value"];

$array_map_assoc = function (callable $f, array $a) {
    return array_map($f, array_keys($a), $a);
};

$f = function ($key, $value) {
    return $key . ' loves ' . $value;
};

var_dump($array_map_assoc($f, $test_array));

所以答案是NO,你不能避免调用array_keys,但是你可以把array_keys被调用的地方抽象出来放到一个高阶函数中,这样可能会很好够了。

【讨论】:

  • array_column 仅适用于 PHP 5.5+,因此此答案与 PHP 5.3 不兼容
  • 我就是那个人。 PHP 5.3 不再是 this 答案日期的要求。恕我直言。
  • 您的第一个替代解决方案无效。您必须将 array_merge 替换为 array_replace 以保留整数键。
  • @Alex83690 谢谢!虽然我会说“无效”有点误导——如果你没有任何整数键也没关系(就像我自己的情况一样)。
  • 如果有人对保留键的数组映射函数感兴趣,同时它的回调可以访问键和值,并且可以只返回一个值(就像普通映射函数一样)而不是“特殊格式的”值(一行关联数组),那么这里是稍微修改的 php 5.6 解决方案:function array_map_assoc(callable $f, array $a) { return array_merge(...array_map(function ($k, $v) use ($f) { return [ $k => $f($k, $v) ]; }, array_keys($a), $a)); }。为此,$f 必须用另一个函数包装,该函数将新值映射到 [key=>new_value]。
【解决方案4】:
$array = [
  'category1' => 'first category',
  'category2' => 'second category',
];
 
$new = array_map(function($key, $value) {
  return "{$key} => {$value}";
}, array_keys($array), $array);

【讨论】:

  • 这个答案值得更多的认可。它从字面上回答了 OP 想要什么。
  • 这对我有用。而且非常简单。
【解决方案5】:

使用 PHP5.3 或更高版本:

$test_array = array("first_key" => "first_value", 
                    "second_key" => "second_value");

var_dump(
    array_map(
        function($key) use ($test_array) { return "$key loves ${test_array[$key]}"; },
        array_keys($test_array)
    )
);

【讨论】:

  • 我认为要求是“而不是调用array_keys和array_values,直接传递$test_array变量”,这样可以不用array_keys吗?
【解决方案6】:

看这里!有一个简单的解决方案!

function array_map2(callable $f, array $a)
{
    return array_map($f, array_keys($a), $a);
}

如问题所述,array_map已经完全具备所需的功能。这里的其他答案严重地使事情变得过于复杂:array_walk 不起作用。

用法

正如您对示例所期望的那样:

$test_array = array("first_key" => "first_value", 
                    "second_key" => "second_value");

var_dump(array_map2(function($a, $b) { return "$a loves $b"; }, $test_array));

【讨论】:

  • 其他答案过于复杂,因为指定的问题 qrrqy_keys() 不应该用于#reasons
【解决方案7】:

我将使用 5.6 或更高版本为该问题添加另一个解决方案。不知道它是否比已经很棒的解决方案更有效(可能不是),但对我来说它更容易阅读:

$myArray = [
    "key0" => 0,
    "key1" => 1,
    "key2" => 2
];

array_combine(
    array_keys($myArray),
    array_map(
        function ($intVal) {
            return strval($intVal);
        },
        $myArray
    )
);

使用strval()作为array_map中的示例函数,这将生成:

array(3) {
  ["key0"]=>
  string(1) "0"
  ["key1"]=>
  string(1) "1"
  ["key2"]=>
  string(1) "2"
}

希望我不是唯一一个觉得这很容易掌握的人。 array_combine 从一个键数组和一个值数组创建一个 key => value 数组,其余的很容易解释。

【讨论】:

  • 谢谢!我正在寻找 array_combine() 与 array_map() 结合以生成新的键 => 值对:array_combine(array_values($arr), array_map(..., $arr))
【解决方案8】:

“手动循环”是指编写一个使用foreach 的自定义函数。这会像 array_map 一样返回一个新数组,因为函数的作用域导致 $array 是一个副本,而不是一个引用:

function map($array, callable $fn) {
  foreach ($array as $k => &$v) $v = call_user_func($fn, $k, $v);
  return $array;
}

虽然您使用array_map 和array_keys 的技术实际上看起来更简单且更强大,因为您可以使用null 作为回调来返回键值对:

function map($array, callable $fn = null) {
  return array_map($fn, array_keys($array), $array);
}

【讨论】:

  • 循环数组引用,会导致spooky things to happen
  • 这并不可怕,它只是意味着你忘记了unset( $value ),因为它仍然存在于定义的范围内。
  • @azis,指的是这篇文章,在开玩笑。如果您忘记取消设置,它会产生意想不到的效果。
  • 感谢您的回答,但我认为很明显我不想使用传统循环。
  • @janenz00 请参阅编辑后的答案以进行澄清。我的意思是在一个干净的变量范围内循环。
【解决方案9】:

这就是我在我的项目中实现它的方式。

function array_map_associative(callable $callback, $array) {
    /* map original array keys, and call $callable with $key and value of $key from original array. */
    return array_map(function($key) use ($callback, $array){
        return $callback($key, $array[$key]);
    }, array_keys($array));
}

【讨论】:

  • 非常干净,不会改变原始数组!
【解决方案10】:

您可以使用此array library 中的map 方法来轻松实现您想要的:

Arr::map($test_array, function($a, $b) { return "$a loves $b"; });

它还保留键并返回新数组,更不用说满足您需求的几种不同模式了。

【讨论】:

    【解决方案11】:

    如果您只需要一次,closure 就可以使用。我会使用generator。

    $test_array = [
        "first_key" => "first_value", 
        "second_key" => "second_value",
    ];
    
    $x_result = (function(array $arr) {
        foreach ($arr as $key => $value) {
            yield "$key loves $value";
        }
    })($test_array);
    
    var_dump(iterator_to_array($x_result));
    
    // array(2) {
    //   [0]=>
    //   string(27) "first_key loves first_value"
    //   [1]=>
    //   string(29) "second_key loves second_value"
    // }
    

    对于可重复使用的东西:

    function xmap(callable $cb, array $arr)
    {
        foreach ($arr as $key => $value) {
            yield $cb($key, $value);
        }
    }
    
    var_dump(iterator_to_array(
        xmap(function($a, $b) { return "$a loves $b"; }, $test_array)
    ));
    

    【讨论】:

      【解决方案12】:

      基于eis's answer,为了避免弄乱原始数组,我最终做了以下操作:

      $test_array = array("first_key" => "first_value",
                          "second_key" => "second_value");
      
      $result_array = array();
      array_walk($test_array, 
                 function($a, $b) use (&$result_array) 
                 { $result_array[] = "$b loves $a"; }, 
                 $result_array);
      var_dump($result_array);
      

      【讨论】:

      • 为什么这比直接将数组值和键直接传递给array_map 更容易?它更慢更复杂,我没有看到优势。
      • @Ariel 你能支持它会更慢的说法,即使是大数字?它只需要迭代一次数组,所以我认为在大 O 表示法中它应该更快。我同意复杂性。
      • @eis 它比较慢,因为它在 PHP 中一次创建一个结果数组,而不是在 C 中大量创建。它确实避免了 array_keys 调用(尽管它在 C 中很快)。对其进行基准测试 - 看看哪个更快,我不太确定,但通常更多的代码 = 更慢的代码。但在复杂性方面,它肯定更糟,而且大多数时候这比速度更重要。
      • 您不需要将第三个参数发送到array_walk,因为您没有在闭包中引用它。
      【解决方案13】:

      我做了这个函数,基于eis's answer:

      function array_map_($callback, $arr) {
          if (!is_callable($callback))
              return $arr;
      
          $result = array_walk($arr, function(&$value, $key) use ($callback) {
              $value = call_user_func($callback, $key, $value);
          });
      
          if (!$result)
              return false;
      
          return $arr;
      }
      

      例子:

      $test_array = array("first_key" => "first_value", 
                      "second_key" => "second_value");
      
      var_dump(array_map_(function($key, $value){
          return $key . " loves " . $value;
      }, $arr));
      

      输出:

      array (
        'first_key' => 'first_key loves first_value,
        'second_key' => 'second_key loves second_value',
      )
      

      当然,您可以使用 array_values 准确返回 OP 想要的内容。

      array_values(array_map_(function($key, $value){
          return $key . " loves " . $value;
      }, $test_array))
      

      【讨论】:

      • @KevinBeal 我在工作中经常使用这个功能。你能指出错误在哪里吗?
      • 首先,目前的代码缺少检查 $arr 是否属于数组类型,但是如果您键入提示您的参数为 callable 和 array 您可以改为放弃检查到is_callable。接下来,您对未使用的 $value 进行分配。您应该忽略返回值。第三,在回调中抛出异常比返回 false 更好。然后你要么总是返回一个有效值,要么总是抛出。
      【解决方案14】:

      YaLinqo library* 非常适合此类任务。它是 .NET 的 LINQ 端口,它完全支持所有回调中的值和键,并且类似于 SQL。例如:

      $mapped_array = from($test_array)
          ->select(function ($v, $k) { return "$k loves $v"; })
          ->toArray();
      

      或者只是:

      $mapped_iterator = from($test_array)->select('"$k loves $v"');
      

      这里,'"$k loves $v"' 是该库支持的完全闭包语法的快捷方式。 toArray() 最后是可选的。方法链返回一个迭代器,所以如果只需要使用foreach对结果进行迭代,可以去掉toArray调用。

      *我开发的

      【讨论】:

        【解决方案15】:

        我一直喜欢数组映射的 javascript 变体。最简单的版本是:

        /**
         * @param  array    $array
         * @param  callable $callback
         * @return array
         */
        function arrayMap(array $array, callable $callback)
        {
            $newArray = [];
        
            foreach( $array as $key => $value )
            {
                $newArray[] = call_user_func($callback, $value, $key, $array);
            }
        
            return $newArray;
        }
        

        所以现在你可以向它传递一个如何构造值的回调函数。

        $testArray = [
            "first_key" => "first_value", 
            "second_key" => "second_value"
        ];
        
        var_dump(
            arrayMap($testArray, function($value, $key) {
                return $key . ' loves ' . $value;
            });
        );
        

        【讨论】:

        • 将数据作为你编写的任何函数的最后一个参数会更有用,因为你可以创建一个新函数来烘焙一些特定的回调(行为)——即你得到函数组合:h(g(f($data))) 将f,然后g,然后h 应用于您的数据。在函数式编程中,通常认为拥有一个对 divers 数据执行相同操作的函数比拥有一个将 divers 函数应用于固定数据集的函数更通用。
        • 在您的示例中,该函数只有 1 个参数。我发现将数据作为第一个参数更容易,例如 array_filter、array_reduce 和 javascript 中的数组函数。
        • 这就是我的观点!通过最后传递数据,它允许您对函数进行 curry(创建一个将循环与特定操作相结合的新函数)并通过使用单个参数调用新函数将 that 应用于数据。这个校长比我在这里解释得更好,在这个答案中:stackoverflow.com/a/5863222
        • 在 PHP 这样的语言中使用 compose 函数不是解决这个问题的更好方法吗?
        • 这是一种替代方案,但需要对 FP 进行大量投资,例如:github.com/nickshanks/fp-php-talk/blob/master/lib.php#L24 或此:github.com/nickshanks/php-fp/blob/master/src/fp.php#L62
        【解决方案16】:

        我会这样做:

        <?php
        
        /**
         * array_map_kv()
         *   An array mapping function to map with both keys and values.
         *
         * @param $callback callable
         *   A callback function($key, $value) for mapping values.
         * @param $array array
         *   An array for mapping.
         */
        function array_map_kv(callable $callback, array $array) {
          return array_map(
            function ($key) use ($callback, $array) {
              return $callback($key, $array[$key]); // $callback($key, $value)
            },
            array_keys($array)
          );
        }
        
        // use it
        var_dump(array_map_kv(function ($key, $value) {
          return "{$key} loves {$value}";
        }, array(
          "first_key" => "first_value",
          "second_key" => "second_value",
        )));
        
        ?>
        

        结果:

        array(2) {
          [0]=>
          string(27) "first_key loves first_value"
          [1]=>
          string(29) "second_key loves second_value"
        }
        

        【讨论】:

          【解决方案17】:

          另一种使用(出)保留键的方法:

          $test_array = [
              "first_key"     => "first_value",
              "second_key"    => "second_value"
          ];
          
          $f = function($ar) {
              return array_map(
                  function($key, $val) {
                      return "{$key} - {$val}";
                  },
                  array_keys($ar),
                  $ar
              );
          };
          
          #-- WITHOUT preserving keys
          $res = $f($test_array);
          
          #-- WITH preserving keys
          $res = array_combine(
              array_keys($test_array),
              $f($test_array)
          );
          

          【讨论】:

            【解决方案18】:

            我发现它缺少明显的答案:

            function array_map_assoc(){
                if(func_num_args() < 2) throw new \BadFuncionCallException('Missing parameters');
            
                $args = func_get_args();
                $callback = $args[0];
            
                if(!is_callable($callback)) throw new \InvalidArgumentException('First parameter musst be callable');
            
                $arrays = array_slice($args, 1);
            
                array_walk($arrays, function(&$a){
                    $a = (array)$a;
                    reset($a);
                });
            
                $results = array();
                $max_length = max(array_map('count', $arrays));
            
                $arrays = array_map(function($pole) use ($max_length){
                    return array_pad($pole, $max_length, null);
                }, $arrays);
            
                for($i=0; $i < $max_length; $i++){
                    $elements = array();
                    foreach($arrays as &$v){
                        $elements[] = each($v);
                    }
                    unset($v);
            
                    $out = call_user_func_array($callback, $elements);
            
                    if($out === null) continue;
            
                    $val = isset($out[1]) ? $out[1] : null;
            
                    if(isset($out[0])){
                        $results[$out[0]] = $val;
                    }else{
                        $results[] = $val;
                    }
                }
            
                return $results;
            }
            

            与array_map 完全一样。差不多了。

            实际上,它不是纯粹的map,正如您从其他语言中所知道的那样。 PHP 很奇怪,所以它需要一些很奇怪的用户函数,因为我们不想破坏我们精确损坏的worse is better 方法。

            真的,它实际上根本不是map。然而,它仍然非常有用。

            • 与 array_map 的第一个明显区别是回调从每个输入数组中获取 each() 的输出,而不是单独的值。您仍然可以一次遍历更多数组。

            • 第二个区别是key从回调返回后的处理方式;回调函数的返回值应该是array('new_key', 'new_value')。键可以并且将被更改,如果返回相同的键,相同的键甚至会导致以前的值被覆盖。这不是常见的map 行为,但它允许您重写密钥。

            • 第三个奇怪的事情是,如果你在返回值中省略了key(array(1 =&gt; 'value') 或array(null, 'value')),就会分配新的密钥,就好像使用了$array[] = $value。这也不是map 的常见行为,但我猜它有时会派上用场。

            • 1234563这个功能完全没有mappy,但如果有这样的功能,它将使这个功能成为array_filter_assoc的绝佳替身。
            • 如果您在回调的返回中省略第二个元素 (1 =&gt; ...)(value 部分),则使用 null 而不是实际值。

            • 除了在回调返回中带有键 0 和 1 的元素之外的任何其他元素都将被忽略。

            • 最后,如果 lambda 返回除 null 或数组之外的任何值,则将其视为键和值都被省略,因此:

              1. 为元素分配了新键
              2. null 用作它的值
            警告:
            请记住,最后一个功能只是以前功能的残余,它可能完全没用。强烈建议不要依赖此功能,因为此功能将随机弃用并在未来版本中意外更改。

            注意:
            与array_map 不同,传递给array_map_assoc 的所有非数组参数,除第一个回调参数外,都以静默方式转换为数组。

            示例:
            // TODO: examples, anyone?

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2014-10-18
              • 2017-05-08
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多