在PHP中如何检测字符串中的分隔符?

3
我想知道如果你有一个字符串,如何检测分隔符?
我们知道php可以使用explode()函数将一个字符串拆分成多个部分,需要提供一个分隔符参数。
但是,在将其发送到explode函数之前,有没有一种方法可以检测分隔符呢?
目前,我只是将字符串输出给用户,让他们输入分隔符。这样做没问题,但我希望应用程序可以为我进行模式识别。
对于这种字符串模式识别,我应该使用正则表达式吗?
编辑:我最初未能指定可能存在的分隔符集。CSV文件中可能使用任何分隔符,但更有可能使用以下字符之一:逗号、分号、竖线和空格。
编辑2:这是我为“确定的分隔符”想出的可行解决方案。
$get_images = "86236058.jpg 86236134.jpg 86236134.jpg";

    //Detection of delimiter of image filenames.
        $probable_delimiters = array(",", " ", "|", ";");

        $delimiter_count_array = array(); 

        foreach ($probable_delimiters as $probable_delimiter) {

            $probable_delimiter_count = substr_count($get_images, $probable_delimiter);
            $delimiter_count_array[$probable_delimiter] = $probable_delimiter_count;

        }

        $max_value = max($delimiter_count_array);
        $determined_delimiter_array = array_keys($delimiter_count_array, max($delimiter_count_array));

        while( $element = each( $determined_delimiter_array ) ){
        $determined_delimiter_count = $element['key'];
        $determined_delimiter = $element['value'];
        }

        $images = explode("{$determined_delimiter}", $get_images);

分隔符长什么样? - Pekka
@Alex,你所要求的听起来相当不切实际,如果有任何东西可以作为分隔符。检测分隔符的模式匹配是一项非常棘手的任务。 - Pekka
1
你根据这个规范想出的任何东西都将是O(n^c)。其中n是所有可能的分隔符集合,c是字符串长度。丑陋... - Jason McCreary
1
你可以构建一个启发式系统,尝试从可能的分隔符列表中识别出分隔符(,;/| 都是常见的)。根据需要,你可以对文件中字符出现的次数进行计数分析(毕竟,我认为每行至少会出现几次分隔符)... - ircmaxell
@Alex,您能否提供更多详细信息?例如输入数据和您期望的可能分隔符。 - Gordon
显示剩余2条评论
5个回答

9
确定您认为可能的分隔符(例如;|),并对每个分隔符在字符串中出现的次数进行搜索(使用substr_count)。然后选择出现最多的作为分隔符,并使用explode函数进行分割。
尽管这种方法可能不是绝对可靠的,但在大多数情况下应该能够正常工作 ;)

这很容易失败。如果我的内容包含大量的,,,,, ;;;;; ||||||会怎么样? - Pekka
如果只是为了实验目的,那么可以作为一个开始。否则,这样的结构将会导致你的系统崩溃。 - markus
5
一种选择是,如果计数高于一个或者它们接近在一起,就逐行遍历文件并计算出现次数。最稳定的数字(只相差1)最有可能是分隔符... - ircmaxell
@Pekka:一切都取决于预期输入的数据。例如,如果您应该输入标签,那么标签名称很可能不会包含“,”或“;”。 - NikiC
4
如果我选择作为分隔符,但输入了类似于巴巴多斯,白俄罗斯,巴西; 加拿大,中国,刚果,古巴的字符串,那么实际上只有一个分隔符,但是另一个常见的逗号,出现了五次。在这种情况下,选择出现最多次数的那个会得到错误的结果。 - stevelove
显示剩余4条评论

3

我觉得这个方法在99.99%的情况下都有效 :) 基本思路是,每行有效分隔符的数量应该相同。 该脚本计算所有行之间的分隔符计数差异。 差异越小,说明有效分隔符的可能性越大。

将所有内容放在一起,该函数读取行并将其作为数组返回:

