Emma
Emma

Reputation: 27723

Fastest way to return TRUE if all values in a 2d array column are numeric

Are there faster methods to check the existence of a number (not null) in one column of a multidimensional array in php (for instance, number9)?

Attempt:

The if statement below seems to be working okay.

$arr=Array(
    [0] => Array
        (
            [date] => 2019-01-16
            [number1] => 20.4
            [number2] => 20.54
            [number3] => 19.71
            [number4] => 19.73
            [number5] => 70849266
            [number6] => 70849266
            [number7] => -0.65
            [number8] => -3.189
            [number9] => 20.0902
            [string1] => Jan 16
            [number10] => 0.047796070100903
        )
    ... more rows ...
    [21] => Array
        (
            [date] => 2019-01-17
            [number1 => 19.49
            [number2] => 20.51
            [number3] => 19.02
            [number4] => 20.25
            [number5] => 85018421
            [number6] => 85018421
            [number7] => 0.52
            [number8] => 2.636
            [number9] => 19.7988
            [string1] => Jan 17
            [number10] => 0.075411577270313
        )
);


function isNumeric($var){
  if (is_numeric($var)){
    return true;
  } else {
    return false;
  }
}

if ((array_walk(array_column($arr, "number8"), 'isNumeric')) != 1)

Upvotes: 2

Views: 468

Answers (5)

mickmackusa
mickmackusa

Reputation: 47774

From PHP8.4 (or use a polyfill until you upgrade your version), array_all() is the perfect choice for a short-circuiting functional-style approach that will never make more than a single loop over the array and will return a boolean value. Demo

var_export(
    array_all($arr, fn($row) => is_numeric($row['number8']))
);

Upvotes: 0

Emma
Emma

Reputation: 27723

Thanks a million everyone, for your great answers!

On my PC, I tried your four functions in PHP 5.5.38 with ~5000 iterations and total times are:

"is_numeric_array_with_cast total time is 0.44153618812561"
"with_array_filter total time is 0.21628260612488"
"is_numeric_array_with_func total time is 0.14269280433655"
"is_numeric_matt_fryer total time is 0.155033826828"


$t1=$t2=$t3=$t4=0;
foreach($arrs as $k=>$arr){
    $s1=microtime(true);
    is_numeric_array_with_cast(array_column($arr, "number8"));
    $e1=microtime(true)-$s1;
    $t1=$t1+$e1;

    $s2=microtime(true);
    with_array_filter(array_column($arr, "number8"));
    $e2=microtime(true)-$s2;
    $t2=$t2+$e2;


    $s3=microtime(true);
    is_numeric_array_with_func(array_column($arr, "number8"));
    $e3=microtime(true)-$s3;
    $t3=$t3+$e3;

    $s4=microtime(true);
    is_numeric_matt_fryer(array_column($arr, "number8"),"number8");
    $e4=microtime(true)-$s4;
    $t4=$t4+$e4;

}






function is_numeric_array_with_cast($arr) {
    foreach ($arr as $b) {
        if ($b != (string)(float)$b) {
            return false;
        }
    }
    return true;
}


function with_array_filter($arr) {
    return $arr == array_filter($arr, 'is_numeric');
}


function is_numeric_array_with_func($arr) {
    foreach ($arr as $b) {
        if (!is_numeric($b)) {
            return false;
        }
    }
    return true;
}

function is_numeric_matt_fryer($arr,$str){
  $bool = true;
  foreach ($arr as $row)
  {
      if (!is_numeric($row[$str]))
      {
          $bool = false;
      }
  }
  return $bool;
}

Upvotes: 0

user1247034
user1247034

Reputation:

Use a foreach loop:

$bool = true;
foreach ($arr as $row)
{
    if (!is_numeric($row['number8']))
    {
        $bool = false;
        break;
    }
}

Upvotes: 0

Dharman
Dharman

Reputation: 33242

Here are my ideas.
First is to just filter the array for numeric only values and compare to the original:

function with_array_filter($arr) {
    return $arr == array_filter($arr, 'is_numeric');
}

The second example uses casting to a float and then back to string, keep in mind that this is not going to be accurate for very big numbers, however seems to be the fastest (if you care about that at all):

function is_numeric_array_with_cast($arr) {
    foreach ($arr as $b) {
        if ($b != (string)(float)$b) {
            return false;
        }
    }
    return true;
}

However probably the simplest solution is to just foreach the array inside a function and return early:

function is_numeric_array_with_func($arr) {
    foreach ($arr as $b) {
        if (!is_numeric($b)) {
            return false;
        }
    }
    return true;
}

Benchmarked with an array of 20 elements over 100000 iterations on PHP 7.2:

$falseArray = [
    '1',
    2.5,
    -10,
    32,
    11.0,
    2.5,
    100101221,
    345,
    -10,
    '-10',
    '+10',
    '10',
    12,
    PHP_INT_MAX,
    PHP_INT_MAX + 1.4e5,
    '-10',
    null,
    'a',
    '5',
    5
];

Matt Fryer
Time: 4.8473789691925

is_numeric_array_with_func
Time:  4.0416791439056

is_numeric_array_with_cast
Time:  3.2953300476074

with_array_filter
Time:  3.99729180336

Upvotes: 1

ArtisticPhoenix
ArtisticPhoenix

Reputation: 21661

AS I said in the comments:

The if statement below seems to be working okay

However, given the code you posed I doubt that: lets look at it.

function isNumeric($var){ ... }

if(array_walk(array_column($arr, "number8"), 'isNumberic'))!=1

The first and most obvious things are these 2

  • isNumberic vs isNumeric, which is a fatal undefined function error (spelling/typo).
  • )!=1 then this is outside of the actual condition, or put another way if(...){ !=1 }

