32
votes
\$\begingroup\$

I found that in PHP (or I probably can't find it) a proper is_numeric_array($array) function is missing. So I created one. The problem is that I don't think it's great and I don't know how to improve it.

Any suggestion?

My first function

function is_numeric_array($array)
{
    $i = 0;
    foreach ($array as $a => $b) { if (is_int($a)) { ++$i; } }
    if (count($array) === $i) { return true; }
    else { return false; }
}

is_numeric_array(array(0,0,0,0,0)); // true
is_numeric_array(array('str' => 1, 'str2' => 2, 'str3' => 3)); // false

Example

As asked, I provide an example on how this could be any useful.

function is_numeric_array($array)
{
    # Code below
}

function someFunction($array)
{
    if (is_numeric_array($array))
    {
        $query = $array[0];
        $param = $array[1];
        $fetch = $array[2];
    }
    else
    {
        $query = $array['query'];
        $param = $array['param'];
        $fetch = $array['fetch'];
    }

    # Do your sql/pdo stuff here
}

# This use is the same of ...
someFunction(array(
    'PDO SQL STATEMENT', 
    array('param1' => 1, 'param2' => 2, 'param3' => 3),
    true
));

# ... this one.
someFunction(array(
    'query' => 'PDO SQL STATEMENT',
    'param' => array('param1' => 1, 'param2' => 2, 'param3' => 3),
    'fetch' => true
));

# To choose one form instead of the other is coder's decision
# Also I know it is useless but I was just wondering why anybody actually looked forward this function
\$\endgroup\$
  • \$\begingroup\$ Im just curious, can you provide a use case for this? \$\endgroup\$ – MANCHUCK Jan 27 '11 at 15:46
  • \$\begingroup\$ Done, hope you like it. \$\endgroup\$ – Shoe Jan 27 '11 at 18:10
  • \$\begingroup\$ They're all bad =) This is the only right one: gist.github.com/1272230 \$\endgroup\$ – Rudie Oct 8 '11 at 12:14
  • \$\begingroup\$ The more I read the more confused I became; You seem to determine if an array is associative or not, Whereas I read this topic as "determine if all array values are numeric/integers". A Straightforward approach to an is_assoc check is exemplified here: stackoverflow.com/a/173479/1695680 \$\endgroup\$ – ThorSummoner Jul 24 '14 at 16:25
  • \$\begingroup\$ Apologies, after further consideration the linked answer is not so reliable; You'd actually want to modify the next answer: stackoverflow.com/a/4254008/1695680 to use 'is_int' instead of 'is_string' \$\endgroup\$ – ThorSummoner Jul 24 '14 at 17:01
45
votes
\$\begingroup\$

Instead of using a counter to count the keys for which the condition is true, you could just return false as soon as you find a key which is not an int and return true if the loop reaches its end without finding such a key:

foreach ($array as $a => $b) {
    if (!is_int($a)) {
        return false;
    }
}
return true;

This has the benefit that it short-circuits (i.e. it stops iterating once it finds a key that is not an int) and gets rid of the counter.

\$\endgroup\$
  • \$\begingroup\$ How about array_fill(3, 5, 0)? Do you consider that a numeric array? \$\endgroup\$ – Rudie Oct 8 '11 at 12:14
  • 2
    \$\begingroup\$ Instead of using is_int($a) use ($a !== (int) $a) and it will be 10x faster. \$\endgroup\$ – bucabay Nov 1 '14 at 19:43
14
votes
\$\begingroup\$

Improving a little on @sepp2k answer(+1) (removing the "unused variable warning" some tools will spit out):

foreach (array_keys($array) as $a)) {
    if (!is_int($a)) {
        return false;
    }
}
return true;

If you want to check if it's an linear array:

return array_merge($array) === $array;

or @RobertPitt's solution :) (Also +1 there)

But my main point:

Why do you need this, i've never had use for something like this and it might be that the solution is change and API design flaw or data structure flaw somewhere ? Doesn't have too, but might.

Response to OPs comment:

I'm building a db method for queries that needs of a 3 keys array. In case the array is Numeric the order must be: statement, parameters, fetch. In case it's not, so the coder is specifying the key string, the order can be different and two out of three required parameters could be empty.

Ok then let my try to do that specif to your requirements :)

my_db_function(
    array("myquery", array("params", "..."), "myfetchmode")
);
// or 
my_db_function(
    array("params" => "myquery", "fetchmode" => "myfetchmode, "params" => array("myparams", "...) )
); 

Maybe i misunderstood a little but it should get the point across how one could build that with a different check :)

