-1

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?

Community
  • 1
  • 1
Victor Ferreira
  • 6,151
  • 13
  • 64
  • 120
  • `array_splice` works with offsets, not keys. What result do you expect? – deceze Jun 09 '15 at 14:38
  • 3
    we need an example. what's the expected result? I rarely trust descriptions alone... they are so easily misunderstood. – Karoly Horvath Jun 09 '15 at 14:40
  • i wanted the rest of the array to have null values or simply nothing. like an associative array or an array with gaps – Victor Ferreira Jun 09 '15 at 14:42
  • 1
    *censored*. No(t just a) description please. Result. You know, something that gives me a chance to verify that my understanding of the description is correct. – Karoly Horvath Jun 09 '15 at 14:42
  • PHP arrays are sparse, meaning they're not automatically filled with `null` values or such up to the largest numeric key. If that's what you want, you'll have to fill your array with `null`s yourself. – deceze Jun 09 '15 at 14:44
  • 1
    Do you really need the gaps? Since **all** PHP arrays are actually maps / dictionaries, you can easily work with `$array[3]` without `$array[0]`, `$array[1]` or `$array[2]` having to be set. – grossvogel Jun 09 '15 at 14:45
  • as I said, not necessarily with nulls. it's not important as long as I can access the value by the giving keys. if I do something $a[3] = 1, it will generate an array with only 1 filled position, the position 3. this is exactly what I expect, but I need a function to insert it at any position, shifting elements when the key exists, creating then if they don't – Victor Ferreira Jun 09 '15 at 14:48
  • @VictorFerreira: OK, it's getting clearer based on your edits. In your example, what if `$array[4]` is already set? Does that value get overwritten by `b` or pushed backward? – grossvogel Jun 09 '15 at 14:54
  • 1
    There is no existing function in PHP which behaves as you describe, which is *inserting keys while pushing other keys forward on conflict without outright renumbering items.* You'll need to write your own implementation which does all that. – deceze Jun 09 '15 at 15:03
  • @grossvogel if [4] exists and you insert it at position 4, this element and all the others in front of it are pushed 1 position forward, like in array_splice – Victor Ferreira Jun 09 '15 at 15:05
  • 1
    I think you're having a hard time because your requirements are very strange, which may mean you're modeling your problem inefficiently. If you really need something that behaves this way, I'd suggest writing a custom data structure (a class that hides the details from client code) along with tests to verify the different cases. You're going to have to manipulate indices manually in code and/or use something other than a simple array under the hood. – grossvogel Jun 09 '15 at 18:13

3 Answers3

2

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";
grossvogel
  • 6,694
  • 1
  • 25
  • 36
1

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

Victor Ferreira
  • 6,151
  • 13
  • 64
  • 120
  • I don't think this really meets your requirements in terms of shifting existing values to make way for the new ones. – grossvogel Jun 10 '15 at 00:22
  • When I run `array_insert (array (1,2), array (3,4), 1)` I get `array ([0] => 1, [1] => 3, [2] => 4)`. The 2 got overwritten and not pushed toward the back of the array. – grossvogel Jun 10 '15 at 04:36
0

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 )
splash58
  • 26,043
  • 3
  • 22
  • 34