Let's assume those are just typos in the question. Even if your code was free of the 2 "defects" I mentioned above you would still have this problem, array_walk works by reference and simply returns true (always). Pass by reference updates the "Original" variable without returning a copy of it (in the case of array_walk)

http://php.net/manual/en/function.array-walk.php

Return Values Returns TRUE.

Which of course just makes your condition pass no matter what. So you should always test both the passing and the failing of the condition (As I did by placing some "canned" bad data in there). This way I know for 100% sure exactly how my code behaves.

Others have posted how to correct this, but not what you did wrong. But just for the sake of completeness I will post an answer anyway.

$arr = array (
    0 => 
    array (
        'date' => '2019-01-16',
        'number1' => 20.4, 
        'number2' => 20.54,
        'number3' => 19.71,
        'number4' => 19.73,
        'number5' => 70849266,
        'number6' => 70849266,
        'number7' => -0.65,
        'number8' => -3.189,
        'number9' => 20.0902,
        'string1' => 'Jan16',
        'number10' => 0.047796070100903
    ),
    array (
        'date' => '2019-01-16',
        'number1' => 20.4, 
        'number2' => 20.54,
        'number3' => 19.71,
        'number4' => 19.73,
        'number5' => 70849266,
        'number6' => 70849266,
        'number7' => -0.65,
        'number8' => 'foo',#intentially not number
        'number9' => 20.0902,
        'string1' => 'Jan16',
        'number10' => 0.047796070100903
    ),

);

$col = array_column($arr, "number8");
$col1 = array_filter($col, 'is_numeric');

if($col != $col1){
    echo "not the same\n";
}

Output:

not the same

Sandbox

I should mention, there is no "need" to count these, as PHP can compare complex objects for equality. As we are comparing the same "root" array ($col in this example) with itself after (possibly) removing some elements, if no elements were removed then both arrays should be not only the same length but also "Identical".

Also if you want to do it in one line (inside the condition) you can do this:

if( ($col = array_column($arr, "number8")) && $col != array_filter($col, 'is_numeric')){
    echo "not the same\n";
}

It's a bit harder to read, and pay attention to $col = array_column assignment.

Upvotes: 1

Related Questions