17

Example:

$arr = array(
  'apple'      => 'sweet',
  'grapefruit' => 'bitter',
  'pear'       => 'tasty',
  'banana'     => 'yellow'
);

I want to switch the positions of grapefruit and pear, so the array will become

$arr = array(
  'apple'      => 'sweet',
  'pear'       => 'tasty',
  'grapefruit' => 'bitter',
  'banana'     => 'yellow'
)

I know the keys and values of the elements I want to switch, is there an easy way to do this? Or will it require a loop + creating a new array?

Thanks

leon.nk
  • 437
  • 2
  • 6
  • 15

17 Answers17

19

Just a little shorter and less complicated than the solution of arcaneerudite:

<?php
if(!function_exists('array_swap_assoc')) {
    function array_swap_assoc($key1, $key2, $array) {
        $newArray = array ();
        foreach ($array as $key => $value) {
            if ($key == $key1) {
                $newArray[$key2] = $array[$key2];
            } elseif ($key == $key2) {
                $newArray[$key1] = $array[$key1];
            } else {
                $newArray[$key] = $value;
            }
        }
        return $newArray;
    }
}

$array = $arrOrig = array(
    'fruit' => 'pear',
    'veg' => 'cucumber',
    'tuber' => 'potato',
    'meat' => 'ham'
);

$newArray = array_swap_assoc('veg', 'tuber', $array);

var_dump($array, $newArray);
?>

Tested and works fine

metti
  • 266
  • 2
  • 5
  • +1, but it will blow up if one index exists and the other does not. – Matthew Jun 17 '11 at 16:39
  • @Mathew: Indeed, but after fixing this problem it would serve the desired purpose well. – Mike Mar 05 '14 at 13:59
  • +1, this seems to be perfect for swapping elements of large arrays. For small, simple, two elements swap, I'd much more prefer a [direct solution](http://stackoverflow.com/a/11298344/1469208)! :] – trejder May 15 '14 at 13:49
  • @trejder: but your direct solutions will not preserve keys! – Legionar May 30 '14 at 12:54
  • +1 add this line to fix blowup: `if (!isset($array[$key1]) || !isset($array[$key2])) {throw new InvalidArgumentException();}` – YAMM Sep 24 '15 at 22:49
4

Here's my version of the swap function:

function array_swap_assoc(&$array,$k1,$k2) {
  if($k1 === $k2) return;  // Nothing to do

  $keys = array_keys($array);  
  $p1 = array_search($k1, $keys);
  if($p1 === FALSE) return;  // Sanity check...keys must exist

  $p2 = array_search($k2, $keys);
  if($p2 === FALSE) return;

  $keys[$p1] = $k2;  // Swap the keys
  $keys[$p2] = $k1;

  $values = array_values($array); 

  // Swap the values
  list($values[$p1],$values[$p2]) = array($values[$p2],$values[$p1]);

  $array = array_combine($keys, $values);
}
ttk
  • 407
  • 3
  • 4
2

if the array comes from the db, add a sort_order field so you can always be sure in what order the elements are in the array.

dazz
  • 96
  • 1
  • 6
1

This may or may not be an option depending on your particular use-case, but if you initialize your array with null values with the appropriate keys before populating it with data, you can set the values in any order and the original key-order will be maintained. So instead of swapping elements, you can prevent the need to swap them entirely:

$arr = array('apple' => null,
             'pear' => null,
             'grapefruit' => null,
             'banana' => null);

...

$arr['apple'] = 'sweet';
$arr['grapefruit'] = 'bitter'; // set grapefruit before setting pear
$arr['pear'] = 'tasty';
$arr['banana'] = 'yellow';
print_r($arr);

>>> Array
(
    [apple] => sweet
    [pear] => tasty
    [grapefruit] => bitter
    [banana] => yellow
)
keithjgrant
  • 12,421
  • 6
  • 54
  • 88
1

Not entirely sure if this was mentioned, but, the reason this is tricky is because it's non-indexed.

