59

I'm looking for a simple function to move an array element to a new position in the array and resequence the indexes so that there are no gaps in the sequence. It doesnt need to work with associative arrays. Anyone got ideas for this one?

$a = array(
      0 => 'a',
      1 => 'c',
      2 => 'd',
      3 => 'b',
      4 => 'e',
);
print_r(moveElement(3,1))
//should output 
    [ 0 => 'a',
      1 => 'b',
      2 => 'c',
      3 => 'd',
      4 => 'e' ]
0

10 Answers 10

133

2x array_splice, there even is no need to renumber:

$array = [
    0 => 'a', 
    1 => 'c', 
    2 => 'd', 
    3 => 'b', 
    4 => 'e',
];

function moveElement(&$array, $fromIndex, $toIndex) {
    $out = array_splice($array, $fromIndex, 1);
    array_splice($array, $toIndex, 0, $out);
}

moveElement($array, 3, 1);

Result:

[
    0 => 'a',
    1 => 'b',
    2 => 'c',
    3 => 'd',
    4 => 'e',
];

redzarf noted: "To clarify $a is $fromIndex and $b is $toIndex"

Sign up to request clarification or add additional context in comments.

5 Comments

Yes it is nice and simple solution, but it works only if old index is greater than new index. Here is another solution: link
@quarky: No, it works as well if new index is greater than old index. As well if both are the same.
But if your destination is AFTER the source, it may not place it where you predict, because the array gets shorter after the first step. If moving to the end, you don't want to use size-1, you want size-2
@GarrGodfrey: Honestly, I've never seen code that works well with predictions. But thanks for your feedback.
It seems that if the offset is greater than the array size, array_splice uses the array size instead, so this works.
14

A lot of good answers. Here's a simple one built on the answer by @RubbelDeCatc. The beauty of it is that you only need to know the array key, not its current position (before repositioning).

/**
 * Reposition an array element by its key.
 *
 * @param array      $array The array being reordered.
 * @param string|int $key They key of the element you want to reposition.
 * @param int        $order The position in the array you want to move the element to. (0 is first)
 *
 * @throws \Exception
 */
function repositionArrayElement(array &$array, $key, int $order): void
{
    if(($a = array_search($key, array_keys($array))) === false){
        throw new \Exception("The {$key} cannot be found in the given array.");
    }
    $p1 = array_splice($array, $a, 1);
    $p2 = array_splice($array, 0, $order);
    $array = array_merge($p2, $p1, $array);
}

Straight forward to use:

$fruits = [
    'bananas'=>'12', 
    'apples'=>'23',
    'tomatoes'=>'21', 
    'nuts'=>'22',
    'foo'=>'a',
    'bar'=>'b'
];

repositionArrayElement($fruits, "foo", 1);

var_export($fruits);

/** Returns
array (
  'bananas' => '12',
  'foo' => 'a', <--  Now moved to position #1
  'apples' => '23',
  'tomatoes' => '21',
  'nuts' => '22',
  'bar' => 'b',
)
**/

Works on numeric arrays also:

$colours = ["green", "blue", "red"];

repositionArrayElement($colours, 2, 0);

var_export($colours);

/** Returns
array (
  0 => 'red', <-- Now moved to position #0
  1 => 'green',
  2 => 'blue',
)
*/

Demo

Comments

9

The solution from hakre with two array_splice commands doesn't work with named arrays. The key of the moved element will be lost.

Instead you can splice the array two times and merge the parts.

function moveElement(&$array, $a, $b) {
    $p1 = array_splice($array, $a, 1);
    $p2 = array_splice($array, 0, $b);
    $array = array_merge($p2,$p1,$array);
}

How does it work:

  • First: remove/splice the element from the array
  • Second: splice the array into two parts at the position you want to insert the element
  • Merge the three parts together

Example:

$fruits = array(
    'bananas'=>'12', 
    'apples'=>'23',
    'tomatoes'=>'21', 
    'nuts'=>'22',
    'foo'=>'a',
    'bar'=>'b'
);

moveElement($fruits, 1, 3);

// Result
['bananas'=>'12', 'tomatoes'=>'21', 'nuts'=>'22', 'apples'=>'23', 'foo'=>'a', 'bar'=>'b']

Comments

3

Arrays in PHP are not actual array in the C sens but associative arrays. But the way to move a value from an index to another is quiet straight forward and is the same as in C++:

