在嵌套数组中查找内部数组

时间:2011-11-19 21:23:51

标签: php arrays nested

我在PHP中有一个嵌套数组:

array (
'0' => "+5x",
'1' => array (
       '0' => "+",
       '1' => "(",
       '2' => "+3",
       '3' => array (
              '0' => "+",
              '1' => "(",
              '2' => array ( // I want to find this one.
                  '0' => "+",
                  '1' => "(",
                  '2' => "+5",
                  '3' => "-3",
                  '4' => ")"
                  ),
              '3' => "-3",
              '4' => ")"
              ),
       '4' => ")"
       )
);       

我需要在这里处理最里面的数组,注释:“我想找到这个。”那是否有功能?

我考虑过这样做(写作一个想法,而不是正确的PHP):

foreach ($array as $id => $value) {
    if ($value is array) {
        $name = $id;
        foreach ($array[$id] as $id_2 => $value_2) {
            if ($value_2 is array) {
                $name .= "." . $id_2;
                foreach ($array[$id][$id_2] as $id_3 => $value_3) {
                    if ($value_3 is array) {
                        $name .= "." . $id_3;
                        foreach ($array[$id][$id_2][$id_3] as $id_4 => $value_4) {
                            if ($value_4 is array) {
                                $name .= "." . $id_4;
                                foreach [and so it goes on];
                            } else {
                                $listOfInnerArrays[] = $name;
                                break;
                            }
                        }
                    } else {
                        $listOfInnerArrays[] = $name;
                        break;
                    }
                }
            } else {
                $listOfInnerArrays[] = $name;
                break;
            }
        }
    }
}

它的作用是使$name成为数组中的当前键。如果值是数组,则使用foreach进入数组并添加“。”和数组的id。所以我们在示例数组中最终得到:

array (
    '0' => "1.3.2",
)

然后我可以处理这些值以访问内置数组。

问题是我正在尝试查找内部数组的数组是动态的,并由用户输入组成。 (它将输入字符串拆分为+或 - ,如果它包含括号,则将其放在单独的嵌套数组中。因此,如果用户键入了很多括号,则会有很多嵌套数组。) 因此,我需要让这个模式减少20次,但无论如何它仍然只能捕获20个嵌套数组。

那还有一个功能吗?或者有没有办法让它在没有我的长代码的情况下做到这一点?也许制作一个循环来制作必要数量的foreach模式并通过eval()运行它?

4 个答案:

答案 0 :(得分:16)

解释

  
简单:
  
描述没有子表达式的表达式(例如“5”,“x”)。
  
化合物:
  
描述具有子表达式的表达式(例如“3 + x”,“1 + 2”)。
  
常量性:
  
表达式是否具有常数值(例如“5”,“1 + 2”)或不具有(例如“x”,“3 + x”)。
  
外部节点:
  
在表达式树中,通过始终向左遍历或始终向右遍历可到达的节点。 “外”总是相对于给定节点;节点可能相对于一个节点是“外部”,但相对于该节点的父节点是“内部”。
  
内部节点:
  
在表达式树中,不是外部节点的节点。

有关“内部”和“外部”节点的说明,请考虑:

       __1__
      /     \ 
     2       5
    / \     / \
   3   4   6   7
3和7总是外部节点。 6是相对于5的外部,但是相对于内部的内部。

答案

这里的困难更多地在于表达格式不均匀而不是嵌套。如果使用表达式树,示例5x+3=(x+(3+(5-3)))等式将解析为:

array(
    '=' => array(
        '+' => array( // 5x + 3
            '*' => array(
                5, 'x'
            ),
            3
        )
        '+' => array( // (x+(3+(5-3)))
            'x',
            '+' => array( // (3+(5-3))
                3,
                '-' => array(
                    5, 3
)   )   )   )   )

请注意,二进制操作的节点是二进制的,并且一元操作将具有一元节点。如果二进制可交换操作的节点可以组合成n-ary节点,5x+3=x+3+5-3可以解析为:

array(
    '=' => array(
        '+' => array( // 5x + 3
            '*' => array(
                5, 'x'
            ),
            3
        )
        '+' => array( // x+3+5-3
            'x',
            3,
            '-' => array(
                5, 3
)   )   )   )

然后,您将编写一个可以简化节点的后序递归函数。 “后序”意味着节点处理在处理其子节点之后发生;还有预订(在其子节点之前处理节点)和按顺序(在节点之前处理一些子节点,之后处理其余子节点)。以下是粗略概述。其中,“thing:Type”表示“thing”的类型为“Type”,“&”表示按引用传递。

simplify_expr(expression : Expression&, operation : Token) : Expression {
    if (is_array(expression)) {
        foreach expression as key => child {
            Replace child with simplify_expr(child, key); 
                key will also need to be replaced if new child is a constant 
                and old was not.
        }
        return simplify_node(expression, operation);
    } else {
        return expression;
    }
}

