【问题标题】:Reference detection in array from another function来自另一个函数的数组中的参考检测
【发布时间】:2013-03-24 12:01:36
【问题描述】:

所以我用的是pin方法,但是检测到引用太晚了一级:

$pin = time();

function wrap($arr){
  test($arr);
}

function test(&$arr){
    global $pin;

    if(in_array($pin, $arr))
        return print "ref";

    $arr[] = $pin;

    foreach($arr as &$v){

        if($v != $pin){

            if(is_array($v))
                return test($v);

            print $v . " ";

      }

    }

}

$array = array(1, 2, 3);
$array[4] = &$array;

wrap($array);

我收到1 2 3 1 2 3 rec

但我希望1 2 3 rec

如果我只是做test($arr) 那么它可以工作,但问题是我需要将测试函数包装在另一个接受值而不是引用的函数中:(

有什么方法可以让我的包装函数在正确的时刻检测到引用?

【问题讨论】:

  • $pin 从不改变,这是故意的吗?
  • 是的。它应该用于比较两个数组是否是同一个变量..
  • @Alex 自动检测?我不这么认为,只有:function wrap(&$arr).
  • 简短回答:不。一旦按值传递输入,内部引用就不再是传递的结构,而是原始的外部结构。唯一可行的方法是通过引用传递,如果您这样做,那么我将您推荐给我的earlier answer,主题是检测两个给定变量是否相互引用。
  • 我会放弃整个方法重写它,有很多问题:)

标签: php arrays loops reference


【解决方案1】:

我认为你把事情复杂化了。我通过遍历数组并检查数组中的当前值是否与数组等效(===)解决了这个问题。

function wrap( $arr){
    test($arr);
}

function test( $arr){
    foreach( $arr as $v) {
        if( $v === $arr) { 
            print 'ref, ';
        } else {
            if( is_array( $v)) { 
                test( $v); 
            } else {
                print $v . ', ';
            }
        }
    }
}

我使用了以下测试用例:

echo "Array 1:\n";
$array1 = array(1, 2, 3);
$array1[4] = &$array1;
wrap( $array1);

echo "\nArray 2:\n";
$array2 = array(1, 2, 3, array(1, 2, 3));
$array2[2] = &$array2;
wrap( $array2);

哪个produced this output

Array 1: 
1, 2, 3, ref 
Array 2: 
1, 2, ref, 1, 2, 3, 

但是,对于嵌套引用,上述方法将失败。如果嵌套引用是可能的,如以下测试用例:

echo "\nArray 3:\n";
$array3 = array(1, 2, 3, array(1, 2, 3));
$array3[3][2] = &$array3;
wrap( $array3);

然后我们需要跟踪我们见过的所有数组引用,像这样:

function wrap( $arr){
    test( $arr);
}

function test( $arr){
    $refs = array(); // Array of references that we've seen

    $f = function( $arr) use( &$refs, &$f) {
        $refs[] = $arr;
        foreach( $arr as $v) {
            if( in_array( $v, $refs)) { 
                print 'ref, ';
            } else {
                if( is_array( $v)) {
                    $f( $v); 
                } else {
                    print $v . ', ';
                }
            }
        }
    };
    $f( $arr);
}

使用上面的测试用例,输出:

Array 3: 
1, 2, 3, 1, ref, 3,

编辑:我更新了跟踪所有引用以消除全局依赖关系的最终函数。

【讨论】:

  • 它不应该在引用之后返回。它应该只打印'ref'并继续下一个元素......
  • @Alex - 我已经更新了我的答案并清理了这篇文章中的 cmets。如果它没有达到您的预期,请告诉我。
  • 您的解决方案仍然会为数组产生误报,例如:$array = array(1, 2, array(1, 2), array(1, 2));。顺便说一句,我看到你在那里做了什么。 ;)
  • @Yoshi - 废话,害怕那个。但是由于您对全局变量提出了如此令人信服的论点,因此我不得不将它们重构出来。 ;)
【解决方案2】:

简介

我认为更好的方法是创建数组的副本并比较修改而不是使用全局引脚,它仍然可以是100% Recursive

示例 1

