views:

195

answers:

3

I'm trying to do something but I can't find any solution, I'm also having some trouble putting it into works so here is a sample code, maybe it'll be enough to demonstrate what I'm aiming for:

$input = array
(
    'who' => 'me',
    'what' => 'car',
    'more' => 'car',
    'when' => 'today',
);

Now, I want to use array_splice() to remove (and return) one element from the array:

$spliced = key(array_splice($input, 2, 1)); // I'm only interested in the key...

The above will remove and return 1 element (third argument) from $input (first argument), at offset 2 (second argument), so $spliced will hold the value more.

I'll be iterating over $input with a foreach loop, I know the key to be spliced but the problem is I don't know its numerical offset and since array_splice only accepts integers I don't know what to do.

A very dull example:

$result = array();

foreach ($input as $key => $value)
{
    if ($key == 'more')
    {
        // Remove the index "more" from $input and add it to $result.
        $result[] = key(array_splice($input, 2 /* How do I know its 2? */, 1));
    }
}

I first though of using array_search() but it's pointless since it'll return the associative index....

How do I determine the numerical offset of a associative index?

+2  A: 

Just grabbing and unsetting the value is a much better approach (and likely faster too), but anyway, you can just count along

$result = array();
$idx = 0; // init offset
foreach ($input as $key => $value)
{
    if ($key == 'more')
    {
        // Remove the index "more" from $input and add it to $result.
        $result[] = key(array_splice($input, $idx, 1));
    }
    $idx++; // count offset
}
print_R($result);
print_R($input);

gives

Array
(
    [0] => more
)
Array
(
    [who] => me
    [what] => car
    [when] => today
)

BUT Technically speaking an associative key has no numerical index. If the input array was

$input = array
(
    'who' => 'me',
    'what' => 'car',
    'more' => 'car',
    'when' => 'today',
    'foo', 'bar', 'baz'
);

then index 2 is "baz". But since array_slice accepts an offset, which is not the same as a numeric key, it uses the element found at that position in the array (in order the elements appear), which is why counting along works.

On a sidenote, with numeric keys in the array, you'd get funny results, because you are testing for equality instead of identity. Make it $key === 'more' instead to prevent 'more' getting typecasted. Since associative keys are unique you could also return after 'more' was found, because checking subsequent keys is pointless. But really:

if(array_key_exists('more', $input)) unset($input['more']);
Gordon
I was hoping for a solution that didn't used counter variables but indeed, very weird results... An associate array might not have a numerical index, but it has a numerical offset - the order by which the elements were defined?
Alix Axel
@Alix more like the order they appear. Since you can sort the array, this is not necessarily the order in which they were defined.
Gordon
@Gordon: Indeed, see my answer bellow. =)
Alix Axel
**Clearification** The above isn't phrased too well. What I wanted to say is, an array key is not the same as it's offset. The offset refers to an element at that position in the array, but that element could have any key (assoc or numeric). Array keys do not denote position. Even numeric array keys can be reordered to appear in random order, so offset 2 could refer to an element with key 12. I'd add this clearification to the answer above, but then it would turn into a CW (which is the dumbest "feature" ever).
Gordon
+1  A: 
$i = 0;
foreach ($input as $key => $value)
{
    if ($key == 'more')
    {
        // Remove the index "more" from $input and add it to $result.
        $result[] = key(array_splice($input, $i , 1));

    }
    $i++;
}
Markus
+2  A: 

I found the solution:

$offset = array_search('more', array_keys($input)); // 2

Even with "funny" arrays, such as:

$input = array
(
    'who' => 'me',
    'what' => 'car',
    'more' => 'car',
    'when' => 'today',
    'foo', 'bar', 'baz'
);

This:

echo '<pre>';
print_r(array_keys($input));
echo '</pre>';

Correctly outputs this:

Array
(
    [0] => who
    [1] => what
    [2] => more
    [3] => when
    [4] => 0
    [5] => 1
    [6] => 2
)

It's a trivial solution but somewhat obscure to get there.

I appreciate all the help from everyone. =)

Alix Axel
if you intend to use that as `$result = key(array_splice($input, array_search('more', array_keys($input)), 1));` do yourself a favor and benchmark it vs `array_key_exists` plus `unset` ;)
Gordon
Note that `array_keys` and `array_search` are O(n) while `$array['more']` and `unset($array['more'])` are both just O(1).
Gumbo
@Gumbo: I know that using unset is way faster, how do you reach to the 1 and n in Big-O notation? I'd like to learn that!
Alix Axel
@Alix Axel: `array_keys` creates an array of all keys; so it has to iterate all *n* keys to create that array, thus O(*n*). `array_search` is iterating the whole array until it finds the needle; in the worst case the needle is not in the array, thus O(*n*). In contrast to that, array access is always constant (even in PHP where arrays are implemented using a hash table), thus O(1).
Gumbo
@Gumbo: Thanks, is it possible to calculate O(*x*) automatically? @Gordon: I've made some realistic benchmarks, using `unset()` is about 10x faster than my approach, I'm going with unset() but I was just curious how the same could be done if I wasn't iterating the array. =)
Alix Axel
@Alix Axel: You can establish the limiting behavior by just counting the number of operations an algorithm takes for an input of the length *n*.
Gumbo
@Gumbo: Thanks again! =)
Alix Axel