function my_db_fuction($statement) {

    if(isset($statement["query"])) { // Assoc 'Mode'
        $query = $statement["query"];
        if(isset($statement["params"])) { $params = $statement["params"]; } 
        else { $params = array(); }
        if(isset($statement["fetchmode"])) { $fetchmode = $statement["fetchmode"]; }
        else { $fetchmode = "select"; // optional param, default value here

    } else if(isset($statement[0]) { // Linear Array Mode

        $query = $statement[0];
        if(isset($statement[1])) { $params = $statement[1]; } 
        else { $params = array(); }
        if(isset($statement[2])) { $fetchmode = $statement[2]; }
        else { $fetchmode = "select"; // optional param, default value here

    } else {
         // Error, misformed format
    }
    // Now we got our 3 variables :)
}

That is still a pretty big code block but i didn't want to shorten it and risk worse readabilty.

What i would do with that is create a my_db_stuff_param_helper($aParams) that will always return an array containing the 3 keys filled with the real values (or defaults if nothing was passed)

  function my_db_fuction($statement) {
      $statement = my_db_parse_params($statement);
      // now e.g. $statement["query"] will always be there
  }

something along those lines "feels" (subjective, i know) better than building a generic function to do the key checking. (I guess it's isset($statement["query"]) instead of is_numeric_array what i boilds down to :)

\$\endgroup\$
  • \$\begingroup\$ I need of it because i'm creating a method that, based on how it is written, must behave different ways. \$\endgroup\$ – Shoe Jan 25 '11 at 18:29
  • 1
    \$\begingroup\$ You are doing something that forces you to do something and because of that you need this method. I'm saying maybe if you do something different you don't need that. Sry i got no idea how i could make myself clearer but i read your answers as "I need this because i need this" and i'm just suggesting that maybe you don't :) And since this is codereview and not SO i though i mention it and don't just provide a code snippet with the (for me at least) obvious answer :) \$\endgroup\$ – edorian Jan 25 '11 at 20:26
  • \$\begingroup\$ I got what you are saying. I'm building a db method for queries that needs of a 3 keys array. In case the array is Numeric the order must be: statement, parameters, fetch. In case it's not, so the coder is specifying the key string, the order can be different and two out of three required parameters could be empty. I know it's strange and I'm going to edit it, but I was just wondering if a proper function doesn't exist. \$\endgroup\$ – Shoe Jan 26 '11 at 7:37
  • \$\begingroup\$ Edited my answer in response, might be a bit overblown :) \$\endgroup\$ – edorian Jan 26 '11 at 8:33
  • 1
    \$\begingroup\$ Didn't thought about this method. Thanks for providing it. But, let's face it, PHP has a lot of useless functions, why don't put is_numeric_array() into the core? <sarcasm> Look at that eval() function, isn't my is_numeric_array() function way more helpfull? </sarcasm> \$\endgroup\$ – Shoe Jan 27 '11 at 18:16
6
votes
\$\begingroup\$

At first I didn't realise you wanted to check see if the keys are integers. If the reason you're doing this is to make sure the keys are 0,1,2,3,4.. etc as an indexed array and not an associative array, then you could use this:

function is_numeric_array($arr)
{
    return array_keys($arr) === range(0,(count($arr)-1));
}

/*
    * Tests
*/
var_dump(is_numeric_array(array('a', 'b', 'c'))); // true
var_dump(is_numeric_array(array("0" => 'a', "1" => 'b', "2" => 'c'))); // true
var_dump(is_numeric_array(array("1" => 'a', "0" => 'b', "2" => 'c'))); // false
var_dump(is_numeric_array(array("a" => 'a', "b" => 'b', "c" => 'c'))); // false

Benchmark results as requested:

Here's the code used to test the execution:

$array = array();
for($i=0;$i<=500000;$i++)
{
    $array[] = "some_string";
}

$m_initial = memory_get_usage();
is_numeric_array($array);
$increase = memory_get_usage() - $m_initial;

As you can see from the above, I tested with a linear array that had 500K strings:

The value of $increase showed 65032 (which is in bytes). If we converted to KB this is around 64 rounded up. The result in KB shows 63.507, which in my opinion is ok.

\$\endgroup\$
  • \$\begingroup\$ It actually checks only values, not keys. Am i wrong? \$\endgroup\$ – Shoe Jan 25 '11 at 18:11
  • \$\begingroup\$ added the $key to that. \$\endgroup\$ – RobertPitt Jan 25 '11 at 18:13
  • \$\begingroup\$ array_filter's callback only takes array values as an argument. You'd need to fetch array_keys first, and filter that. \$\endgroup\$ – Mchl Jan 25 '11 at 18:18
  • \$\begingroup\$ yea is scrapped it, I was a little in-consistence and should of done my research. \$\endgroup\$ – RobertPitt Jan 25 '11 at 18:22
  • \$\begingroup\$ Well, i'd like that the second example returns false since you, as coder, defined a specific key. \$\endgroup\$ – Shoe Jan 25 '11 at 18:31
5
votes
\$\begingroup\$

Ok, here's a shot for consistency and proper naming conventions (Since numeric and int mean different things all together, there's little point calling one the other...):

function isNumericArray(array $array) {
    foreach ($array as $a => $b) {
        if (!is_numeric($a)) {
            return false;
        }
    }
    return true;
}

function isIntArray(array $array) {
    foreach ($array as $a => $b) {
        if (!is_int($a)) {
            return false;
        }
    }
    return true;
}

Now, for a more OO approach, I'd build a filteriterator:

class NumericFilterIterator extends FilterIterator {
    public function accept() {
        return is_numeric(parent::current());
    }
}

class IntFilterIterator extends FilterIterator {
    public function accept() {
        return is_int(parent::current());
    }
}

Then, if you just want to iterate over the integer, just do $it = new IntFilterIterator(new ArrayIterator($array));. If you want to verify, you can do:

$it = new IntFilterIterator(new ArrayIterator($array));
if ($array != iterator_to_array($it)) {
    //Has a non-int element
}
\$\endgroup\$
  • \$\begingroup\$ +1 for the difference and the FilterIterator. Only thing that could be improved is to only check the array values. No need to run over the whole array. \$\endgroup\$ – kaiser Mar 24 '14 at 1:33
5
votes
\$\begingroup\$

This will drop out as soon as an element is found that is not an int, making the function more efficient for large arrays.

function is_numeric_array($array) {
   foreach ($array as $a=>$b) {
      if (!is_int($a)) {
         return false;
      }
   }
   return true;
}
\$\endgroup\$
  • 1
    \$\begingroup\$ That doesn't do the same thing as his code does it? Unless I'm missing something this checks whether the values are ints, not the keys. Also the code will cause an error if the keys aren't ints. \$\endgroup\$ – sepp2k Jan 25 '11 at 16:42
  • 1
    \$\begingroup\$ Also it's return $i instead of return $1. \$\endgroup\$ – Shoe Jan 25 '11 at 16:53
  • \$\begingroup\$ Also, I'm not sure how count works, but it likely rewalks the array, causing you to go over it twice. \$\endgroup\$ – Mark Loeser Jan 25 '11 at 17:08
  • \$\begingroup\$ ...also no stop condition on while loop. Changed. \$\endgroup\$ – Michael K Jan 25 '11 at 17:27
  • 1
    \$\begingroup\$ I would also try to krsort($array,SORT_STRING); first. This should move most non-numeric keys to the begining of the array. One would need to benchmark if it gives any increase in performance. \$\endgroup\$ – Mchl Jan 25 '11 at 18:03
3
votes
\$\begingroup\$

This would probably be way more efficient:

function is_numeric_array($array)
{
    $array = array_keys($array);

    if ($array === array_filter($array, 'is_int'))
    {
        return true;
    }

    return false;
}

I just noticed what you're really trying to do is not check if all the keys in an array are integers but rather if the array is indexed (non-associative), for this use array_values() does the trick:

function is_numeric_array($array)
{
    return ($array === array_values($array));
}
\$\endgroup\$
  • \$\begingroup\$ this should be the accepted solution! array_values FTW! \$\endgroup\$ – Tomáš Fejfar Mar 29 '17 at 20:23
2
votes
\$\begingroup\$

If you're actually trying to determine whether it's a sequential integer array rather than associative, i.e. something that json_encode would make an array vs an object, this is probably the fastest way to to do so:

function is_normal_array($arr) {
    $c = count($arr);
    for ($i = 0; $i < $c; $i++) {
        if (!isset($arr[$i])) {
            return false;
        }
    }
    return true;
}

...of course the fastest code is code you never run, so consider whether you really, really need to do this, and only use it where you do.

\$\endgroup\$
  • 1
    \$\begingroup\$ This should be faster than the foreach solutions others have posted, but the algorithms don't agree perfectly. The foreach solutions return true for array(0 => 'a', 1 => 'b', 3 => 'd') whereas mine returns false. \$\endgroup\$ – Jay Paroline Jan 27 '11 at 7:24
2
votes
\$\begingroup\$

This is an old post, but if you're looking for a shorter but still elegant solution, use this:

return array_values($array) === $array

You could even use it as condition since it's pretty short too. Cheers

\$\endgroup\$

Not the answer you're looking for? Browse other questions tagged or ask your own question.