这是来自您上面的示例:

$array = array(1,2,3);
$array[4] = &$array;
wrap($array);

输出

Array
(
    [0] => 1
    [1] => 2
    [2] => 3
    [4] => ref
)

示例 2

我们真的确定它的检测参考还是只是数组的副本

//Case 1 : Expect no modification
$array = array(1, 2, 3, array(1, 2, 3));
wrap( $array);

//Case 2 : Expect Modification in Key 2
$array = array(1, 2, 3, array(1, 2, 3));
$array[2] = &$array;
wrap( $array);

输出

Array
(
    [0] => 1
    [1] => 2
    [2] => 3
    [3] => Array
        (
            [0] => 1
            [1] => 2
            [2] => 3
        )

)
Array
(
    [0] => 1
    [1] => 2
    [2] => ref
    [3] => Array
        (
            [0] => 1
            [1] => 2
            [2] => 3
        )

)

示例 3

这真的是递归的吗?

$array = array(1, 2, 3, array(1, 2, 3));
$array[4][4][2][6][1] = array(1,2,3=>&$array);
wrap( $array);

输出

Array
(
    [0] => 1
    [1] => 2
    [2] => 3
    [3] => Array
        (
            [0] => 1
            [1] => 2
            [2] => 3
        )

    [4] => Array
        (
            [4] => Array
                (
                    [2] => Array
                        (
                            [6] => Array
                                (
                                    [1] => Array
                                        (
                                            [0] => 1
                                            [1] => 2
                                            [3] => ref   <-- GOT YOU
                                        )

                                )

                        )

                )

        )

)

您修改后的函数

/**
 * Added printf since test now returns array
 * @param array $arr
 */
function wrap(array $arr) {
    printf("<pre>%s<pre>", print_r(test($arr), true));
}


/**
 * - Removed Top Refrence
 * - Removed Global
 * - Add Recursion
 * - Returns array
 * @param array $arr
 * @return array
 */
function test(array $arr) {
    $temp = $arr;
    foreach ( $arr as $key => &$v ) {
        if (is_array($v)) {
            $temp[$key]['_PIN_'] = true;
            $v = isset($arr[$key]['_PIN_']) ? "ref" : test($v);
        }
    }
    unset($temp); // cleanup
    return $arr;
}

