【问题标题】:Search in hierarchical data in PHP在 PHP 中搜索分层数据
【发布时间】:2015-04-20 06:48:11
【问题描述】:

这是我拥有的数据结构(为了更清楚地理解而对其进行了简化):

• USA
  • Alabama
    • Montgomery
    • Birmingham
  • Arizona
    • Phoenix
    • Mesa
    • Gilbert
• Germany
  • West Germany
    • Bonn
    • Cologne

我需要返回给定节点的所有路径——即:如果用户输入Arizona,我需要返回USA → Arizona。如果输入Birmingham,我需要返回USA → Alabama → Birmingham

在 PHP 中有没有简单的方法来搜索这样的结构?

【问题讨论】:

  • 您最好创建自己的树形数据结构。没有内置库,一些用户库会做类似的事情,但最好自己构建一个而不是找到完美的匹配。
  • 如果您有多个相同的条目会发生什么,例如在Birmingham 上搜索:United Kingdom → England → BirminghamUSA → Alabama → Birmingham 有一个伯明翰
  • @MarkBaker 在我的案例中还有另一个内容——这只是解释每个节点之间关系的示例。我很确定,这不会发生在我的情况下。
  • 这是SQL 表吗? XML 方案? ...请添加更多信息
  • @BramDriesen XML 看起来不错。

标签: php search data-structures


【解决方案1】:

如果你没有庞大的数据结构,你可以使用 XML 解析。它众所周知且易于实施。它具有访问父元素所需的能力。

这是一个简单的例子:

$xml = <<<XML
<list>
  <state name="USA">
    <region name="Alabama">
      <city name="Montgomery" />
      <city name="Birmingham" />
    </region>
    <region name="Arizona">
      <city name="Phoenix" />
      <city name="Mesa" />
      <city name="Gilbert" />
    </region>
  </state>
  <state name="Germany">
    <region name="West Germany">
      <city name="Bonn" />
      <city name="Cologne" />
    </region>
  </state>
</list>
XML;


$doc = new \DOMDocument;
$doc->preserveWhiteSpace = false;
$doc->loadXML($xml);

$xpath = new \DOMXPath($doc);
// XPath query to match all elements with
// attribute name equals to your searched phrase
$locations = $xpath->query("//*[@name='Cologne']");

function parse($list) {

  $response  = [];

  foreach ($list as $node) {
      $response[] = $node->attributes->getNamedItem('name')->nodeValue;
      $parentNode = $node->parentNode;
      // traverse up to root element
      // root element has no attributes
      // feel free to use any other condition, such as checking to element's name
      while ($parentNode->hasAttributes()) {
          $response[] = $parentNode->attributes->getNamedItem('name')->nodeValue;
          $parentNode = $parentNode->parentNode;
      }
  }

  return $response;
}

$parsedLocations = array_reverse(parse($locations));

echo implode(' →  ', $parsedLocations), PHP_EOL;

