Insert elements at an array position even if the array is empty or position doesn't exist

1.1k views Asked by At

I tried to use the array_splice strategy as described here Insert new item in array on any position in PHP

But it doesn't work since the array is empty or the key doesn't exist. So I tried checking if the key is set first and then create it. But still it doesn't work.

If, for example, the array is empty in the first call and I want to insert elements at index 3, and I create position 3 before the array_splice, the elements are inserted from position 0. Same happens if I don't check before using array_splice. If the array is empty, the insert fails

function array_insert($array,$input,$index){
    if(!isset($array[$index])) $array[$index] = 0;
    array_splice($array,$index,0,$input);

    return $array;
}

So the following call

array_insert(array(),array(36,37),3);

Generates this

array(1) { [3]=> int(0) } //var_dump before splice, after isset
array(3) { [0]=> int(0) [1]=> string(2) "36" [2]=> string(2) "37" } //var_dump  after splice

What am I missing?!

@edit The expected result is: If I insert array('a','b','c') at position 3 in an empty array the resulting array should allow me to access 'a' by the key 3, 'b' by 4 etc. Not sure what is better, nulls to fill the gaps or associative keys.

@edit2

insert(array(),array(1,2),3);

array(2) { [3]=> int(1) [4]=> int(2) }

$a = array(); $a[2] = 3; insert($a,array(1,2),1);

array(3) { 1=> int(1) [2]=> int(2) [3] => int(3) }

insert(array(1,2),array(4,5),1);

array(4) { [0]=> int(1) 1=> int(4) [2] => int(5) [3] => int(2) }

In terms of performance, what is the better choice by the way?

3

There are 3 answers

0
grossvogel On

I think this satisfies your requirements, and I've included test cases so you can judge for yourself.

class ShiftingArray implements ArrayAccess
{
    private $values;

    public function __construct ($initial_values = array ())
    {
        $this->values = $initial_values;
    }

    public function get_values ()
    {
        return $this->values;
    }

    public function insert ($new_values, $offset)
    {
        if (!is_array ($new_values))
        {
            $new_values = array ($new_values);
        }

        foreach ($new_values as $value)
        {
            $this->insert_single ($offset, $value);
            $offset++;
        }
    }

    private function insert_single ($index, $value)
    {
        if (isset ($this->values[$index]))
        {
            $this->insert_single ($index + 1, $this->values[$index]);
        }
        $this->values[$index] = $value;
    }

    /**
    *   The following methods allow you to use an instance of ShiftingArray
    *   like a normal array, e.g.
    *
    *   $array = new ShiftingArray ();
    *   $array->insert (array (1,2,3), 4);
    *   echo $array[5]; //  prints 2
    */

    /*  boolean ArrayAccess::offsetExists (mixed $offset) */
    public function offsetExists ($offset)
    {
        return isset ($this->values [$offset]);
    }

    /*  mixed ArrayAccess::offsetGet (mixed $offset) */
    public function offsetGet ($offset)
    {
        return isset ($this->values [$offset]) ? $this->values[$offset] : null;
    }

    /*  ArrayAccess::offsetSet (mixed $offset, mixed $value) */
    public function offsetSet ($offset, $value)
    {
        $this->insert_single ($offset, $value);
    }

    /*  ArrayAccess::offsetUnset (mixed $offset) */
    public function offsetUnset ($offset)
    {
        unset ($this->values[$offset]);
    }
}

