繁体   English   中英

在PHP中搜索分层数据

[英]Search in hierarchical data in PHP

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

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

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

在PHP中是否有在这种结构中进行搜索的简单方法?

如果您没有巨大的数据结构,则可以使用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;

这是一种逐步构建路径的可能策略:从数组的第一级开始,然后检查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");

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

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

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

我假设您想要表格中的结果

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

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

在PHP中是否有在这种结构中进行搜索的简单方法?

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

如果只需要查找“叶子”,则可以像在此StackOverflow问题中那样,在RecursiveIteratorIterator上使用RecursiveArrayIterator
但是,由于您还需要找到中间密钥,因此它并不是真正的选择。
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);

@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] =>
)

根据您的数据结构。

$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

希望对您有帮助。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM