将 CSV 处理为带有键列标题的数组

Posted

技术标签:

【中文标题】将 CSV 处理为带有键列标题的数组【英文标题】:Process CSV Into Array With Column Headings For Key 【发布时间】:2012-04-28 04:36:23 【问题描述】:

我有一个 CSV,第一行包含字段名称。示例数据是...

"Make","Model","Note"
"Chevy","1500","loaded"
"Chevy","2500",""
"Chevy","","loaded"

我需要将我的数据格式化为键值对数组,其中键名是列标题。我想第 1 行应该是这样的:

$array = [
    "Make" => "Chevy",
    "Model" => "1500",
    "Note" => "loaded"
];

...第 2 行...

$array = [
    "Make" => "Chevy",
    "Model" => "1500",
    "Note" => ""
];

...和第 3 行...

$array = [
    "Make" => "Chevy",
    "Model" => "",
    "Note" => "loaded"
];

除了静态之外,我不确定如何执行此操作 - 问题是具有相关数据的列可能会从一个文件更改为下一个文件...重新排列、删除或添加列。

非常感谢您的想法。

【问题讨论】:

你可能是指第二个数组示例中的 2500 【参考方案1】:
$all_rows = array();
$header = fgetcsv($file);
while ($row = fgetcsv($file)) 
  $all_rows[] = array_combine($header, $row);

print_r($all_rows);

【讨论】:

感谢您的及时回复。尽管我最终得到了键中的数据,但这很接近。在返回的任何数组中都看不到任何列标题。 @BitBucket:如果您转储$all_rows 中的数据,您应该会看到一个包含以标题数据作为键的子数组的数组。 请注意,您需要在第一次创建 $header 时运行它,以确保您给任何没有标题的列提供像 "unknown" 这样的虚拟数据。 $x 或数组组合将是不同的长度【参考方案2】:

php 已经在SplFileObject 中提供了您所需的 99.9%,您可以通过扩展它来添加缺少的 0.1%。在以下示例中,CSVFile 从它扩展而来:

$csv = new CSVFile('../data/test.csv');

foreach ($csv as $line)

    var_dump($line);

使用您的示例数据:

array(3) 
  ["Make"]=>  string(5) "Chevy"
  ["Model"]=> string(4) "1500"
  ["Note"]=>  string(6) "loaded"

array(3) 
  ["Make"]=>  string(5) "Chevy"
  ["Model"]=> string(4) "2500"
  ["Note"]=> string(0) ""

array(3) 
  ["Make"]=>  string(5) "Chevy"
  ["Model"]=> string(0) ""
  ["Note"]=>  string(6) "loaded"

CSVFile 定义如下:

class CSVFile extends SplFileObject

    private $keys;

    public function __construct($file)
    
        parent::__construct($file);
        $this->setFlags(SplFileObject::READ_CSV);
    

    public function rewind()
    
        parent::rewind();
        $this->keys = parent::current();
        parent::next();
    

    public function current()
    
        return array_combine($this->keys, parent::current());
    

    public function getKeys()
    
        return $this->keys;
    

如果你这样做,细节就会被很好地封装掉。此外,在current() 函数中处理错误(例如计数不匹配)更容易,因此使用数据的代码不需要处理它。

编辑:

但是,给出的示例在可重用性方面很短。与其从 SplFileObject 扩展,不如聚合它:

class KeyedArrayIterator extends IteratorIterator

    private $keys;

    public function rewind()
    
        parent::rewind();
        $this->keys = parent::current();
        parent::next();
    

    public function current()
    
        return array_combine($this->keys, parent::current());
    

    public function getKeys()
    
        return $this->keys;
    

代码相同,但省略了封装在构造函数中的细节。这种减少允许更广泛地使用该类型,例如使用(但不仅限于)上述 SplFileObject

$file = new SplFileObject('../data/test.csv');
$file->setFlags($file::READ_CSV);

$csv = new KeyedArrayIterator($file);

foreach ($csv as $line) 
    var_dump($line);

如果现在听起来太冗长,可以再次对其进行包装以再次为其提供更好的外观:

class CSVFile extends KeyedArrayIterator

    /**
     * @param string $file
     */
    public function __construct($file)
    
        parent::__construct(new SplFileObject($file));
        $this->setFlags(SplFileObject::READ_CSV);
    

由于 TraversableIterator 的标准修饰能力,CSVFile 的第一个示例中的原始构造函数代码可以被 100% 复制。

最后的添加还允许保持使用 CSVFile 迭代器的原始代码完整:

$csv = new CSVFile('../data/test.csv');

foreach ($csv as $line) 
    var_dump($line);

所以只需快速重构以允许更多代码重用。您可以免费获得一个 KeyedArrayIterator

【讨论】:

为了感兴趣,你能想出一种方法来制作这个无标题的 CSV 吗? 这很简单:省略 rewind 函数并在构造函数中传递键。如果您需要更多的灵活性,我已经将一些代码放入带有示例的要点中,但它仍然是非常好的 alpha:gist.github.com/4153380 刚刚测试过这个,我喜欢这个想法,但它的性能似乎比 fgetcsv 像这里 ***.com/questions/4801895/csv-to-associative-array @giorgio79:好吧,如果您不想将整个文件存储在内存中,这种基于迭代器的方法更有用。因此,它与速度无关,而与内存有关。所以只是展示替代方法,每一种都有它的优点/缺点。 我知道这是一篇很老的帖子,但我最近在一个项目中使用它并取得了巨大的成功,所以谢谢。我遇到的一个问题是无法使用 SKIP_EMPTY 标志。有没有办法添加它?【参考方案3】:
$csv_data = array_map('str_getcsv', file('Book.csv'));// reads the csv file in php array
$csv_header = $csv_data[0];//creates a copy of csv header array
unset($csv_data[0]);//removes the header from $csv_data since no longer needed
foreach($csv_data as $row)
    $row = array_combine($csv_header, $row);// adds header to each row as key
    var_dump($row);//do something here with each row

【讨论】:

【参考方案4】:
function processCsv($absolutePath)

    $csv = array_map('str_getcsv', file($absolutePath));
    $headers = $csv[0];
    unset($csv[0]);
    $rowsWithKeys = [];
    foreach ($csv as $row) 
        $newRow = [];
        foreach ($headers as $k => $key) 
            $newRow[$key] = $row[$k];
        
        $rowsWithKeys[] = $newRow;
    
    return $rowsWithKeys;

【讨论】:

【参考方案5】:

在这一点上,我假设您已经解决了这个问题,但我想我会提出一个建议的方法来解决这个问题,可能不是最好/最优雅的解决方案,但它可以解决问题:

$row = 1;
$array = array();
$marray = array();
$handle = fopen('file.csv', 'r');
if ($handle !== FALSE) 
    while (($data = fgetcsv($handle, 0, ',')) !== FALSE) 
        if ($row === 1) 
            $num = count($data);
            for ($i = 0; $i < $num; $i++) 
                array_push($array, $data[$i]);
            
        
        else 
            $c = 0;
            foreach ($array as $key) 
                $marray[$row - 1][$key] = $data[$c];
                $c++;
            
        
        $row++;
    
    echo '<pre>';
    print_r($marray);
    echo '</pre>';

【讨论】:

【参考方案6】:

试试这个代码:

$query = "SELECT * FROM datashep_AMS.COMPLETE_APPLICATIONS";
$export= mysql_query($query);
$first = true;
$temp = $export[0];
//echo "<pre>"; print_r($first); exit;

header('Content-Type: text/csv');
header('Content-Disposition: attachment; filename=file.csv');
header('Pragma: no-cache');
header("Expires: 0");

$outstream = fopen("php://output", "w");



foreach($export as $result)

    if($first)
        $titles = array();
        foreach($temp as $key=>$val)
            $titles[] = $key;
        
        //print_r ($titles);exit;
        fputcsv($outstream, $titles);
    
    $first = false;
    fputcsv($outstream, $result);


fclose($outstream);

谢谢

【讨论】:

【参考方案7】:

试试这个

$csv = array_map("str_getcsv", file('file.csv', FILE_SKIP_EMPTY_LINES));    
$header = array_shift($csv); // get header from array

foreach ($csv as $key => $value)     
    $csv[$key] = array_combine($header, $value);
    var_dump($csv[$key]['Model']);


var_dump($csv);

【讨论】:

【参考方案8】:

array_combine() 函数仅在标题列与数据列匹配时才有效,否则将引发错误。

【讨论】:

这应该是对现有答案的评论,而不是自己的答案? 谢谢,但认为这可能有助于集中解决方案;在这个主题上已经是一个紧张的地方 - 有这么多的答案。我只是为了避免混淆而提到它。许多自称为“专家”的 PHP 已经发布了使用 array_combine 函数的解决方案,但没有注意到其中有流。我的解决方案是编辑 CSV 文件头以匹配数据,然后在使用 array_combine 之前存储输出... @Pellumb:除非是故意的错误。如果您正在运行大多数(如果不是全部)示例(即使是那些不使用 array_combine() ,但尤其是那些没有的)没有正确处理 CSV 文件/缓冲区/流中实际标题列之前的 cmets。另一个常见问题是末尾有一个(或多个)终止符行(array_combine() 也会用错误消息突出显示)。【参考方案9】:

在上面蒂姆库珀的回答中,而不是

$all_rows = array();
$header = null;
while ($row = fgetcsv($file)) 
    if ($header === null) 
        $header = $row;
        continue;
    
    $all_rows[] = array_combine($header, $row);

我会以更优雅、更高效的方式编写代码:

$rows = null;
$header = fgetcsv($file);
while ($row = fgetcsv($file)) 
    $rows[] = array_combine($header, $row);

【讨论】:

以上是关于将 CSV 处理为带有键列标题的数组的主要内容,如果未能解决你的问题,请参考以下文章

使用 pandas 读取带有 numpy 数组的 csv

使用 pandas 读取带有 numpy 数组的 csv

为啥将 numpy 数组转换为 csv 文件不显示属性名称,而是将第一行值作为属性名称?

将数组转换为csv

将 numpy 数组转换为 CSV 字符串,将 CSV 字符串转换回 numpy 数组

VB.NET:将 CSV 文件读入二维数组