Copy the value to move to a temporary buffer, translate all the elements to crush the empty spot at the source position and in the same free up a spot on the destination position. Put the backup value in the destination spot.

function moveElement ($a , $i , $j)
{
      $tmp =  $a[$i];
      if ($i > $j)
      {
           for ($k = $i; $k > $j; $k--) {
                $a[$k] = $a[$k-1]; 
           }        
      }
      else
      { 
           for ($k = $i; $k < $j; $k++) {
                $a[$k] = $a[$k+1];
           }
      }
      $a[$j] = $tmp;
      return $a;
}


$a = array(0, 1, 2, 3, 4, 5);
print_r($a);

$a = moveElement($a, 1, 4);
echo ('1 ->  4');
print_r($a);


$a = moveElement($a, 5, 0);
echo ('5 ->  0' );
print_r($a);

Output:

Array
(
    [0] => 0
    [1] => 1
    [2] => 2
    [3] => 3
    [4] => 4
    [5] => 5
)
1 ->  4Array
(
    [0] => 0
    [1] => 2
    [2] => 3
    [3] => 4
    [4] => 1
    [5] => 5
)
5 ->  0Array
(
    [0] => 5
    [1] => 0
    [2] => 2
    [3] => 3
    [4] => 4
    [5] => 1
)

You'll need to add some Exception handling to have a complete code.

Comments

0

A function that preserves keys:

function moveElementInArray($array, $toMove, $targetIndex) {
    if (is_int($toMove)) {
        $tmp = array_splice($array, $toMove, 1);
        array_splice($array, $targetIndex, 0, $tmp);
        $output = $array;
    }
    elseif (is_string($toMove)) {
        $indexToMove = array_search($toMove, array_keys($array));
        $itemToMove = $array[$toMove];
        array_splice($array, $indexToMove, 1);
        $i = 0;
        $output = Array();
        foreach($array as $key => $item) {
            if ($i == $targetIndex) {
                $output[$toMove] = $itemToMove;
            }
            $output[$key] = $item;
            $i++;
        }
    }
    return $output;
}

$arr1 = Array('a', 'b', 'c', 'd', 'e');
$arr2 = Array('A' => 'a', 'B' => 'b', 'C' => 'c', 'D' => 'd', 'E' => 'e');

print_r(moveElementInArray($arr1, 3, 1));
print_r(moveElementInArray($arr2, 'D', 1));

Ouput:

Array
(
    [0] => a
    [1] => d
    [2] => b
    [3] => c
    [4] => e
)
Array
(
    [A] => a
    [D] => d
    [B] => b
    [C] => c
    [E] => e
)

Comments

0

For those who don't like array_splice due to "array by reference" &, here an array_slice variant.
Object oriented and unit tested.

<?php declare(strict_types=1);

namespace App\API\Common\Sorter;

final class ArraySorter
{
    public function moveItem(int $fromIndex, int $toIndex, array $array): array
    {
        $movingItem = $array[$fromIndex];
        $slicingArray = $array;
        unset($slicingArray[$fromIndex]);

        $startingItems = array_slice($slicingArray, 0, $toIndex);
        $endingItems = array_slice($slicingArray, $toIndex);
        $splicedArray = array_merge($startingItems, [$movingItem], $endingItems);
        return $splicedArray;
    }
}

Unit tests

<?php declare(strict_types=1);

namespace App\API\Tests\Unit\Common;

use App\API\Common\Sorter\ArraySorter;
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\TestCase;

final class ArraySorterTest extends TestCase
{
    private ArraySorter $arraySorter;

    protected function setUp(): void
    {
        $this->arraySorter = new ArraySorter();
    }

    /** @dataProvider moveItemProvider */
    public function testMoveItem(int $fromIndex, int $toIndex, array $expectedArray): void
    {
        $array = ['a', 'b', 'c', 'd'];
        $result = $this->arraySorter->moveItem($fromIndex, $toIndex, $array);
        Assert::assertSame($expectedArray, $result);
    }

    public function moveItemProvider(): array
    {
        return [
            'move first forward' => ['fromIndex' => 0, 'toIndex' => 1, 'expectedArray' => ['b', 'a', 'c', 'd']],
            'move middle forward' => ['fromIndex' => 1, 'toIndex' => 2, 'expectedArray' => ['a', 'c', 'b', 'd']],
            'move middle back' => ['fromIndex' => 2, 'toIndex' => 1, 'expectedArray' => ['a', 'c', 'b', 'd']],
            'move last back' => ['fromIndex' => 3, 'toIndex' => 2, 'expectedArray' => ['a', 'b', 'd', 'c']],
        ];
    }
}

