【问题标题】:Determine .csv delimiter in PHP在 PHP 中确定 .csv 分隔符
【发布时间】:2013-02-03 13:49:51
【问题描述】:

注意:我首先要说我知道我可能遗漏了一些非常明显的东西。我正处于一种编码迷雾中,看不到简单的解决方案。

问题:我用 PHP 编写了一个脚本来解析 .csv 文件,选择包含电子邮件地址的列,并将它们放入数据库。现在,我发现用户正在尝试上传具有 .csv 文件类型但实际上不是逗号分隔的文件。我正在尝试编写一个能够正确确定分隔符(制表符、换行符、空格等)的函数,但是遇到了一些问题。我想我想得到一个包含所有这些地址的数组,这样键的数量就会增加对该分隔符的信任。

代码:

$filename = "../some/path/test.csv";   
if (($handle = fopen($fileName, "r")) !== FALSE) {
    $delimiters = array(',', ' ', "\t", "\n");
    $delimNum = 0;
    foreach ($delimiters as $delimiter) {
      $row = 0;
      while (($data = fgetcsv($handle, 1000, $delimiter)) !== FALSE) {
        $data = (string)$data[0];
        $delimiterList[$delimNum] = explode($delimiter, $data);
        $row++;
    }
    $delimNum++;
}
die(print_r($delimiterList));
}

结果:

Array
(
[0] => Array
    (
        [0] => email
peter.parker@example.com
atticus.finch@example.com
steve.rogers@example.com
phileas.fogg@example.com
s.winston@example.com
paul.revere@example.com
fscott.fitzgerald@example.com
jules.verne@example.com
martin.luther@example.com
ulysses.grant@example.com
tony.stark@example.com
    )
)

就像我说的,我知道这可能是处理这个问题的错误方法,所以我很感谢您提供的任何见解!

【问题讨论】:

  • 您可以随时添加另一个输入,允许用户指定分隔符。
  • 我认为@Supericy 的想法是最好的,这似乎不是你可以轻易确定的,除非你有某种基础开始。

标签: php parsing csv delimiter fgetcsv


【解决方案1】:

用可用性而不是代码来解决这个问题。让用户选择分隔符。

但是,由于他们可能不知道制表符分隔、CSV 等的含义,因此只需向他们展示预览即可。他们可以从选项中进行选择,直到输出看起来正确且呈表格形式。

然后你根据选择的格式进行解析。

