Массив массивов PHP, включающий ключи


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_keysи array_valuesпрямой передачи $test_arrayпеременной?

Желаемый результат:

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

См. Также: stackoverflow.com/search?q=each_with_index для контрастного подхода к этой общей проблеме
dreftymac

Ответы:


207

Не с 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

1
правильно, изменилось :) хорошо, как они сделали array_map из array_walk.
эйс

Здорово спасибо Чтобы не испортить исходный массив, вот что я в итоге сделал (смотри мой ответ ниже)
Хосе Томас Тосино

3
Это не "функциональное программирование", хотя, так array_walk()как не возвращает результирующий массив, а вместо этого bool.
Мэ

@ да, как я уже писал в ответе - вместо возврата значения он меняет параметр
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 (), было бы глупо предоставить ответ, который не имеет преимущества (например, вызывает меньше функций) по сравнению с текущим решением.
Чиното Вокро

Ответ на оригинальный вопрос - НЕТ, и это наиболее подходящее решение.
Усобан

65

Вот мое очень простое, 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"
}

Частичное применение

Если вам нужно многократно использовать функцию с разными массивами, но с одной и той же функцией отображения, вы можете сделать то, что называется частичным применением функции (связано с « каррированием »), что позволяет передавать массив данных только при вызове:

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 с половиной года :) Я пришел сюда в поисках решения, не нашел ничего, что мне понравилось, поэтому придумал свое.
Николас Шенкс

1
Я буду таким парнем. PHP 5.3 больше не должен быть требованием для даты этого ответа. ПО МОЕМУ МНЕНИЮ.
Erutan409

1
Ваше первое альтернативное решение недействительно. Вы должны заменить array_mergeна, array_replaceчтобы сохранить ключи, которые были бы целыми числами.
Alex83690

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
Я думаю, что требование было «вместо вызова array_keys и array_values, напрямую передавая переменную $ test_array», это можно использовать без array_keys?
эйс

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
Брэд Кент

2

Под «ручным циклом» я подразумевал написание пользовательской функции, которая использует foreach. Это возвращает новый массив, как это 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

Это не жутко, это просто означает, что вы забыли, unset( $value )потому что оно все еще существует в определенной области.
Азиз Пунджани

@azis, шутил насчет жуткости, ссылаясь на статью. Это создаст неожиданные эффекты, если вы забудете сбросить.
janenz00

1
Спасибо за ответ, но я подумал, что совершенно ясно, что я не хочу использовать традиционный цикл.
Хосе Томас Тосино

@ janenz00 См. отредактированный ответ для уточнения. Я имел в виду зацикливание в чистой области видимости переменных.
ryanve

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? Это медленнее и сложнее, я не вижу преимущества.
Ариэль

1
@ Ариэль, можешь ли ты подтвердить, что это будет медленнее, даже с большими числами? Он должен повторять массив только один раз, поэтому я думаю, что он должен быть на величины быстрее в больших O-нотациях. Я согласен о сложности, хотя.
эйс

@eis Это медленнее, потому что он создает массив результатов по одному в PHP, а не массово в C. Он, тем не менее, избегает вызова array_keys (хотя это быстро, поскольку он в C). Оцените это - посмотрите, что быстрее, я не совсем уверен, но обычно больше кода = медленный код. По сложности это определенно хуже, и в большинстве случаев это важнее скорости.
Ариэль

1
Вам не нужно отправлять третий аргумент, так 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чтобы вернуть именно то, что хочет ОП.

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

@KevinBeal Я часто использую эту функцию в своей работе. Не могли бы вы указать, где ошибки?
Хулио Ведоватто

2
Во-первых, в коде в его нынешнем виде отсутствует проверка, имеющая $arrтип массив, однако, если вы введете подсказку в качестве аргумента как, callableи arrayвместо этого вы можете отбросить проверку is_callable. Затем вы делаете присвоение $ value, которое затем не используется. Вы должны просто игнорировать возвращаемое значение. В-третьих, было бы лучше вызвать исключение в обратном вызове, чем возвращать false. Затем вы либо всегда вернете правильное значение, либо всегда выбросите.
Николас Шенкс

1

Библиотека YaLinqo * хорошо подходит для такого рода задач. Это порт LINQ из .NET, который полностью поддерживает значения и ключи во всех обратных вызовах и напоминает 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вызов может быть удален.

* разработано мной


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_combineсоздает key => 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-вариант массива map. Самый простой вариант этого будет:

/**
 * @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к вашему данные. Обычно считается, что в функциональном программировании более универсально иметь функцию, которая выполняет ту же самую операцию с данными о дайверах, чем иметь функцию, которая применяет функции дайверов к фиксированному набору данных.
Николас Шенкс

В вашем примере у вас есть только 1 аргумент для функции. Я считаю, что проще поместить данные в качестве первого аргумента, например, array_filter, array_reduce и функции массива в javascript.
Blablabla

Это моя точка зрения! Передав данные последним, это позволяет вам каррировать функцию (создать новую функцию, которая объединяет циклы с определенной операцией) и применять это к данным, вызывая новую функцию с одним параметром. Этот принцип объяснен лучше, чем я могу здесь, в этом ответе: stackoverflow.com/a/5863222
Николас Шенкс

Разве использование функции compose на языке, подобном PHP, не является лучшим решением этой проблемы?
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возвращаемое значение (либо by, array(1 => 'value')либо array(null, 'value')), новый ключ будет назначен, как если бы он $array[] = $valueбыл использован. Это не mapобычное поведение, но иногда бывает полезно, я думаю.

  • Четвертая странная вещь: если функция обратного вызова не возвращает значение или возвращает null, весь набор текущих ключей и значений опускается в выводе, он просто пропускается. Эта функция совершенно не работает map, но это сделало бы эту функцию отличным трюком для двойной array_filter_assoc, если бы была такая функция.

  • Если вы опустите второй элемент ( 1 => ...) ( часть значения ) в возврате обратного вызова, nullиспользуется вместо реального значения.

  • Любые другие элементы, кроме тех, которые имеют ключи 0и 1возвращаются в обратном вызове, игнорируются.

  • И, наконец, если лямбда возвращает какое-либо значение, кроме nullмассива или, оно обрабатывается так, как если бы ключ и значение были опущены, поэтому:

    1. назначен новый ключ для элемента
    2. null используется как значение
ВНИМАНИЕ:
Имейте в виду, что эта последняя функция является лишь остатком предыдущих функций и, вероятно, совершенно бесполезна. Полагаться на эту функцию крайне не рекомендуется, так как эта функция будет случайно исключена и неожиданно измененной в будущих выпусках.

ПРИМЕЧАНИЕ.
В отличие от in array_map, все array_map_assocпараметры, не являющиеся массивами, переданные , за исключением первого параметра обратного вызова, автоматически преобразуются в массивы.

ПРИМЕРЫ:
// TODO: examples, anyone?

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.