【讨论】:

    【解决方案3】:
    function wrap($arr){ test($arr); }
    /// ...
    wrap($array);
    

    您的wrap() 函数为$arr 分配新的内存块。当您在wrap()s 主体内调用test() 函数时,它会引用$arr 内存块,而不是$arrays 内存块,因为$arr 是一个副本$array 和 PHP 内存管理系统将它们分开存储


    有一个通用的参考点功能:

    function is_equal_refs(&$a, &$b){
        $buffer = $a;          // saving current value in temporary variable
    
        $a = md5(time());      // assigning new value to memory block, pointed by reference
    
        $result = ($a === $b); // if they're still equal, then they're point to the same place.
    
        $a = $buffer;          // restoring value
    
        return $result;        // returning result
    }
    

    所以,让我们做一些测试:

    <?php
    header('Content-Type: text/plain');
    
    function is_equal_refs(&$a, &$b){
        $buffer = $a;
    
        $a = md5(time());
    
        $result = ($a === $b);
    
        $a = $buffer;
    
        return $result;
    }
    
    function wrap($arr){ test($arr); }
    
    function test(&$arr){
        foreach($arr as &$v){
    
            if(is_equal_refs($arr, $v)){
                print_r('ref');
                echo PHP_EOL;
                break;
            }
    
            if(is_array($v))return test($v);
    
            print_r($v);
            echo PHP_EOL;
        }
    }
    
    $array   = array(1, 2, 3);
    $array[] = &$array;
    
    wrap($array);
    ?>
    

    演出:

    1   // < $arr
    2
    3
    1   // < $array
    2
    3
    ref // < $array doubled -> reference found
    

    这种行为的原因是$arr[3] 包含对$arrays 内存块的引用,而不是对自身内存块的引用。

    让我们删除$array[] = &amp;$array; 行,并修改wrap() 函数来检查:

    function wrap($arr){
        $arr[] = &$arr;
    
        test($arr);
    }
    

    结果是:

    1   // < $arr
    2
    3
    ref // < $arr doubled -> reference found
    

    因为$arr 不是指向$array,而是指向$arr[3] 中的自身。因此,在您的代码中,您想要发现不同的引用。


    结论:您想要实现的是打破 PHP 内存管理规则。


    UPDv1:

    需要寻求解决方法,以在wrap() 函数范围内恢复$array 引用。

    1) “bad”/“globals”的做法:

    <?php
    header('Content-Type: text/plain');
    
    function is_equal_refs(&$a, &$b){
        $buffer = $a;
    
        $a = md5(time());
    
        $result = ($a === $b);
    
        $a = $buffer;
    
        return $result;
    }
    
    function wrap($array){
        global $check; // <- THIS
    
        test(empty($check) ? $array : $check); // <- THIS
    }
    
    function test(&$arr){
        foreach($arr as &$v){
    
            if(is_equal_refs($v, $arr)){
                print_r('ref');
                echo PHP_EOL;
                break;
            }
    
            if(is_array($v)){
                test($v);
            } else {
                print $v . ' ';
                echo PHP_EOL;
            }
        }
    }
    
    $array   = array(1, 2, 3);
    $array[] = &$array;
    $check   = &$array; // <- and THIS
    
    wrap($array);
    ?>
    

    其中显示:

    1
    2
    3
    ref
    

    2) “将所有内容包装在数组或对象中”的做法:(首选且可靠)

    <?php
    header('Content-Type: text/plain');
    
    define('REF_MARKER', 'x-my-tr!cky-ref'); // trick key definition
    
    function is_equal_refs(&$a, &$b){
            $buffer = $a;
    
            $a = md5(time());
    
            $result = ($a === $b);
    
            $a = $buffer;
    
            return $result;
        }
    
    function wrap(array $arr){
            // restore reference, if trick.
            // it might be moved to the top part of test() function (might affect performance).
            if(isset($arr[REF_MARKER]))$arr = &$arr[REF_MARKER];
    
            test($arr);
        }
    
    // $array - subject to test;
    // $refs  - internal ref list of all `subjects`;
    function test(&$array, $refs = array()){
            $refs[] = &$array;
    
            foreach($array as &$value){
    
                foreach($refs as &$ref){
                    if(is_equal_refs($ref, $value))return print 'ref ';
                }
    
                if(is_array($value)){
                    $refs[] = &$value;
    
                    test($value, $refs);
                } else {
                    print $value . ' ';
                }
            }
        }
    
    $array   = array(1, 2, 3);
    $array[] = &$array;
    
    wrap(array(REF_MARKER => &$array)); // trick
    
    print PHP_EOL;
    
    $ring      = array(1, 2, 3, array(4, 5, 6));
    $ring[3][] = &$ring;
    
    wrap(array(REF_MARKER => &$ring)); // trick
    
    print PHP_EOL;
    
    $test = array('a', 'b', 'c');
    $ring = array(1, 2, 3);
    
    $ring[] = &$test;
    $test[] = &$ring;
    
    wrap(array(REF_MARKER => &$ring)); // trick
    
    print PHP_EOL;
    
    wrap(range(1, 5)); // normal
    
    print PHP_EOL;
    
    $test = array(1, 2, 3, array(1, 2, 3), 4, array(5, 2, 3), array(6, array(1, 2, 3), 7), array(1, 2, 3));
    
    wrap($test); // normal
    
    print PHP_EOL;
    
    $test[]    = &$test;
    $test[3][] = &$test;
    $test[5][] = &$test[3];
    
    wrap(array(REF_MARKER => &$test)); // trick
    ?>
    

    演出:

    1 2 3 ref
    1 2 3 4 5 6 ref
    1 2 3 a b c ref
    1 2 3 4 5
    1 2 3 1 2 3 4 5 2 3 6 1 2 3 7 1 2 3
    1 2 3 1 2 3 ref 4 5 2 3 ref 6 1 2 3 7 1 2 3 ref
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-10-28
      • 1970-01-01
      相关资源
      最近更新 更多