Let's take:

$arrOrig = array(
  'fruit'=>'pear',
  'veg'=>'cucumber',
  'tuber'=>'potato'
);

Get the keys:

$arrKeys = array_keys($arrOrig);
print_r($arrKeys);
Array(
 [0]=>fruit
 [1]=>veg
 [2]=>tuber
)

Get the values:

$arrVals = array_values($arrOrig);
print_r($arrVals);
Array(
  [0]=>pear
  [1]=>cucumber
  [2]=>potato
)

Now you've got 2 arrays that are numerical. Swap the indices of the ones you want to swap, then read the other array back in in the order of the modified numerical array. Let's say we want to swap 'fruit' and 'veg':

$arrKeysFlipped = array_flip($arrKeys);
print_r($arrKeysFlipped);
Array (
 [fruit]=>0
 [veg]=>1
 [tuber]=>2
)
$indexFruit = $arrKeysFlipped['fruit'];
$indexVeg = $arrKeysFlipped['veg'];
$arrKeysFlipped['veg'] = $indexFruit;
$arrKeysFlipped['fruit'] = $indexVeg;
print_r($arrKeysFlipped);
Array (
 [fruit]=>1
 [veg]=>0
 [tuber]=>2
)

Now, you can swap back the array:

$arrKeys = array_flip($arrKeysFlipped);
print_r($arrKeys);
Array (
 [0]=>veg
 [1]=>fruit
 [2]=>tuber
)

Now, you can build an array by going through the oringal array in the 'order' of the rearranged keys.

$arrNew = array ();
foreach($arrKeys as $index=>$key) {
  $arrNew[$key] = $arrOrig[$key];
}
print_r($arrNew);
Array (
 [veg]=>cucumber
 [fruit]=>pear
 [tuber]=>potato
)

I haven't tested this - but this is what I'd expect. Does this at least provide any kind of help? Good luck :)

You could put this into a function $arrNew = array_swap_assoc($key1,$key2,$arrOld);

<?php
if(!function_exists('array_swap_assoc')) {
    function array_swap_assoc($key1='',$key2='',$arrOld=array()) {
       $arrNew = array ();
       if(is_array($arrOld) && count($arrOld) > 0) {
           $arrKeys = array_keys($arrOld);
           $arrFlip = array_flip($arrKeys);
           $indexA = $arrFlip[$key1];
           $indexB = $arrFlip[$key2];
           $arrFlip[$key1]=$indexB;
           $arrFlip[$key2]=$indexA;
           $arrKeys = array_flip($arrFlip);
           foreach($arrKeys as $index=>$key) {
             $arrNew[$key] = $arrOld[$key];
           }
       } else {
           $arrNew = $arrOld;
       }
       return $arrNew;
    }
}
?>

WARNING: Please test and debug this before just using it - no testing has been done at all.

0

There is no easy way, just a loop or a new array definition.

Peter Gluck
  • 8,168
  • 1
  • 38
  • 37
OverLex
  • 2,501
  • 1
  • 24
  • 27
  • 2
    It's for using with Drupal form API, the order of the array determines the order each element (field) is printed. However, I can also use the '#weight' property to alter the order which it seems would be easier. It just means iterating over each field to set a new weight. – leon.nk Mar 15 '10 at 15:47
  • 5
    I agree with Lex. PHP does not provide a native function to swap array items. You can probably write your own with a combination of the rest of the array functions and lots of patience but, honestly, rewriting the full array with a foreach loop is by far the easiest method. A custom swap function is not worth the effort unless you handle very large arrays and, in such case, you should probably redesign that part of the code logic. – Álvaro González Mar 15 '10 at 16:15
  • Is this supposed to be an answer? Well, I happen to have the same problem. I need to swap the order of any two elements in an array – Mike Mar 05 '14 at 12:29
  • Yes, this does answer the question, but Metti's answer is what people will be looking for (although note the comments). – Jamie G Apr 04 '14 at 16:13
