array_splice preserving keys
Asked Answered
D

3

12

I faced the situation that splicing arrays with preserved-keys, so I made the following function.
I reached the solution that wrapping each items with array, but there seems to be some memory-inefficient statements.
Have you any ideas?
Thank you.


array_splice_pk

This preserves keys, differently from array_splice.

Overview:

  • &$input -> same as array_splice one.
  • $key -> target key.
  • $use_key_as_offset -> use $key parameter as a numeric offset.
  • $length -> same as array_splice one.
  • $replacement -> same as array_splice one. But you can also provide key for each value.

Code:

function array_splice_pk(&$input, $key, $use_key_as_offset = false, $length = 0, $replacement = null) {
    if (!is_array($input) || !is_scalar($key)) {
        return array();
    }
    if ($replacement !== null) {
        $replacement = array($replacement);
        if (!is_array($replacement[0])) {
            $replacement = array($replacement);
        }
    }
    $wrapper = array();
    foreach ($input as $k => $v) {
        $wrapper[] = array($k => $v);
    }
    $del_key = null;
    foreach ($wrapper as $k => $v) {
        if ($use_key_as_offset) {
            if ($k === (int)$key) {
                $del_key = $k;
                break;
            }
        } else {
            if (key($v) == $key) {
                $del_key = $k;
                break;
            }
        }
    }
    if ($del_key === null) {
        return array();
    }
    if ($replacement === null) {
        $wrapper_ret = array_splice($wrapper, $del_key, $length);
    } else {
        $wrapper_ret = array_splice($wrapper, $del_key, $length , $replacement);
    }
    $ret = $input = array();
    foreach ($wrapper_ret as $wrap) {
        list($k, $v) = each($wrap);
        $ret[$k] = $v;
    }
    foreach ($wrapper as $wrap) {
        list($k ,$v) = each($wrap);
        $input[$k] = $v;
    }
    return $ret;
}

Sample:

$arr1 = $arr2 = array(
    'one'   => 'test',
    'two'   => 'test',
    'three' => 'test',
    'four'  => 'test',
);
$ret1 = array_splice_pk($arr1, 'three', false, 1, array('fizz' => '!!!'));
$ret2 = array_splice_pk($arr2, 2      , true , 1, array('fizz' => '!!!'));

var_dump('Result1', $arr1, $ret1, 'Result2', $arr2, $ret2);

Result:

string(7) "Result1"
array(4) {
  ["one"]=>
  string(4) "test"
  ["two"]=>
  string(4) "test"
  ["fizz"]=>
  string(3) "!!!"
  ["four"]=>
  string(4) "test"
}
array(1) {
  ["three"]=>
  string(4) "test"
}
string(7) "Result2"
array(4) {
  ["one"]=>
  string(4) "test"
  ["two"]=>
  string(4) "test"
  ["fizz"]=>
  string(3) "!!!"
  ["four"]=>
  string(4) "test"
}
array(1) {
  ["three"]=>
  string(4) "test"
}
Donough answered 16/5, 2013 at 10:52 Comment(3)
array_splice does preserve string keys. You're only using string keys. What's wrong with array_splice?Humperdinck
@Humperdinck NO. ideone.com/TuMVcLDonough
Oh, my bad. The replacement array keys are indeed not preserved. Though it occurs to me that if you're using associative arrays, you can overwrite or insert specific keys using $replacement + $original. If you want to remove keys as well, this should better be done in a separate step. Replacing string keys with other string keys by numeric position seems like a weird operation.Humperdinck
D
2

I'll post the self-answer with my PHP 8 knowledge in 2022.
It correctly accepts negative length/offset and string offset.