// begin test cases
$test_cases = array (
    array (
        'Name' => 'Start Empty, Zero Offset, Single Insert',
        'Initial' => array (),
        'Insert' => 6,
        'Offset' => 0,
        'Output' => array (0 => 6),
    ),
    array (
        'Name' => 'Start Empty, Zero Offset',
        'Initial' => array (),
        'Insert' => array (3, 2),
        'Offset' => 0,
        'Output' => array (0 => 3, 1 => 2),
    ),
    array (
        'Name' => 'Start Empty, Positive Offset, Single Insert',
        'Initial' => array (),
        'Insert' => 'hello',
        'Offset' => 11,
        'Output' => array (11 => 'hello'),
    ),
    array (
        'Name' => 'Start Empty, Positive Offset',
        'Initial' => array (),
        'Insert' => array (9, 'blah'),
        'Offset' => 3,
        'Output' => array (3 => 9, 4 => 'blah'),
    ),
    array (
        'Name' => 'No Shift',
        'Initial' => array (1 => 9),
        'Insert' => array (4, 'blah'),
        'Offset' => 3,
        'Output' => array (1 => 9, 3 => 4, 4 => 'blah'),
    ),
    array (
        'Name' => 'Single Shift',
        'Initial' => array (2 => 13),
        'Insert' => 6,
        'Offset' => 2,
        'Output' => array (2 => 6, 3 => 13),
    ),
    array (
        'Name' => 'Single Element, Double Shift',
        'Initial' => array (2 => 13),
        'Insert' => array (6, 7),
        'Offset' => 2,
        'Output' => array (2 => 6, 3 => 7, 4 => 13),
    ),
    array (
        'Name' => 'Multiple Element, Double Shift',
        'Initial' => array (5 => 13, 6 => 15),
        'Insert' => array (2, 3),
        'Offset' => 5,
        'Output' => array (5 => 2, 6 => 3, 7 => 13, 8 => 15),
    ),
    array (
        'Name' => 'Shift Only Some',
        'Initial' => array (2 => 1, 5 => 13, 6 => 15),
        'Insert' => array (2, 3),
        'Offset' => 5,
        'Output' => array (2 => 1, 5 => 2, 6 => 3, 7 => 13, 8 => 15),
    ),
    array (
        'Name' => 'Shift Fills Gaps',
        'Initial' => array (2 => 0, 3 => 11, 6 => 9, 7 => 'a'),
        'Insert' => array (12, 14),
        'Offset' => 4,
        'Output' => array (2 => 0, 3 => 11, 4 => 12, 5 => 14, 6 => 9, 7 => 'a'),
    ),
);

// run tests
$passes = $failures = 0;
foreach ($test_cases as $case)
{
    $array = new ShiftingArray ($case['Initial']);
    $array->insert ($case['Insert'], $case['Offset']);
    if ($array->get_values () != $case['Output'])
    {
        echo $case['Name'] . " FAILED\n";
        print_r ($array->get_values ());
        print_r ($case['Output']);
        echo "\n\n";
        $failures++;
    }
    else
    {
        $passes++;
    }
}
echo "\n\nTests Finished: $passes Passes, $failures Failures";
2
Victor Ferreira On

After some time me and a friend managed to make it work. I'm sure it will be useful for many people

function array_max_key($a){
  if(count($a)) return max(array_keys($a));
  return 0;
}

function array_insert($a,$b,$index){
  if(!is_array($b)) $b = array($b);

  $max = array_max_key($a);
  if($index > $max) $max = $index;
  $ab = array();
  $max++;

  for($i=0;$i<$max;$i++){
    if(isset($a[$i]) && $i<$index){
        $ab[$i] = $a[$i];
    }else if($i == $index){
        $_max = count($b);
        for($j=0;$j<$_max;$j++){
            $ab[$i+$j] = $b[$j];
        }
        if(isset($a[$i])) $ab[] = $a[$i];
    }else if(isset($a[$i])){
        if(isset($ab[$i])) $ab[] = $a[$i];
        else $ab[$i] = $a[$i];
    }
  }

  return $ab;
}

 array_insert(array(),array(1,2,3),4);
 $a = array(); $a[5] = 1; $a[6] = 2;
 array_insert($a,2,4);

So if you try to add elements to the I position of an array it will add them even if that position doesn't exist (or the array is empty), and will shift the other elements when it finds conflicts. Element can either be an array or not

0
splash58 On

You talked about splice so much, that i decided to not write my variant. But it is not the absolute :)

function array_insert($array, $add, $index) {
  // make array with desired keys
  $add = array_combine(range($index, $index + count($add)-1), $add);
  // split old array to two parts - (0 <= key < $index) and ($index <= key) 
  $after = $index ? array_diff_key($array, array_flip(range(0, $index-1))) : $array;
  $before = array_diff($array, $after);
  // Combine arrays
  $array = array_replace($add, $before);
  foreach($after as $item)
     $array[] = $item;
   return($array);    
}

print_r(array_insert(array(), array(a,b,c), 3));
// Array ( [3] => a [4] => b [5] => c )
print_r(array_insert(range(1,10), array(a,b,c), 3));
// Array ( [3] => a [4] => b [5] => c [0] => 1 [1] => 2 [2] => 3 [6] => 4 [7] => 5 [8] => 6 [9] => 7 [10] => 8 [11] => 9 [12] => 10 )
print_r(array_insert(range(1,2), array(a,b,c), 3));
// Array ( [3] => a [4] => b [5] => c [0] => 1 [1] => 2 )
print_r(array_insert(array(10=>10,3=>3), array(a,b,c), 3));
// Array ([3] => a [4] => b [5] => c [6] => 10 [7] => 3 )