0

Classical associative array doesn't define or guarantee sequence of elements in any way. There is plain array/vector for that. If you use associative array you are assumed to need random access but not sequential. For me you are using assoc array for task it is not made for.

Andrey
  • 59,039
  • 12
  • 119
  • 163
  • 3
    If that was true, it'd make asort() any other builtin functions completely useless... – Álvaro González Mar 15 '10 at 15:53
  • if you see i wrote "Classical associative array", not "php associative array". Function asort is specific to php implementation. again what i said is correct, one should assoc arrays for random access. if people would care look a bit wider than just PHP... – Andrey Mar 15 '10 at 15:57
  • 1
    What's the problem of using PHP implementations when coding in PHP? Do you write language agnostic PHP code? What for? So you can run your PHP with a Perl interpreter? :-? – Álvaro González Mar 15 '10 at 16:07
  • key part was that assoc array was made for "random access". if you try to use it for sequential access most probably you designed your code incorrectly. – Andrey Mar 15 '10 at 16:15
  • -1 because the order is a well defined and supported feature of php. – goat Mar 15 '10 at 18:33
  • the only supported thing is that this order is same if array is not modified. i downloaded php sources and saw that assoc arrray is implemented as a hash table + linked list for iteration. what you call by "order" is position in that linked list. you can edit this list in any way, just by adding or removing elements. no one can define that this list will be populated the same way in every php version. – Andrey Mar 15 '10 at 20:44
  • the only reason this list is persisted is iteration of all values, because it is hard to fetch them from hash table itself. other use of it are architecturally incorrect and can appear due to lack of basic data structure principles. – Andrey Mar 15 '10 at 20:46
  • the php array is an _ordered_ map. It is a feature. It is predictable. It is advertised as being so. Numerous occurrences of native functions and documentation rely on this feature. – goat Mar 15 '10 at 22:18
  • where did you get that it is ordered? i took **sources** of file php.exe and saw implementation of it. Associative array in PHP is Hashmap for O(1) access and linked list for traversal. – Andrey Mar 16 '10 at 13:34
0

yeah I agree with Lex, if you are using an associative array to hold data, why not using your logic handle how they are accessed instead of depending on how they are arranged in the array.

If you really wanted to make sure they were in a correct order, trying creating fruit objects and then put them in a normal array.

Bryan Clark
  • 203
  • 1
  • 2
  • 10
0

There is no easy way to do this. This sounds like a slight design-logic error on your part which has lead you to try to do this when there is a better way to do whatever it is you are wanting to do. Can you tell us why you want to do this?

You say that I know the keys and values of the elements I want to switch which makes me think that what you really want is a sorting function since you can easily access the proper elements anytime you want as they are.

$value = $array[$key];

If that is the case then I would use sort(), ksort() or one of the many other sorting functions to get the array how you want. You can even use usort() to Sort an array by values using a user-defined comparison function.

Other than that you can use array_replace() if you ever need to swap values or keys.

Xeoncross
  • 55,620
  • 80
  • 262
  • 364
0

fwiw here is a function to swap two adjacent items to implement moveUp() or moveDown() in an associative array without foreach()

/**
 * @param array  $array     to modify
 * @param string $key       key to move
 * @param int    $direction +1 for down | -1 for up
 * @return $array
 */
protected function moveInArray($array, $key, $direction = 1)
{
    if (empty($array)) {
        return $array;
    }
    $keys  = array_keys($array);
    $index = array_search($key, $keys);
    if ($index === false) {
        return $array; // not found
    } 
    if ($direction < 0) {
        $index--;
    }
    if ($index < 0 || $index >= count($array) - 1) {
        return $array; // at the edge: cannot move
    } 

    $a          = $keys[$index];
    $b          = $keys[$index + 1];
    $result     = array_slice($array, 0, $index, true);
    $result[$b] = $array[$b];
    $result[$a] = $array[$a];
    return array_merge($result, array_slice($array, $index + 2, null, true)); 
}
Steve
  • 3,601
  • 4
  • 34
  • 41