【讨论】:

    【解决方案2】:

    我将展示一个可能是一个很好的解决方案的算法,不要认为这个问题很容易,这就像猜测,所以这个问题不会有一个完美的解决方案。

    人们应该尝试使用统计数据或其他一些启发式方法来接近 99% 的良好解决方案。我是一名计算机科学家,也是一名开发人员,但这是机器学习或数据科学家给出的近似值。

    这里是:

    1. 从文件的所有行中随机选择一些行,比如 10
    2. 统计每个候选分隔符的出现次数
    3. 用这个数字计算每个分隔符的平均值和方差。
    4. 规范化数字,这意味着给出 0 到 1 之间的数字,using your custom linear function
    5. weigths to the two values for each delimiters and sum,这会给出每个分隔符的分数,您可以将其用作决定

    看起来很复杂,但却是一个相当不错且不难的算法。下面是一个计算示例:

    分隔符计数(直方图)

    |         | ; | , | \t  |
    |---------|---|---|-----|
    | LINE 1  | 3 | 1 |  13 |
    | LINE 2  | 2 | 1 |   0 |
    | LINE 3  | 3 | 1 |   0 |
    | LINE 4  | 3 | 1 | 124 |
    | LINE 5  | 2 | 1 |   2 |
    | LINE 6  | 2 | 1 |   2 |
    | LINE 7  | 3 | 1 |  12 |
    | LINE 8  | 3 | 1 |   0 |
    | LINE 9  | 3 | 1 |   0 |
    | LINE 10 | 3 | 1 |   0 |
    

    计算和最终得分

    |            |  ;   |  ,   |  \t  |  | WEIGHTS |  ;   |  ,   | \t |
    |------------|------|------|------|--|---------|------|------|----|
    | AVERAGE    |  2,7 |    1 | 15,3 |  |         |      |      |    |
    | NORMALIZED | 0,17 | 0,06 |    1 |  | 1       | 0,17 | 0,06 |  1 |
    | VARIANCE   | 0,21 |    0 | 1335 |  |         |      |      |    |
    | NORMALIZED | 0,99 |    1 |    0 |  | 3       | 2,99 |    3 |  0 |
    |            |      |      |      |  | SCORE   | 3,17 | 3,06 |  1 |
    

    如您所见,分隔符 ';'有更好的分数。我认为加权方差比发现的分隔符的平均值也好。更有可能有一个文件,其中分隔符在每一行中变化不大。

    【讨论】:

      【解决方案3】:

      这不是一个完美的解决方案,但它可能会帮助你 - 如果你不能问分隔符是什么。

      不再尝试解析为 CSV,而是尝试检索有效的电子邮件地址。我不认为空格、逗号、制表符或换行符是有效的电子邮件部分,对吧? (谁知道;)在using regular expressions to validate email 上查看这个讨论 - 这样你就可以看到这个解决方案的一些缺陷。

      但是,我会使用 preg_match_all() 编写正则表达式,并以有效的电子邮件格式检索所有字符串的列表。

      祝你好运!

      【讨论】:

        【解决方案4】:

        手册中的SplFileObject::getCsvControl

        我发现太晚了,所以写了一个运行良好的函数。 如果有用/感兴趣,我的方法是:

        我使用$handle$ColName 参数和$ColName 可选

        $ColName 允许您检查哪个分隔符在第一条记录中找到预期的标题列名称,如果 csv 文件有标题行。

        如果没有标题行,或者您不知道列名,它会使用默认检查:哪个分隔符会找到同一记录的大多数字段(这通常是正确的)。然后,我还检查该分隔符是否为接下来的几行中的每一行返回相同数量的字段。

        fgetcsv 似乎在块中工作,并强制每条记录具有与块中的最大值相同的字段数,因此即使每条记录的字段数不同,这也可以工作

        【讨论】:

        • 请注意,此函数不会神奇地从给定文件中猜测 CSV 控件,而是返回先前使用 SplFileObject::setCsvControl() 设置的内容。
        【解决方案5】:

        这是我的解决方案。 如果您知道您期望有多少列,它就可以工作。 最后,分隔符是 $actual_separation_character

        $separator_1=",";
        $separator_2=";";
        $separator_3="\t";
        $separator_4=":";
        $separator_5="|";
        
        $separator_1_number=0;
        $separator_2_number=0;
        $separator_3_number=0;
        $separator_4_number=0;
        $separator_5_number=0;
        
        /* YOU NEED TO CHANGE THIS VARIABLE */
        // Expected number of separation character ( 3 colums ==> 2 sepearation caharacter / row )
        $expected_separation_character_number=2;  
        
        
        $file = fopen("upload/filename.csv","r");
        while(! feof($file)) //read file rows
        {
            $row= fgets($file);
        
            $row_1_replace=str_replace($separator_1,"",$row);
            $row_1_length=strlen($row)-strlen($row_1_replace);
        
            if(($row_1_length==$expected_separation_character_number)or($expected_separation_character_number==0)){
            $separator_1_number=$separator_1_number+$row_1_length;
            }
        
            $row_2_replace=str_replace($separator_2,"",$row);
            $row_2_length=strlen($row)-strlen($row_2_replace);
        
            if(($row_2_length==$expected_separation_character_number)or($expected_separation_character_number==0)){
            $separator_2_number=$separator_2_number+$row_2_length;
            }
        
            $row_3_replace=str_replace($separator_3,"",$row);
            $row_3_length=strlen($row)-strlen($row_3_replace);
        
            if(($row_3_length==$expected_separation_character_number)or($expected_separation_character_number==0)){
            $separator_3_number=$separator_3_number+$row_3_length;
            }
        
            $row_4_replace=str_replace($separator_4,"",$row);
            $row_4_length=strlen($row)-strlen($row_4_replace);
        
            if(($row_4_length==$expected_separation_character_number)or($expected_separation_character_number==0)){
            $separator_4_number=$separator_4_number+$row_4_length;
            }
        
            $row_5_replace=str_replace($separator_5,"",$row);
            $row_5_length=strlen($row)-strlen($row_5_replace);
        
            if(($row_5_length==$expected_separation_character_number)or($expected_separation_character_number==0)){
            $separator_5_number=$separator_5_number+$row_5_length;
            }
        
        } // while(! feof($file))  END
        fclose($file);
        
        /* THE FILE ACTUAL SEPARATOR (delimiter) CHARACTER */
        /* $actual_separation_character */
        
        if ($separator_1_number==max($separator_1_number,$separator_2_number,$separator_3_number,$separator_4_number,$separator_5_number)){$actual_separation_character=$separator_1;}
        else if ($separator_2_number==max($separator_1_number,$separator_2_number,$separator_3_number,$separator_4_number,$separator_5_number)){$actual_separation_character=$separator_2;}
        else if ($separator_3_number==max($separator_1_number,$separator_2_number,$separator_3_number,$separator_4_number,$separator_5_number)){$actual_separation_character=$separator_3;}
        else if ($separator_4_number==max($separator_1_number,$separator_2_number,$separator_3_number,$separator_4_number,$separator_5_number)){$actual_separation_character=$separator_4;}
        else if ($separator_5_number==max($separator_1_number,$separator_2_number,$separator_3_number,$separator_4_number,$separator_5_number)){$actual_separation_character=$separator_5;}
        else {$actual_separation_character=";";}
        
        /* 
        if the number of columns more than what you expect, do something ...
        */
        
        if ($expected_separation_character_number>0){
        if ($separator_1_number==0 and $separator_2_number==0 and $separator_3_number==0 and $separator_4_number==0 and $separator_5_number==0){/* do something ! more columns than expected ! */}
        }
        

        【讨论】:

          猜你喜欢
          • 2018-08-20
          • 2015-11-25
          • 2015-08-09
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-08-14
          相关资源
          最近更新 更多