function array_splice_assoc(array &$input, int|string $key, ?int $length = null, $replacement = [], bool $use_int_key_as_offset = true): array
{
    // Normalize key/offset
    $offset = match (true) {
        is_string($key) || !$use_int_key_as_offset => array_flip(array_keys($input))[$key] ?? throw new OutOfBoundsException(),
        $key < 0 => count($input) + $key,
        default => $key,
    };

    // Normalize length
    $length = match (true) {
        $length === null => count($input) - $offset,
        $length < 0 => count($input) + $length - $offset,
        default => $length,
    };

    // Manipulate each part
    $before = array_slice($input, 0, $offset, true);
    $removed = array_slice($input, $offset, $length, true);
    $after = array_slice($input, $offset + $length, null, true);

    // Merge parts, allowing the latter overrides the former
    $input = array_replace($before, (array)$replacement, $after);

    return $removed;
}

Examples:

$array = ['a' => 'A', 'b' => 'B', 3 => 'C', 4 => 'D'];

$original = $array;
$removed = array_splice_assoc($original, 1, 1, [5 => 'E']);
echo json_encode(compact('original', 'removed')) . PHP_EOL;
/*
{"original":{"a":"A","5":"E","3":"C","4":"D"},"removed":{"b":"B"}}
*/

$original = $array;
$removed = array_splice_assoc($original, 2, replacement: [5 => 'E']);
echo json_encode(compact('original', 'removed')) . PHP_EOL;
/*
{"original":{"a":"A","b":"B","5":"E"},"removed":{"3":"C","4":"D"}}
*/

$original = $array;
$removed = array_splice_assoc($original, -3, 1, [5 => 'E']);
echo json_encode(compact('original', 'removed')) . PHP_EOL;
/*
{"original":{"a":"A","5":"E","3":"C","4":"D"},"removed":{"b":"B"}}
*/

$original = $array;
$removed = array_splice_assoc($original, -3, -1, [5 => 'E']);
echo json_encode(compact('original', 'removed')) . PHP_EOL;
/*
{"original":{"a":"A","5":"E","4":"D"},"removed":{"b":"B","3":"C"}}
*/

$original = $array;
$removed = array_splice_assoc($original, 'b', 2, [5 => 'E']);
echo json_encode(compact('original', 'removed')) . PHP_EOL;
/*
{"original":{"a":"A","5":"E","4":"D"},"removed":{"b":"B","3":"C"}}
*/

$original = $array;
$removed = array_splice_assoc($original, 3, 1, [5 => 'E']);
echo json_encode(compact('original', 'removed')) . PHP_EOL;
/*
{"original":{"a":"A","b":"B","3":"C","5":"E"},"removed":{"4":"D"}}
*/

$original = $array;
$removed = array_splice_assoc($original, 3, 1, [5 => 'E'], false);
echo json_encode(compact('original', 'removed')) . PHP_EOL;
/*
{"original":{"a":"A","b":"B","5":"E","4":"D"},"removed":{"3":"C"}}
*/
Donough answered 4/1, 2022 at 10:1 Comment(0)
U
9

I found this on the manual for array_slice.

<?php
function array_splice_assoc(&$input, $offset, $length, $replacement = array()) {
    $replacement = (array) $replacement;
    $key_indices = array_flip(array_keys($input));
    if (isset($input[$offset]) && is_string($offset)) {
            $offset = $key_indices[$offset];
    }
    if (isset($input[$length]) && is_string($length)) {
            $length = $key_indices[$length] - $offset;
    }

    $input = array_slice($input, 0, $offset, TRUE)
            + $replacement
            + array_slice($input, $offset + $length, NULL, TRUE); 
}

 $fruit = array(
    'orange' => 'orange',
    'lemon' => 'yellow',
    'lime' => 'green',
    'grape' => 'purple',
    'cherry' => 'red',
 );

  // Replace lemon and lime with apple
  array_splice_assoc($fruit, 'lemon', 'grape', array('apple' => 'red'));

  // Replace cherry with strawberry
  array_splice_assoc($fruit, 'cherry', 1, array('strawberry' => 'red'));
  ?>

It appears more space and time efficient while it preserves the keys.

Udella answered 16/5, 2013 at 15:25 Comment(2)
Nice function! But the first example array_splice_assoc($fruit, 'lemon', 'grape', array('apple' => 'red')); seems to work oddly. What does 'grape' means?Donough
In the context it can be seen as 'till'. It calculates the length from the position of the 'grape'.Udella
N
4

Here's a function that preserves keys of the replacement array and has the same argument list as the original array_splice function:

<?php

function array_splice_preserve_keys(&$input, $offset, $length=null, $replacement=array()) {
    if (empty($replacement)) {
        return array_splice($input, $offset, $length);
    }

    $part_before  = array_slice($input, 0, $offset, $preserve_keys=true);
    $part_removed = array_slice($input, $offset, $length, $preserve_keys=true);
    $part_after   = array_slice($input, $offset+$length, null, $preserve_keys=true);

    $input = $part_before + $replacement + $part_after;

    return $part_removed;
}

// use as normal
array_splice_preserve_keys($input, $offset, $length, $replacement);

See it published at https://github.com/lode/gaps.

Nosewheel answered 4/2, 2015 at 21:8 Comment(1)
array_splice does not seem to preserve numeric keys for $input thoughPreoccupied
D
2

I'll post the self-answer with my PHP 8 knowledge in 2022.
It correctly accepts negative length/offset and string offset.

function array_splice_assoc(array &$input, int|string $key, ?int $length = null, $replacement = [], bool $use_int_key_as_offset = true): array
{
    // Normalize key/offset
    $offset = match (true) {
        is_string($key) || !$use_int_key_as_offset => array_flip(array_keys($input))[$key] ?? throw new OutOfBoundsException(),
        $key < 0 => count($input) + $key,
        default => $key,
    };

    // Normalize length
    $length = match (true) {
        $length === null => count($input) - $offset,
        $length < 0 => count($input) + $length - $offset,
        default => $length,
    };

    // Manipulate each part
    $before = array_slice($input, 0, $offset, true);
    $removed = array_slice($input, $offset, $length, true);
    $after = array_slice($input, $offset + $length, null, true);

    // Merge parts, allowing the latter overrides the former
    $input = array_replace($before, (array)$replacement, $after);

    return $removed;
}

Examples:

$array = ['a' => 'A', 'b' => 'B', 3 => 'C', 4 => 'D'];

$original = $array;
$removed = array_splice_assoc($original, 1, 1, [5 => 'E']);
echo json_encode(compact('original', 'removed')) . PHP_EOL;
/*
{"original":{"a":"A","5":"E","3":"C","4":"D"},"removed":{"b":"B"}}
*/

$original = $array;
$removed = array_splice_assoc($original, 2, replacement: [5 => 'E']);
echo json_encode(compact('original', 'removed')) . PHP_EOL;
/*
{"original":{"a":"A","b":"B","5":"E"},"removed":{"3":"C","4":"D"}}
*/

$original = $array;
$removed = array_splice_assoc($original, -3, 1, [5 => 'E']);
echo json_encode(compact('original', 'removed')) . PHP_EOL;
/*
{"original":{"a":"A","5":"E","3":"C","4":"D"},"removed":{"b":"B"}}
*/

$original = $array;
$removed = array_splice_assoc($original, -3, -1, [5 => 'E']);
echo json_encode(compact('original', 'removed')) . PHP_EOL;
/*
{"original":{"a":"A","5":"E","4":"D"},"removed":{"b":"B","3":"C"}}
*/

$original = $array;
$removed = array_splice_assoc($original, 'b', 2, [5 => 'E']);
echo json_encode(compact('original', 'removed')) . PHP_EOL;
/*
{"original":{"a":"A","5":"E","4":"D"},"removed":{"b":"B","3":"C"}}
*/

$original = $array;
$removed = array_splice_assoc($original, 3, 1, [5 => 'E']);
echo json_encode(compact('original', 'removed')) . PHP_EOL;
/*
{"original":{"a":"A","b":"B","3":"C","5":"E"},"removed":{"4":"D"}}
*/

$original = $array;
$removed = array_splice_assoc($original, 3, 1, [5 => 'E'], false);
echo json_encode(compact('original', 'removed')) . PHP_EOL;
/*
{"original":{"a":"A","b":"B","5":"E","4":"D"},"removed":{"3":"C"}}
*/
Donough answered 4/1, 2022 at 10:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.