0

There is an easy way:

$sourceArray = array(
    'apple'      => 'sweet',
    'grapefruit' => 'bitter',
    'pear'       => 'tasty',
    'banana'     => 'yellow'
);
// set new order
$orderArray = array(
    'apple'      => '', //this values would be replaced
    'pear'       => '',
    'grapefruit' => '',
    //it is not necessary to touch all elemets that will remains the same
);
$result = array_replace($orderArray, $sourceArray);
print_r($result);

and you get:

$result = array(
  'apple'      => 'sweet',
  'pear'       => 'tasty',
  'grapefruit' => 'bitter',
  'banana'     => 'yellow'
)
Eugene Kaurov
  • 2,356
  • 28
  • 39
0
function arr_swap_keys(array &$arr, $key1, $key2, $f_swap_vals=false) {
   // if f_swap_vals is false, then
   // swap only the keys, keeping the original values in their original place
   // ( i.e. do not preserve the key value correspondence )
   // i.e. if arr is (originally)
   // [ 'dog' => 'alpha', 'cat' => 'beta', 'horse' => 'gamma' ]
   // then calling this on arr with, e.g. key1 = 'cat', and key2 = 'horse'
   // will result in arr becoming: 
   // [ 'dog' => 'alpha', 'horse' => 'beta', 'cat' => 'gamma' ]
   // 
   // if f_swap_vals is true, then preserve the key value correspondence
   // i.e. in the above example, arr will become:
   // [ 'dog' => 'alpha', 'horse' => 'gamma', 'cat' => 'beta' ]
   // 
   // 

   $arr_vals = array_values($arr); // is a (numerical) index to value mapping
   $arr_keys = array_keys($arr);   // is a (numerical) index to key mapping
   $arr_key2idx = array_flip($arr_keys);
   $idx1 = $arr_key2idx[$key1];
   $idx2 = $arr_key2idx[$key2];
   swap($arr_keys[$idx1], $arr_keys[$idx2]);
   if ( $f_swap_vals ) {
      swap($arr_vals[$idx1], $arr_vals[$idx2]);
   }
   $arr = array_combine($arr_keys, $arr_vals);
}

function swap(&$a, &$b) {
   $t = $a;
   $a = $b;
   $b = $t;
}
0

I'll share my short version too, it works with both numeric and associative arrays.

array array_swap ( array $array , mixed $key1 , mixed $key2 [, bool $preserve_keys = FALSE [, bool $strict = FALSE ]] )

Returns a new array with the two elements swapped. It preserve original keys if specified. Return FALSE if keys are not found.

function array_swap(array $array, $key1, $key2, $preserve_keys = false, $strict = false) {
    $keys = array_keys($array);
    if(!array_key_exists($key1, $array) || !array_key_exists($key2, $array)) return false;
    if(($index1 = array_search($key1, $keys, $strict)) === false) return false;
    if(($index2 = array_search($key2, $keys, $strict)) === false) return false;
    if(!$preserve_keys) list($keys[$index1], $keys[$index2]) = array($key2, $key1);
    list($array[$key1], $array[$key2]) = array($array[$key2], $array[$key1]);
    return array_combine($keys, array_values($array));
}

For example:

$arr = array_swap($arr, 'grapefruit', 'pear');
SubjectDelta
  • 405
  • 1
  • 3
  • 14
0

Well it's just a key sorting problem. We can use uksort for this purpose. It needs a key comparison function and we only need to know that it should return 0 to leave keys position untouched and something other than 0 to move key up or down.

Notice that it will only work if your keys you want to swap are next to each other.

<?php

$arr = array(
  'apple'      => 'sweet',
  'grapefruit' => 'bitter',
  'pear'       => 'tasty',
  'banana'     => 'yellow'
);