Comments

0

This answer will not resequence the outputted array as per the OP's specs. It will preserve the original keys/indexs.

So here is an answer that supports all array types which I've found none of the answers do:

  • numeric arrays ['A','B','C']
  • numeric assoc arrays [0 => 'A', 11 => 'B', 22 => 'C']
  • assoc arrays ['a' => 'A', 'b' => 'B', 'c' => 'C']

It's a bit longwinded and takes about twice the time of the accepted answer by hakre but it works across the board.

The issue for my use case is that the others depend on the index as opposed to the position of the element in the array.

    // Call it like so:
    $shiftedArray = ShiftItem($array, 4, 1);

    function ShiftItem(array $array, int $initialPosition, int $finalPosition)
    {
        // Validate args...

        if ($initialPosition === $finalPosition)
            return $array;

        $item = self::ItemFromPosition($array, $initialPosition);

        if ($initialPosition < $finalPosition) {

            $p1 = array_slice($array, 0, $initialPosition, true); // Grab the elements
            $p2 = array_slice($array, $initialPosition + 1, $finalPosition - $initialPosition, true);
            $p3 = array_slice($array, $finalPosition, null, true);

            return $p1 + $p2 + $item + $p3;

        } else if ($initialPosition > $finalPosition) {

            $p1 = array_slice($array, 0, $finalPosition, true); // Grab the elements
            $p2 = array_slice($array, $finalPosition, $initialPosition - $finalPosition, true);
            $p3 = array_slice($array, $initialPosition, null, true);

            return $p1 + $item + $p2 + $p3;

        }
    }

    function ItemFromPosition(array $array, int $position): array
    {
        // Validate args...

        $itemKey = $itemValue = null;
        $count = 0;
        foreach ($array as $key => $value) {

            if ($count === $position) {
                $itemKey = $key;
                $itemValue = $value;
                break;
            }

            $count++;
        }

        return [$itemKey => $itemValue];
    }

2 Comments

To be fair, you have extended your answer beyond the scope of the asked question. This asked question clearly states "index" -- not key, not associative key. Hakre's answer is appropriate. It seems this answer is probably more appropriate for a different question which has different requirements to respect and maintain associative keys. I also see that other answers have preserved original keys, so perhaps this page has already spiralled out of the initial scope.
You are quite right, @mickmackusa, this answer is off the mark. The op clearly asks for something that will "resequence the indexes so that there are no gaps in the sequence." which this answer does not do. Tough love is still love, so I'll take what I can get :)
-1

May be I'm wrong but shouldn't it be easier just to make a copy of the array and then replace the values?

function swap($input, $a, $b){
  $output = $input;
  $output[$a] = $input[$b];
  $output[$b] = $input[$a];
  return $output;
}

$array = ['a', 'c', 'b'];
$array = swap($array, 1, 2);

1 Comment

This way you only swap two elements But the task is to move
-1

Based on a previous answer. In case if you need to save key indexes of an associative array, which are could be any number or string:

function custom_splice(&$ar, $a, $b){
    $out = array_splice($ar, $a, 1);
    array_splice($ar, $b, 0, $out);
}

function moveElement(&$array, $a, $b) {
    $keys = array_keys($array);

    custom_splice($array, $a, $b);
    custom_splice($keys, $a, $b); 

    $array = array_combine($keys,$array);
}
$s = '{ 
"21": "b", 
"2": "2", 
"3": "3", 
"4": "4", 
"6": "5", 
"7": "6" 
}';
$e = json_decode($s,true);

moveElement($e, 2, 0); 

print_r($e);

Array
(
    [3] => 3
    [21] => b
    [2] => 2
    [4] => 4
    [6] => 5
    [7] => 6
)

Demo

Comments

-2

You need to create an auxiliary variable.

moveElement($a, $i,$j)
  {
  $k=$a[$i];
  $a[$i]=$a[$j];
  $a[$j]=$k;
  return $a;
  }

2 Comments

This is more like swapElements(), but not move
No: c will go to 3 and not 2 like in the example.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.