simplify_node(expression : Expression&, operation : Token) : Expression;

在某种程度上,真正的挑战是撰写simplify_node。它可以在表达式节点上执行许多操作:

  1. 如果一个内在的大孩子与另一个孩子的常数不匹配,但是它的兄弟姐妹确实如此,则交换兄弟姐妹。换句话说,将奇数输出作为外部节点。这一步是为下一步做准备。
        +            +                +            +
       / \          / \              / \          / \
      \+   2  --->  +   2            +   y  --->  +   y
     / \          / \              / \          / \
    1   x        x   1            x   1        1   x
    
  2. 如果节点和子节点是相同的可交换操作,则可以重新排列节点。例如,有旋转:

        +            +
       / \          / \
      \+   c  --->  a   +
     / \              / \
    a   b            b   c
    

    这对应于将“(a + b)+ c”改变为“a +(b + c)”。当“a”与“b”和“c”的常量不匹配时,您将要旋转。它允许将下一个转换应用于树。例如,此步骤会将“(x + 3)+1”转换为“x +(3 + 1)”,因此下一步可以将其转换为“x + 4”。

    总体目标是将const子树作为兄弟姐妹。如果一个可交换节点有两个const后代,它们可以相互旋转。如果一个节点只有一个const后代,那么让它成为一个子节点,这样层次结构中的一个节点可以将const节点与另一个祖先的const子节点组合起来(即const节点浮动直到它们是兄弟节点,此时它们像苏打水中的气泡一样。

  3. 如果所有子节点都是常量,请评估节点并将其替换为结果。
  4. 处理具有多个复合子节点和n-ary节点的节点作为读者练习。

    面向对象的替代

    OO方法(使用对象而不是数组来构建表达式树)将具有许多优点。操作将与节点关联得更紧密;它们是节点对象的属性,而不是节点键。将辅助数据与表达式节点相关联也会更容易,这对优化很有用。您可能不需要深入了解OOP范例来实现它。可以使以下简单类型层次结构起作用:

                       Expression
                      /          \
            SimpleExpr            CompoundExpr
            /        \
    ConstantExpr    VariableExpr
    

    操纵树的现有自由函数将成为方法。接口看起来像下面的伪代码。在其中:

    • Child < Parent表示“Child”是“Parent”的子类。
    • 属性(例如isConstant)可以是方法或字段;在PHP中,您可以使用overloading实现此目的。
    • (...){...}表示函数,括号和括号之间的参数之间的参数(非常类似于Javascript中的function (...){...})。此语法用于作为方法的属性。简单方法只是为方法体使用括号。

    现在为样本:

    Expression {
        isConstant:Boolean
        simplify():Expression
    }
    
    SimpleExpr < Expression {
        value:Varies
        /* simplify() returns an expression so that an expression of one type can 
           be replaced with an expression of another type. An alternative is
           to use the envelope/letter pattern:
             http://users.rcn.com/jcoplien/Patterns/C++Idioms/EuroPLoP98.html#EnvelopeLetter
             http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Envelope_Letter
         */
        simplify():Expression { return this }
    }
    
    ConstantExpr < SimpleExpr {
        isConstant:Boolean = true
    }
    
    VariableExpr < SimpleExpr {
        isConstant:Boolean = false
    }
    
    CompoundExpr < Expression {
        operation:Token
        children:Expression[]
    
        commutesWith(op:Expression):Boolean
        isCommutative:Boolean
        isConstant:Boolean = (){ 
            for each child in this.children:
                if not child.isConstant, return false
            return true
        }
        simplify():Expression {
            for each child& in this.children {
                child = child.simplify()
            }
            return this.simplify_node()
        }
        simplify_node(): Expression {
            if this.isConstant {
                evaluate this, returning new ConstExpr
            } else {
                if one child is simple {
                    if this.commutesWith(compound child)
                       and one grand-child doesn't match the constness of the simple child 
                       and the other grand-child matches the constness of the simple child 
                    {
                        if (compound child.isCommutative):
                            make odd-man-out among grand-children the outer child
                        rotate so that grand-children are both const or not
                        if grand-children are const:
                            set compound child to compound child.simplify_node()
                        }
                } else {
                    ...
                }
            }
            return this
        }
    }
    

    例如,SimpleExprConstantExpr的PHP实现可能是:

    class SimpleExpr extends Expression {
        public $value;
        function __construct($value) {
            $this->value = $value;
        }
        function simplify() { 
            return $this;
        }
    }
    
    class ConstantExpr extends SimpleExpr {
        // Overloading
        function __get($name) {
            switch ($name) {
            case 'isConstant':
                return True;
            }
        }
    }
    

    ConstantExpr的替代实现:

    function Expression {
        protected $_properties = array();
        // Overloading
        function __get($name) {
            if (isset($this->_properties[$name])) {
                return $this->_properties[$name]; 
            } else {
                // handle undefined property
                ...
            }
        }
        ...
    }
    
    class ConstantExpr extends SimpleExpr {
        function __construct($value) {
            parent::construct($value);
            $this->_properties['isConstant'] = True;
        }
    }
    

答案 1 :(得分:1)

请尝试以下代码并告诉我结果。

您只需将数组传递给find_deepest函数。

function find_deepest( $array )
{
    $index       = '';        // this variable stores the current position (1.2, 1.3.2, etc.)
    $include     = true;      // this variable indicates if the current position should be added in the result or not
    $result      = array();   // this is the result of the function, containing the deepest indexes
    $array_stack = array();   // this is a STACK (or LIFO) to temporarily store the sub-arrays - see http://en.wikipedia.org/wiki/LIFO_%28computing%29
    reset( $array );          // here we make the array internal POINTER move to the first position

    // each loop interaction moves the $array internal pointer one step forward - see http://php.net/each
    // if $current value is null, we reached the end of $array; in this case, we will also continue the loop, if the stack contains more than one array
    while ( ( $current = each( $array ) ) || ( count( $array_stack ) > 1 ) )
    {
        // we are looping $array elements... if we find an array (a sub-array), then we will "enter it...
        if ( is_array( $current['value'] ) )
        {
            // update the index string
            $index .= ( empty ( $index ) ? '' : '.' ) . $current['key'];
            // we are entering a sub-array; by default, we will include it
            $include = true;
            // we will store our current $array in the stack, so we can move BACK to it later
            array_push( $array_stack, $array );
            // ATTENTION! Here we CHANGE the $array we are looping; here we "enter" the sub-array!
            // with the command below, we start to LOOP the sub-array (whichever level it is)
            $array = $current['value'];
        }
        // this condition means we reached the END of a sub-array (because in this case "each()" function has returned NULL)
        // we will "move out" of it; we will return to the previous level
        elseif ( empty( $current ) )
        {
            // if we reached this point and $include is still true, it means that the current array has NO sub-arrays inside it (otherwise, $include would be false, due to the following lines)
            if ( $include )
                $result[] = $index;
            // ATTENTION! With the command below, we RESTORE $array to its precedent level... we entered a sub-array before, now we are goin OUT the sub-array and returning to the previous level, where the interrupted loop will continue
            $array = array_pop( $array_stack );
            // doing the same thing with the $index string (returning one level UP)
            $index = substr( $index, 0, strrpos( $index, '.' ) );
            // if we are RETURNING one level up, so we do NOT want the precedent array to be included in the results... do we?
            $include = false;
        }
        // try removing the comment from the following two lines! you will see the array contents, because we always enter this "else" if we are not entering a sub-array or moving out of it
        // else
        //   echo $index . ' => ' . $current['value'] . '<br>';
    }
    return $result;
}

$result = find_deepest( $my_array );
print_r( $result );

代码中最重要的部分是:

  1. each循环
  2. 中的while命令
  3. array_push函数调用,我们将当前数组存储在“数组堆栈”中,以便稍后返回
  4. array_pop函数调用,我们通过从“数组堆栈”恢复当前数组返回一个级别

答案 2 :(得分:1)

RecursiveIteratorIterator知道任何孩子的当前深度。因为你只对有孩子的孩子感兴趣,过滤没有孩子的孩子并寻找最大深度。

然后根据深度再次过滤最大深度:

$ritit = new RecursiveIteratorIterator(new RecursiveArrayIterator($arr), RecursiveIteratorIterator::SELF_FIRST);

$cf = new ChildrenFilter($ritit);

$maxdepth = NULL;
foreach($cf as $v)
{
    $maxdepth = max($maxdepth, $cf->getDepth());
}

if (NULL === $maxdepth)
{
    throw new Exception('No children found.');
}

$df = new DepthFilter($cf, $maxdepth);

foreach($df as $v)
{
    echo "Array with highest depth:\n", var_dump($v), "\n";
}

Demo / Source

答案 3 :(得分:1)

递归foreach函数,来自http://php.net/manual/en/control-structures.foreach.php

的评论
/* Grab any values from a multidimensional array using infinite recursion.  --Kris */
function RecurseArray($inarray, $result) {
    foreach ($inarray as $inkey => $inval) {
        if (is_array($inval)) {
            $result = RecurseArray($inval, $result);
        } else {
            $result[] = $inval;
        }
    }

    return $result;
}

请注意,上述实现会生成扁平数组。为了保护嵌套:

function RecurseArray($inarray) {
    $result = array();
    foreach ( $inarray as $inkey => $inval ) {
        if ( is_array($inval) ) {
            $result[] = RecurseArray($inval);
        } else {
            // process $inval, store in result array
            $result[] = $inval;
        }
    }

    return $result;
}

就地修改数组:

function RecurseArray(&$inarray) {
    foreach ( $inarray as $inkey => &$inval ) {
        if ( is_array($inval) ) {
            RecurseArray($inval);
        } else {
            // process $inval
            ...
        }
    }
}