【讨论】:

    【解决方案2】:

    这里有一个可能的策略,可以逐步构建路径:从数组的第一级开始,检查 searc 项是否等于键。如果不是,则检查该值,否则,如果该值是一个数组 (is_array()),则使用前缀递归地重复搜索。

    数据集:

    $str = array(
        "USA" => array(
            "Alabama" => array(
                "Montgomery",
                "Birmingham"
            ),
            "Arizona" => array(
                "Phoenix",
                "",
                "Gilbert"
            ),
            "West Germany" => array(
                "Bonn",
                "",
                "Cologne"
            )
        ),
        "Germany" => array(
            "West Germany" => array(
                "Bonn",
                "Mesa",
                "Cologne"
            )
        )
    );
    

    功能:

    function getPath($haystack, $needle, $prefix=""){
        $path = "";
        foreach($haystack as $key=>$value){
    
            if($path!="")break;
    
            if($key===$needle){
                return $prefix.$key;
                break;
            }
            elseif($value===$needle) {
                return $prefix.$value;
                break;
            }
            elseif(is_array($value)) {
                $path.=getPath($value,$needle,$prefix.$key."=>");   
            }   
        }
        return $path;
    }
    

    测试:

    echo getPath($str,"Mesa");
    

    如果出现重复,您将获得第一个结果。如果未找到搜索词,则会得到一个空字符串。

    【讨论】:

      【解决方案3】:

      由于“数据结构”非常模糊,并且您唯一的提示是您使用的是 PHP,我将假设您的“数据结构”含义如下:

      [
          'USA' =>
          [
              'Alabama' =>
              [
                  'Montgomery',
                  'Birmingham'
              ],
              'Arizona' =>
              [
                  'Phoenix',
                  'Mesa',
                  'Gilbert'
              ]
          ],
          'Germany' =>
          [
              'West Germany' =>
              [
                  'Bonn',
                  'Cologne'
              ]
          ]
      ]
      

      我假设你希望你的结果在表单中

      ['USA', 'Alabama', 'Birmingham']
      

      如果不是这种情况,请告知我们您的数据实际上是如何可用的以及您希望得到的结果如何。

      在 PHP 中有没有简单的方法来搜索这样的结构?

      这取决于您对“简单”的定义。
      对我来说,适合单个功能的解决方案是“简单的”。
      但是,没有开箱即用的解决方案可以用于单行。

      如果您只需要找到“叶子”,您可以使用RecursiveIteratorIterator 而非RecursiveArrayIterator,如this StackOverflow question
      但是由于您也需要找到中间键,所以这不是一个真正的选择。
      array_walk_recursive 也是如此。

      您可能可以使用ArrayIteratorarray_walk,但在这个例子中,它们实际上并不能做任何foreach 循环不能做的事情,除了复杂的事情。
      所以我会选择foreach 循环:

      function findMyThing($needle, $haystack) // Keep argument order from PHP array functions
      {
          // We need to set up a stack array + a while loop to avoid recursive functions for those are evil.
          // Recursive functions would also complicate things further in regard of returning.
          $stack =
          [
              [
                  'prefix' => [],
                  'value' => $haystack
              ]
          ];
          // As long as there's still something there, don't stop
          while(count($stack) > 0)
          {
              // Copy the current stack and create a new, empty one
              $currentStack = $stack;
              $stack = [];
              // Work the stack
              for($i = 0; $i < count($currentStack); $i++)
              {
                  // Iterate over the actual array
                  foreach($currentStack[$i]['value'] as $key => $value)
                  {
                      // If the value is an array, then
                      //   1. the key is a string (so we need to match against it)
                      //   2. we might have to go deeper
                      if(is_array($value))
                      {
                          // We need to build the current prefix list regardless of what we're gonna do below
                          $prefix = $currentStack[$i]['prefix'];
                          $prefix[] = $key;
                          // If the current key, is the one we're looking for, heureka!
                          if($key == $needle)
                          {
                              return $prefix;
                          }
                          // Otherwise, push prefix & value onto the stack for the next loop to pick up
                          else
                          {
                              $stack[] =
                              [
                                  'prefix' => $prefix,
                                  'value' => $value
                              ];
                          }
                      }
                      // If the value is NOT an array, then
                      //   1. the key is an integer, so we DO NOT want to match against it
                      //   2. we need to match against the value itself
                      elseif($value == $needle)
                      {
                          // This time append $value, not $key
                          $prefix = $currentStack[$i]['prefix'];
                          $prefix[] = $value;
                          return $prefix;
                      }
                  }
              }
          }
          // At this point we searched the entire array and didn't find anything, so we return an empty array
          return [];
      }
      

      然后像这样使用它

      $path = findMyThing('Alabama', $array);
      

      【讨论】:

        【解决方案4】:

        @Siguza

        避免那些邪恶的递归函数

        递归不是邪恶的(或评估),并且可以很好地与堆栈一起使用

        function df($v,array &$in,array &$stack,$search) {
            $stack[] = $v;
            if ( $v == $search ) {
                return [true,$stack];
            }
            if ( is_array($in) ) {
                foreach ($in as $vv => $k) {
                    if ( is_array($k) ) {
                        $r = df($vv, $k, $stack, $search);
                        if ($r[0]) {
                            return $r;
                        }
                    }
                    else if ($k == $search) {
                        $stack[] = $k;
                        return [true,$stack];
                    }
                }
            }
            array_pop($stack);
            return [false,null];
        }
        

        用法:

        $s = [];
        $r = df('',$in,$s,'Bonn');
        print_r($r);
        $s = [];
        $r = df('',$in,$s,'West Germany');
        print_r($r);
        $s = [];
        $r = df('',$in,$s,'NtFound');
        print_r($r);
        

        输出:

        Array
        (
            [0] => 1
            [1] => Array
        (
            [0] =>
                [1] => Germany
                    [2] => West Germany
                    [3] => Bonn
                )
        
        )
        Array
        (
            [0] => 1
            [1] => Array
        (
            [0] =>
                [1] => Germany
                    [2] => West Germany
                )
        
        )
        Array
        (
            [0] =>
                [1] =>
        )
        

        【讨论】:

          【解决方案5】:

          根据你的数据结构。

          $data['USA'] = ['Alabama' => ['Montgomery','Birmingham'],'Arizona' => ['Phoenix','Mesa','Gilbert']];
          $data['Germany'] = ['West Germany' => ['Bonn','Cologne']];
          
          function getHierarchy($location, $data){
              $totalCountries = count($data);
          
              //Get Array Keys of rows eg countries.
              $keys = array_keys($data);
              $hierarchy= [];
          
              //Loop Through Countries
              for($i = 0; $i < $totalCountries; $i++){
                  //If we have found the country then return it.
                  if($location == $keys[$i]) return [$keys[$i]];
                  $hierarchy[] = $keys[$i];
                  foreach($data[$keys[$i]] as $city => $places){
          
                      // if we have found the city then return it with country.
                      if($city == $location){
                          $hierarchy[] = $city;
                          return $hierarchy;
                      }
          
                      // if we have found the place in our places array then return it with country -> city -> place.
                      if(in_array($location, $places)){
                          $hierarchy[] = $city;
                          $hierarchy[] = $location;
                          return $hierarchy;
                      }
                  }
                  // Reset Hirarcy if we do not found our location in previous country.
                  $hierarchy = [];
              }
          }
          
          $found = getHierarchy('Birmingham', $data);
          if($found){
              echo implode(' -> ', $found);
              // Output will be USA -> Alabama -> Birmingham
          }
          

          它只能找到一个国家城市和地点,如果找到任何位置,它将破坏整个功能并返回第一个带有城市和地点的位置。

          这是一个更改进的版本,它也可以找到多个位置。 https://gist.github.com/touqeershafi/bf89351f3b226aae1a29

          希望对你有帮助。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2016-06-26
            • 1970-01-01
            • 2022-01-24
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多