function readCSV($fileName)
{
    //detect these delimeters
    $delA = array(";", ",", "|", "\t");
    $linesA = array();
    $resultA = array();

    $maxLines = 20; //maximum lines to parse for detection, this can be higher for more precision
    $lines = count(file($fileName));
    if ($lines < $maxLines) {//if lines are less than the given maximum
        $maxLines = $lines;
    }

    //load lines
    foreach ($delA as $key => $del) {
        $rowNum = 0;
        if (($handle = fopen($fileName, "r")) !== false) {
            $linesA[$key] = array();
            while ((($data = fgetcsv($handle, 1000, $del)) !== false) && ($rowNum < $maxLines)) {
                $linesA[$key][] = count($data);
                $rowNum++;
            }

            fclose($handle);
        }
    }

    //count rows delimiter number discrepancy from each other
    foreach ($delA as $key => $del) {
        echo 'try for key=' . $key . ' delimeter=' . $del;
        $discr = 0;
        foreach ($linesA[$key] as $actNum) {
            if ($actNum == 1) {
                $resultA[$key] = 65535; //there is only one column with this delimeter in this line, so this is not our delimiter, set this discrepancy to high
                break;
            }

            foreach ($linesA[$key] as $actNum2) {
                $discr += abs($actNum - $actNum2);
            }

            //if its the real delimeter this result should the nearest to 0
            //because in the ideal (errorless) case all lines have same column number
            $resultA[$key] = $discr;
        }
    }

    var_dump($resultA);

    //select the discrepancy nearest to 0, this would be our delimiter
    $delRes = 65535;
    foreach ($resultA as $key => $res) {
        if ($res < $delRes) {
            $delRes = $res;
            $delKey = $key;
        }
    }

    $delimeter = $delA[$delKey];

    echo '$delimeter=' . $delimeter;

    //get rows
    $row = 0;
    $rowsA = array();
    if (($handle = fopen($fileName, "r")) !== false) {
        while (($data = fgetcsv($handle, 1000, $delimeter)) !== false) {
            $rowsA[$row] = Array();
            $num = count($data);
            for ($c = 0; $c < $num; $c++) {
                $rowsA[$row][] = trim($data[$c]);
            }
            $row++;
        }
        fclose($handle);
    }

    return $rowsA;
}

1

我有同样的问题,我正在处理来自各种数据库的大量CSV文件,这些文件是由不同的人以不同的方式提取到CSV中的,有时对于相同的数据集每次都不同... 我只是在我的转换基类中实现了一个像这样的函数

protected function detectDelimiter() {
    $handle = @fopen($this->CSVFile, "r");
    if ($handle) {
        $line=fgets($handle, 4096);
        fclose($handle);            

        $test=explode(',', $line);
        if (count($test)>1) return ',';

        $test=explode(';', $line);
        if (count($test)>1) return ';';

        //.. and so on
    }
    //return default delimiter
    return $this->delimiter;
}

这并不总是好的,例如: 测试你的大脑,伙计;好的;假的;真的 会失败... - Павел Иванов

-2

我遇到了同样的问题。我的系统将从客户端接收CSV文件,但它可以使用";"、","或" "作为分隔符,我想改进系统,使客户端不必知道哪个是(他们从来不知道)。

我搜索并找到了这个库: https://github.com/parsecsv/parsecsv-for-php

非常好用且易于使用。


这种方法有很多问题,如果所有分隔符出现的次数都一样怎么办?如果唯一匹配的是零怎么办?那意味着整行只有一列吗?也许你可以搜索CSV库或插件并使用它。 - jean
是的,你说得对。最终我使用了这个库来解决我的问题:https://github.com/parsecsv/parsecsv-for-php - Rick Sander

-2
我做了类似这样的东西:
$line = fgetcsv($handle, 1000, "|");
if (isset($line[1]))
    {
    echo "delimiter is: |";
    $delimiter="|";
    }
    else
    {
    $line1 = fgetcsv($handle, 1000, ";");
    if (isset($line1[1]))
        {
        echo "delimiter is: ;";
        $delimiter=";";
        }
        else
        {
        echo "delimiter is: ,";
        $delimiter=",";
        }
    }

这只是检查读取一行后是否有第二列。


你的方法或许很有趣,但最好还是用循环实现一下 ;) - Ali Alwash

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接