PHP的array_map包括键


208

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

$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_keysarray_values,直接传递$test_array变量?

所需的输出是:

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

Answers:


206

不适用于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的所需输出匹配。
cmbuckley 2012年

1
正确,已更改:)很高兴他们使array_map与array_walk有所不同。
eis 2012年

很好,谢谢。为了避免搞乱原来的阵列,这里就是我最终没有(看我的回答如下)
何塞托马斯腌肠

3
这不是“功能编程”,因为array_walk()它不会返回结果数组,而是返回布尔值。
mae

@mae是的,就像我在答案中写的一样-而不是返回值,而是更改了参数
-eis

145

这可能是最短和最容易推断的原因:

$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')
)

15
我只是意识到,这个问题专门说不使用array_keys()。不过,这似乎是一个愚蠢的要求。
凯文·比尔

3
该问题提供了使用array_keys()的解决方案,但提供比当前解决方案没有优势(例如,调用较少的函数)的答案将是很愚蠢的。
Chinoto Vokro'3

原始问题的答案为否,这是最合适的解决方案。
usoban

65

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

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

您提供的Callable应该本身返回一个带有两个值的数组,即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"
}

部分申请

如果您需要多次使用具有不同数组但具有相同映射功能的函数,则可以执行称为部分函数应用程序(与“ 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。如前所述,返回重复的键将导致以后的值获胜。

nb 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));

因此答案是否定的,您不能避免调用array_keys,但是您可以抽象出array_keys被调用到更高阶函数中的位置,这可能就足够了。


7
似乎应该将此类标记为正确答案。
eddiewould

6
@eddiewould,谢谢,但是我迟到了4½年:)我来这里寻求解决方案,找不到我喜欢的解决方案,于是提出了我自己的解决方案。
Nicholas Shanks

1
我就是那个家伙。PHP 5.3应该不再是日期的要求这个答案。恕我直言。
Erutan409 '18

1
您的第一个替代解决方案无效。您必须替换array_mergearray_replace以保留将为整数的键。
Alex83690 '18

1
@ Alex83690谢谢!虽然我会说“无效”有些误导-这是好的,如果你没有任何整数键(因为是我自己的情况属实)。
尼古拉斯·尚克斯

20

使用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)
    )
);

1
我认为要求是“而不是直接调用$ test_array变量而不是调用array_keys和array_values”,可以在没有array_keys的情况下使用吗?
eis 2014年

4

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

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));
}

非常干净,并且不会改变原始数组!
拉斐尔·坎德利雷

4

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

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
Brad Kent

2

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

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

实际上,使用array_mapwith的技术array_keys看起来更简单并且功能更强大,因为您可以将其null用作回调以返回键值对:

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

引用循环数组,可能会导致发生怪异的事情
janenz00 2012年

这并不怪异,只是意味着您忘记了,unset( $value )因为它仍然存在于定义的范围内。
阿齐兹·蓬贾尼(Anniz Punjani)2012年

@azis在开玩笑,指的是这篇文章。如果您忘记取消设置,它将产生意想不到的效果。
janenz00 2012年

1
感谢您的回答,但我认为很明显我不想使用传统循环。
2012年

@ janenz00请参阅编辑后的答案以进行澄清。我的意思是循环一个干净的变量范围。
ryanve 2014年

1

根据eis的答案,这是我最终为了避免弄乱原始数组而做的事情:

$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);

2
为什么这比仅将数组值和键直接传递给array_map更容易?它比较慢,也更复杂,我看不到优势。
阿里埃勒(Ariel)2014年

1
@Ariel您是否可以支持声称即使有大量数字,速度也较慢?它只需要迭代一次数组,所以我认为大O表示法的速度应该更快。我同意复杂性。
2014年

