PHP - 以顺序方式填充数组,直到达到最大长度

时间:2016-03-09 23:15:14

标签: php arrays regex loops sequential

我需要使用PHP:

从以这种方式格式化的文本文件中提取数据
BEGIN
#1 
#2 
#3 
#4 
#5 
#6 
1       2015-05-31  2001-11-24  'Name Surname'      ID_1        0 
2       2011-04-01  ?           ?                   ID_2        1 
2       2013-02-24  ?           ?                   ID_3        1 
2       2014-02-28  ?           'Name Surname'      ID_4        2 
END

信息按照如下数组的逻辑进行组织:

Array ( [#1] => 1 [#2] => 2015-05-31 [#3] => 2001-11-24 [#4] => 'Name Surname' [#5] => ID_1 [#6] => 0 )
Array ( [#1] => 2 [#2] => 2011-04-01 [#3] => ?           [#4] => ?             [#5] => ID_2 [#6] => 1 )
Array ( [#1] => 2 [#2] => 2013-02-24 [#3] => ?           [#4] => ?             [#5] => ID_3 [#6] => 1 )
Array ( [#1] => 2 [#2] => 2014-02-28 [#3] => ?           [#4] => 'Name Surname' [#5] => ID_4 [#6] => 2 )

我正在寻找获得该输出的方法。我正在使用此代码:

<?php 
    //ini_set('max_execution_time', 300); //300 seconds = 5 minutes

    function startsWith($str, $char){
        return $str[0] === $char;
    }

    $txt_path = "./test.txt";
    $txt_data = @file_get_contents($txt_path) or die("Could not access file: $txt_path");
    //echo $txt_data;

    $loop_pattern = "/BEGIN(.*?)END/s";
    preg_match_all($loop_pattern, $txt_data, $matches);
    $loops = $matches[0];
    //print_r($loops);
    $loops_count = count($loops);
    //echo $loops_count; // number of loops into the file
    foreach ($loops as $key => $value) {
        $value = trim($value);
        $pattern = array("/[[:blank:]]+/", "/BEGIN(.*)/", "/END(.*)/");
        $replacement = array(" ", "", "");
        $value = preg_replace($pattern, $replacement, $value);
        //print_r($value);
        //echo "<br><br>";
        $value_array = explode("\n", $value);
        $value_array_clean = array_filter($value_array, 'strlen');
        $value_array_clean_reindex = array_values($value_array_clean);
        //print_r($value_array_clean_reindex);
        //echo "<br><br>";
        $keys = array();
        $values = array();
        foreach ($value_array_clean_reindex as $key => $value) {
            $value = trim($value);
            if ( startsWith($value, "#") ) {
                array_push($keys, $value);
                $keys_count = count($keys);
            } else {
                array_push($values, $value);
                $values_count = count($values);

                $loop_dic = array();
                foreach ($values as $key => $value) {
                    $value = trim($value);
                    preg_match_all("/'(?:.|[^'])*'|\S+/", $value, $matches);
                    //print_r($matches[0]);
                    $loop_dic = array_combine($keys, $matches[0]);
                }

                print_r($loop_dic);
                echo "<br><br>";
            }
        }
    }
?>

它给了我想要的输出:

Array ( [#1] => 1 [#2] => 2015-05-31 [#3] => 2001-11-24 [#4] => 'Name Surname' [#5] => ID_1 [#6] => 0 )
Array ( [#1] => 2 [#2] => 2011-04-01 [#3] => ? [#4] => ? [#5] => ID_2 [#6] => 1 )
Array ( [#1] => 2 [#2] => 2013-02-24 [#3] => ? [#4] => ? [#5] => ID_3 [#6] => 1 )
Array ( [#1] => 2 [#2] => 2014-02-28 [#3] => ? [#4] => 'Name Surname' [#5] => ID_4 [#6] => 2 )

但有时在命令级别出现问题:

$loop_dic = array_combine($keys, $matches[0]);

据我所知,在原始文本文件中,线条很长,这些都被打破了,产生了一条新线;而不是:

2       2014-02-28  ?           'Name Surname'      ID_4        2 

这条线被打破了:

2       2014-02-28  ?           'Name Surname'      
ID_4        2 

因此,当我按\n分解字符串时,它会在我合并的两个数组的长度中出现错误。

我会问你一个替代方法来解决这个问题,获得相等长度的数组,如果在原始文件中发生中断的话。

在网上搜索,我发现array_fill;也许,如果我知道(通过count)每个循环([#1],...,[#6])的数组中的键数,就可以循环并填充数组的值,按顺序添加它们,直到值的每个数组的最大长度。

感谢您的关注和帮助。

编辑#1

感谢@ fusion3k的解决方案! 使用某些输入文件检查行为会显示另外两个问题:

1)分析一些错误,我发现有时输入文件使用双引号(而不是单引号),并且分号之间的多行文本块,如下所示:

;This is some text
in multiline with "double 
quotes" too
;

需要被视为给定键的单个值,其值必须是内联的,如@ fusion3k代码,将\n替换为(空格)。我正在尝试将@ fusion3k的工作代码与精心设计的代码合并以解决此问题。文件结构可能就像这样:

BEGIN
#1 
#2 
#3 
#4 
#5 
#6 
1       2015-05-31  2001-11-24  "Name Surname"      ID_1        0 
2       2011-04-01  ?           ?                   ID_2        1 
2       2013-02-24  ?           ?                   ID_3        1 
2       2014-02-28  ?           "Name Surname"      ID_4        2 
;This is some text
in multiline with "double 
quotes" too
;
2016-01-22  ?           "Name Surname"      ID_5        2 
END

应生成类似上述工作代码的内容,但考虑到存在不同的文本块定界符,如分号;),单引号')或者像其他文件一样,双引号"),用于分隔必须被视为键的单个值的文本块,例如这个数组相对于上面的文本文件内容:

Array ( [#1] => Array ( [0] => 1 [1] => 2 [2] => 2 [3] => 2 [4] => This is some text in multiline with "double quotes" too ) [#2] => Array ( [0] => 2015-05-31 [1] => 2011-04-01 [2] => 2013-02-24 [3] => 2014-02-28 [4] => 2016-01-22 ) [#3] => Array ( [0] => 2001-11-24 [1] => ? [2] => ? [3] => ? [4] => ? ) [#4] => Array ( [0] => Name Surname [1] => ? [2] => ? [3] => Name Surname [4] => Name Surname ) [#5] => Array ( [0] => ID_1 [1] => ID_2 [2] => ID_3 [3] => ID_4 [4] => ID_5 ) [#6] => Array ( [0] => 0 [1] => 1 [2] => 1 [3] => 2 [4] => 2 ) )

我在一个简单的字符串上工作,找到一个考虑(分号)AND(单引号 OR 双引号)。目前我还没有找到使用所有三个分隔符来分隔文本块的文件,但似乎可以找到分号 + single_quotes 分号 + double_quotes 或仅 single_quotes 或仅 double_quotes ;在同一个文本文件中找到包含所有三种分隔符的解决方案......:

$string = 'something here 
;and there
;
oh, "that\'s all!"';
$string = str_replace( "\n", " ", $string );
$origin = array("/[[:blank:]]+/", "/\"/", "/;/");
$replacement = array(" ", "\" ", "; ");
$string = preg_replace($origin, $replacement, $string);
$pattern = '/([;"])\s+/';
print_r(array_filter(preg_split( $pattern, $string ), 'strlen'));

这是输出(如所需):

Array ( [0] => something here [1] => and there [2] => oh, [3] => that's all! )

注意分号之间的文本块:它始终以新行开头,开头是分号,并以分号结束在一个新行中,然后开始另一个换行符。

我不知道它是否能以更好,更快的方式编写...我尝试将其与@ fusion3k的代码合并,处理上述文本文件内容,但没有成功。我尝试了像这样的if/elseif/else结构:

if ( preg_match('/;(.*?);|\'(.*?)\'/', $value, $matches) ) {// semicolon with single quotes in the $value string
    $value = str_replace( "\n", " ", $value );
    $origin = array("/[[:blank:]]+/", "/'/", "/;/");
    $replacement = array(" ", "' ", "; ");
    $value = preg_replace($origin, $replacement, $value);
    $pattern = '/'.str_repeat( "([;'])\s+", count( $keys ) ).'/';
    print_r(array_filter(preg_split( $pattern, $value ), 'strlen')); // I would have an array of values of the same length of the array for the keys
    echo "<br><br>";
} elseif ( preg_match('/;(.*?);|"(.*?)"/', $value, $matches) ) {// semicolon with double quotes in the $value string
    $value = str_replace( "\n", " ", $value );
    $origin = array("/[[:blank:]]+/", "/\"/", "/;/");
    $replacement = array(" ", "\" ", "; ");
    $value = preg_replace($origin, $replacement, $value);
    $pattern = '/'.str_repeat( "([;\"])\s+", count( $keys ) ).'/';
    print_r(array_filter(preg_split( $pattern, $value ), 'strlen')); // I would have an array of values of the same length of the array for the keys
    echo "<br><br>";
} else {// neither single quotes (or double quotes) nor semicolon in the $value string
    $pattern = '/'.str_repeat( "(\S+)\s+", count( $keys ) ).'/';
    preg_match_all( $pattern, $value, $matches );
    //print_r($matches);
    //echo "<br><br>";
    $loop_dic = array_combine( $keys, array_slice( $matches, 1 ) );
    print_r( $loop_dic ); // this is good...maybe in a better way?
    echo "<br><br>";
}

唯一有效的代码是使用@ fusion3k代码的最后一个代码。

2)当文件非常大时,会发生第二种行为(可能已经解决)。命令:

$loop_pattern = "/BEGIN(.*?)END/s";
preg_match_all($loop_pattern, $txt_data, $matches);
$loops = $matches[0];
//print_r($loops);
$loops_count = count($loops);
//echo $loops_count; // number of loops into the file

不会占用文件中的所有循环(大文件)。 我想答案可能是here。所以,设置:

ini_set('max_execution_time', 300); // 300 seconds = 5 minutes
ini_set("pcre.backtrack_limit", "100000000"); // default 100k = "100000"

似乎解决了这个问题,但我不知道这是否是唯一的方法:确实,如果文件很大(17MB或更高),浏览器的响应时间会有一点(我在Firefox上测试)最新的),在页面加载完成之前...将整个文件解析成块,直到它的完整大小,但是,如何做到这一点?

非常感谢您的关注和帮助

1 个答案:

答案 0 :(得分:1)

要解决您的问题,通常的方法是计算检索到的匹配项 - 如果它们小于键 - 继续循环而不重新初始化$loop_dic

我建议你使用倒置方法:不是逐行爆炸,而是在检索值之前用空格替换换行:你的字符串结构足够坚固以允许这种方法,你知道字段号,所以这种方法应该有效。

foreach循环外的代码不会发生变化。以同样的方式,检索由BEGIN ... END包装的文本的代码不受影响:

foreach( $loops as $key => $value ) 
{
    $value = trim( $value );
    $pattern = array( "/[[:blank:]]+/", "/BEGIN(.*)/", "/END(.*)/" );
    $replacement = array( " ", "", "" );
    $value = preg_replace( $pattern, $replacement, $value );

要检索密钥,我们使用preg_match_all(),然后删除preg_replace()的相对行:

    preg_match_all( '/^#\d+/m', $value, $matches );
    $keys = $matches[0];

    $value = preg_replace( '/^#\d+\s*/m', '', $value );

现在,$value我们只有数据线。我们用空格替换所有换行符:

    $value = str_replace( "\n", " ", $value );

然后,我们通过重复键编号的字段模式来构造行模式,并按preg_match_all()检索所有行:

    $pattern = '/'.str_repeat( "('[^']+'|\S+)\s+", count( $keys ) ).'/';
    preg_match_all( $pattern, $value, $matches );

最后,我们使用array_slice()删除全局匹配项,我们将其与$keys结合使用,我们得到了期望的结果。可以关闭foreach循环:

    $values = array_combine( $keys, array_slice( $matches, 1 ) );
}

ideone demo

我的$values与您的$loop_dic之间的主要区别在于$values主阵列中有列,但如果您喜欢按行排列数组,则可以轻松转换它。 / p>

我已经使用许多不同的“断线”测试了代码,并且它可以工作。我建议你用不同的字符串仔细测试它,看看它在任何情况下都能正常工作。