3

Yes, this is a bit of a trick question; one array (without copies), as opposed to any odd array. Let me explain, so let's start here ;

$a = array ( 'one' => 1, 'two' => 2, 'three' => 3, 'four' => 4, 'five' => 5, 'six' => 6 ) ;

Pretend that this array is long, over a hundred long. I loop through it, step by step, but at some point (let's make up that this happens at the second item) something happens. Maybe the data is funky. Nevertheless, we need to add some items to it for later processing, and then keep looping through it, without losing the current position. Basically, I would like to do something like this ;

echo current ( $a ) ;  // 'two'
array_insert ( $a, 'four', 'new_item', 100 ) ;
echo current ( $a ) ;  // 'two'

Definition for array_insert is ( $array, $key_where_insert_happens, $new_key, $new_value ) ; Of course $new_key and $new_value should be wrapped in an array wrapper, but that's besides the point right now. Here's what I want to see happening after the above code having ran ;

print_r ( $a ) ; // array ( 'one' => 1, 'two' => 2, 'three' => 3, 'four' => 4, 'new_item' => 100, 'five' => 5, 'six' => 6 ) ;
echo current ( $a ) ;  // 'two'

Whenever you use array_splice, array_slice, array_push or most of the other array fiddling functions, you basically create a copy of the array, and then you can copy it back, but this breaks the reference to the original array and the position as well, and my loop above breaks. I could use direct reference (ie. $a['new_item'] = 'whatever;) or put it at the end, but none of these will insert items into a given position.

Any takers? How can I do a true insert into an associative array directly (that's being processed elsewhere)? My only solution so far is to ;

  1. record the position (current())
  2. Do the splice/insert (array_slice)
  3. overwrite old array with new ($old = $new)
  4. search the new position (first reset() then looping through to find it [!!!!!!])

Surely there's a better, simpler and more elegant way for doing something that's currently kludgy, heavy and poorly hobbled together? Why isn't there a array_set_position ( $key ) function that quickly can help this out, or an array_insert that works directly on the same array (or both)?

AlexanderJohannesen
  • 2,028
  • 2
  • 13
  • 26
  • 1
    Can you guarantee that the item you're adding is AFTER the current position? If not, then it won't get processed anyway. – Ed Marty May 09 '11 at 02:08
  • It sounds like you would be better off using [array_map](http://php.net/manual/en/function.array-map.php) – samshull May 09 '11 at 02:12
  • Ed, yes, an apt point. Mostly this is a stack I'm iterating over, and events early on the stack might add events to be run later. – AlexanderJohannesen May 09 '11 at 02:41
  • 1
    samshull, not sure how you see a usage for that? Array_map iterates and runs functions over those items, but does not alter the number of items in an array, does it? – AlexanderJohannesen May 09 '11 at 02:43
  • Well, I've gone through http://www.google.com/codesearch/p?hl=en#EKZaOgYQHwo/unstable/sources/php-5.1.4.tar.bz2%7COdM6HPk4UX4/php-5.1.4/ext/standard/array.c&q=array%20lang%3ac%20package%3aphp, and nothing jumps out, except some room for improvement with the Z_ARRVAL_PP implementation (near this comment in the source files; "/* * This is where the magic happens. */" :) Brilliant. – AlexanderJohannesen May 09 '11 at 04:51
  • Does this answer meet your requirements? http://stackoverflow.com/questions/3353745/how-to-insert-element-into-array-to-specific-position/7257599#7257599 – Tom Auger Aug 31 '11 at 13:31
  • @Tom Auger : Well, it does the splitting and inserting, but like all other solutions is a split and merge of arrays rather than a true insert. The problem is that when you've got thousands of these it becomes a resource and speed hog, and I've since looked into the PHP's C source code to see, and it seems I need to hack a more true insert (when I find time ... :) – AlexanderJohannesen Sep 01 '11 at 04:51

2 Answers2

3

Maybe I'm not understanding you correctly but have you looked into array_splice()?

This answer might also interest you.


Would something like this work?

function array_insert($input, $key, $value)
{
    if (($key = array_search($key, array_keys($input))) !== false)
    {
        return array_splice($input, $key, 1, $value);
    }

    return $input;
}


This was the best I could come up with:

$a = array
(
    'one' => 1,
    'two' => 2,
    'three' => 3,
    'four' => 4,
    'five' => 5,
    'six' => 6,
);

ph()->Dump(next($a)); // 2
array_insert($a, 'four', array('new_item' => 100));
ph()->Dump(current($a)); // 2

function array_insert(&$array, $key, $data)
{
    $k = key($array);

    if (array_key_exists($key, $array) === true)
    {
        $key = array_search($key, array_keys($array)) + 1;
        $array = array_slice($array, null, $key, true) + $data + array_slice($array, $key, null, true);

        while ($k != key($array))
        {
            next($array);
        }
    }
}

ph()->Dump($a);

/*
Array
(
    [one] => 1
    [two] => 2
    [three] => 3
    [four] => 4
    [new_item] => 100
    [five] => 5
    [six] => 6
)
*/

I don't think it's possible to set an array internal pointer without looping.

Community
  • 1
  • 1
Alix Axel
  • 151,645
  • 95
  • 393
  • 500
  • Hmm, well it doesn't keep (internal) positions of arrays intact, so I still need to reset and search/set it, no? I don't have a problem with the actual splicing, it's just that all splicings on offer destroys internal counters, forcing me to deal with a rather expensive manual operation. – AlexanderJohannesen May 09 '11 at 02:45
  • @AlexanderJohannesen: Yeah, I think I don't fully understand your question. Could you post some dummy data and the respective code / output? – Alix Axel May 09 '11 at 02:52
  • @Alix, yes, ok, I've updated the question. It's perhaps more about a better seek_and_set array position using keys question ... :) – AlexanderJohannesen May 09 '11 at 03:10
  • @AlexanderJohannesen: Got it. I don't see any way to do that but let me think about it for a while. – Alix Axel May 09 '11 at 03:15
  • @AlexanderJohannesen: I just realized that the code I came up with was exactly the algorithm you described... Anyway, this doesn't seem to be possible, because `array_set_position()`: http://pt.php.net/manual/en/function.next.php#52362, http://pt.php.net/manual/en/function.next.php#83087, http://pt.php.net/manual/en/function.current.php#83212 and so on... – Alix Axel May 09 '11 at 03:45
  • Thanks, Alix. It seems we agree on this. I may have to look into the PHP source files and see if there's an obvious enhancement to be made here. Of course, I could re-implement the whole thing as a linked object list and deal with this using internal indeces, but it seems a lot of work for what should be simple. Hmm. Thanks, btw. :) – AlexanderJohannesen May 09 '11 at 03:55
  • Hmm, I'm having a problem with your solution, in that items added don't seem to work (silent fail) if the key is set (ie. array ( 'somekey' => $something ) ). If there is no key (ie. array ( $something ) ) it works. Must be something that happens when you try to add the arrays together (ie. $before + $data + $after). Hmm. – AlexanderJohannesen May 09 '11 at 07:09
  • Forget that last part, problem somewhere else. Good stuff. :) – AlexanderJohannesen May 09 '11 at 09:53
2

Rather than using the associative array as the event queue, keep a separate integer-indexed array. Loop over an explicit index variable rather than using the internal array position.

$events = array_keys($a);
for ($i=0; $i < count($events); ++$i) {
    ...
    /* if there's an event to add after $j */
    array_splice($events, $j+1, 0, array($new_event_key));
    $a[$new_event_key] = $new_event_data;
    ...
}

To keep things more cohesive, you can package the two arrays into an event queue class.

outis
  • 75,655
  • 22
  • 151
  • 221
  • Yes, that's one way, although my app rely on the keys for other things. Perhaps it needs to be split, leaving more serializing work to be done, but maybe there is no better way? Hmm. – AlexanderJohannesen May 09 '11 at 04:02
  • @Alexander: if a built-in data structure doesn't meet your needs, your best bet is to implement your own. With an event queue class, you can implement the [`Iterator`](http://php.net/Iterator) and [`ArrayAccess`](http://php.net/ArrayAccess) interfaces. It will function as an array, but will give you greater control over positioning. You can even add methods to get and set the position (possibly using [overloading](http://php.net/__get)). – outis May 10 '11 at 08:04
  • yes, of course, and I'm starting to lean that way. First I'll see how expensive the insert/seek operation on an array is, and if proven bad I'll do exactly that. :) It just seemed like a hashmap with internal pointer would have at least a way to set the pointer by key, but I may have to go all OO on 'im to make it snappy enough. – AlexanderJohannesen May 10 '11 at 20:55