@eis速度较慢,因为它一次在PHP中创建了一个结果数组,而不是在C中批量创建。尽管如此,它确实避免了array_keys的调用(尽管这很快,因为它在C中)。对它进行基准测试-看看哪个更快,我不确定,但是通常更多代码=较慢代码。但是在复杂性方面,这绝对会更糟,并且在大多数情况下,这比提高速度更为重要。
Ariel 2014年

1
您不需要将第三个arg发送到,array_walk因为您没有在闭包中引用它。
史蒂文·卢

1

我根据eis的答案做了这个功能:

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我在工作中经常使用此功能。您能指出错误在哪里吗?
朱利奥·韦多瓦托

2
首先,目前的代码缺少$arr数组类型的检查,但是如果您将参数键入为as callable,则array可以将检查放到is_callable。接下来,您对$ value进行赋值,然后将其未使用。您应该只忽略返回值。第三,在回调中抛出异常要比返回false更好。然后,您将始终返回有效值或始终抛出。
Nicholas Shanks

1

YaLinqo库*非常适合此类任务。它是.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()最后是可选的。方法链返回一个迭代器,因此,如果只需要使用来迭代结果foreachtoArray则可以删除调用。

*由我开发


1

我会做这样的事情:

<?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"
}

1

我将使用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_combinekey => value从键数组和值数组创建一个数组,其余的很容易解释。


1

您可以使用此数组库中的map方法来轻松实现所需的目标:

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

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


1
$array = [
  'category1' => 'first category',
  'category2' => 'second category',
];

$new = array_map(function($key, $value) {
  return "{$key} => {$value}";
}, array_keys($array), $array);

资源


0

我一直喜欢数组映射的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)))apply f,然后g,然后h到您的数据。通常,在函数式编程中,具有对divers数据执行相同操作的功能比具有将divers功能应用于固定数据集的功能更具通用性。
Nicholas Shanks

在您的示例中,该函数只有1个参数。我发现将数据作为第一个参数比较容易,例如array_filter,array_reduce和javascript中的数组函数。
blablabla

这就是我的观点!通过最后传递数据,它使您可以咖喱函数(创建将循环与特定操作结合在一起的新函数),并通过使用单个参数调用新函数将应用于数据。在这个答案中,我对此问题的解释比我在这里能解释的要好得多:stackoverflow.com/a/5863222
Nicholas Shanks

在像PHP这样的语言中使用compose函数不是解决此问题的更好解决方案吗?
blablabla

1
这是一种替代方法,但需要在FP上进行大量投资,例如:github.com/nickshanks/fp-php-talk/blob/master/lib.php#L24或以下代码:github.com/nickshanks/php-fp/blob /master/src/fp.php#L62
尼古拉斯·尚克斯

0

使用(不使用)保留密钥的另一种方法:

$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)
);

-2

我看到它缺少明显的答案:

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()从每个输入数组获取输出,而不是单独获取值。您仍然可以一次遍历更多数组。

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

  • 第三怪异的事情是,如果您省略key了返回值(通过array(1 => 'value')array(null, 'value')),则将分配新密钥,就像$array[] = $value使用过的那样。这也不map是常见的行为,但是我想有时候它会派上用场。

  • 第四个怪异的事情是,如果回调函数不返回值或返回null,则从输出中省略当前键和值的整个集合,将其跳过。此功能是完全不map统一的,但array_filter_assoc如果有此功能,它将使该功能成为特技的两倍。

  • 如果您省略了回调返回中的第二个元素(1 => ...)(部分),null则使用它代替实际值。

  • 除了带有键01在callback的返回中的元素以外的任何其他元素都将被忽略。

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

    1. 分配了元素的新键
    2. null 被用作价值
警告:
请记住,这最后一个功能只是先前功能的残余,可能完全没有用。强烈建议不要使用此功能,因为此功能将在以后的版本中被不建议使用并进行意外更改。

注意:
与in不同array_maparray_map_assoc除了第一个回调参数外,传递给的所有非数组参数都将静默转换为数组。

例子:
// TODO: examples, anyone?

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.