uksort(
    $arr,
    function ($k1, $k2) {
        if ($k1 == 'grapefruit' && $k2 == 'pear') return 1;
        else return 0;
    }
);

var_dump($arr);
Karol Samborski
  • 2,757
  • 1
  • 11
  • 18
0

Here are two solutions. The first is longer, but doesn't create a temporary array, so it saves memory. The second probably runs faster, but uses more memory:

function swap1(array &$a, $key1, $key2)
{
  if (!array_key_exists($key1, $a) || !array_key_exists($key2, $a) || $key1 == $key2) return false;

  $after = array();
  while (list($key, $val) = each($a))
  {
    if ($key1 == $key)
    {
      break;
    }
    else if ($key2 == $key)
    {
      $tmp = $key1;
      $key1 = $key2;
      $key2 = $tmp;
      break;
    }
  }

  $val1 = $a[$key1];
  $val2 = $a[$key2];

  while (list($key, $val) = each($a))
  {
    if ($key == $key2)
      $after[$key1] = $val1;
    else
      $after[$key] = $val;
    unset($a[$key]);
  }

  unset($a[$key1]);
  $a[$key2] = $val2;

  while (list($key, $val) = each($after))
  {
    $a[$key] = $val;
    unset($after[$key]);
  }

  return true;
}

function swap2(array &$a, $key1, $key2)
{    
  if (!array_key_exists($key1, $a) || !array_key_exists($key2, $a) || $key1 == $key2) return false;

  $swapped = array();

  foreach ($a as $key => $val)
  {
    if ($key == $key1)
      $swapped[$key2] = $a[$key2];
    else if ($key == $key2)
      $swapped[$key1] = $a[$key1];
    else
      $swapped[$key] = $val;
  }

  $a = $swapped;

  return true;
}
Matthew
  • 47,584
  • 11
  • 86
  • 98
-1

I wrote a function with more general purpose, with this problem in mind.

  1. array with known keys
  2. specify order of keys in a second array ($order array keys indicate key position)

function order_array($array, $order) {

    foreach (array_keys($array) as $k => $v) {
        $keys[++$k] = $v;
    }
    for ($i = 1; $i <= count($array); $i++) {
        if (isset($order[$i])) {
            unset($keys[array_search($order[$i], $keys)]);
        }
        if ($i === count($array)) {
                    array_push($keys, $order[$i]);
                } else {
                    array_splice($keys, $i-1, 0, $order[$i]);
                }
            }
        }
        foreach ($keys as $key) {
            $result[$key] = $array[$key];
        }
        return $result;
    } else {
        return false;
    }
}

$order = array(1 => 'item3', 2 => 'item5');
$array = array("item1" => 'val1', "item2" => 'val2', "item3" => 'val3', "item4" => 'val4', "item5" => 'val5');

print_r($array); -> Array ( [item1] => val1 [item2] => val2 [item3] => val3 [item4] => val4 [item5] => val5 ) 

print_r(order_array($array, $order)); -> Array ( [item3] => val3 [item5] => val5 [item1] => val1 [item2] => val2 [item4] => val4 )

I hope this is relevant / helpful for someone

G4Hu
  • 338
  • 1
  • 3
  • 18
-2

Arrays in php are ordered maps.

$arr = array('apple'=>'sweet','grapefruit'=>'bitter','
pear'=>'tasty','banana'=>'yellow');

doesn't mean that that the first element is 'apple'=>'sweet' and the last - 'banana'=>'yellow' just because you put 'apple' first and 'banana' last. Actually, 'apple'=>'sweet' will be the first and 'banana'=>'yellow' will be the second because of alphabetical ascending sort order.

a1ex07
  • 36,826
  • 12
  • 90
  • 103
  • 3
    This would only be true after a `ksort()`. Indexed arrays maintain their ordering (eg. the order in which the array elements are initialized) – keithjgrant Mar 15 '10 at 18:16
  • -1 by default, elements are implicitly ordered by the time that a key was initialized. – goat Mar 15 '10 at 18:41