如何从PHP中导入的CSV重新排序列标题?

时间:2018-11-27 20:28:25

标签: php

我试图对在PHP中导入的CSV文件中的列进行重新排序。订单应为ID,运营商,TrackingNumber,ShippingDate

我的代码如下:

 $rename = array('Customer PO#'=>'ID', 'Ship Via'=>'Carrier', 'Tracking Number'=>'TrackingNumber', 'Ship Date'=>'ShippingDate'); 
 $csv = array_map('str_getcsv', file('file.csv')); 

 foreach($csv[0] as $col => $colname) {
     if(!empty($rename[$colname])) $csv[0][$col] = $rename[$colname];
 }
 array_walk($csv, function(&$a) use ($csv) { 
     $a = array_combine($csv[0], $a);

     if (array_key_exists('Product ID', $a)) {
         unset($a['Product ID']);
     }
     if (array_key_exists('Customer Name', $a)) {
         unset($a['Customer Name']);
     }
 }); 

var_dump($csv);

我的数组如下:

  [1]=>
  array(4) {
    ["ShippingDate"]=>
    string(10) "11/21/2018"
    ["ID"]=>
    string(5) "59098"
    ["Carrier"]=>
    string(16) "USPS First Class"
    ["TrackingNumber"]=>
    string(22) "940011020088289578733355"
  }

3 个答案:

答案 0 :(得分:3)

重新排序任何关联数组的最佳方法是将其与默认数组合并。因此,请按以下顺序创建一个默认数组:

$default = [
   'ID' => '',
   'Carrier' => '',
   'TrackingNumber' => '',
   'ShippingDate' => ''
];

然后简单地将现有数据合并到该数组中,如下所示:

$default = [
   'ID' => '',
   'Carrier' => '',
   'TrackingNumber' => '',
   'ShippingDate' => ''
];

$data = [
   'ShippingDate' => '11/27/2018',
   'Carrier' => 'abc',
   'TrackingNumber' => 'defg',
   'ID' => '1',
];
$ordered = array_merge($default, $data);

print_r($ordered);

Test online

Bonus1

作为奖励,因为您已经拥有此数组:

 $rename = array('Customer PO#'=>'ID', 'Ship Via'=>'Carrier', 'Tracking Number'=>'TrackingNumber', 'Ship Date'=>'ShippingDate'); 

您可以使用它来创建默认数组,如下所示:

 $default = array_fill_keys($rename, '');

保持良好和干燥的状态(请勿重复)

Bonus2

作为另一项奖励,而不是取消设置这些'Product ID''Customer Name',您可以使用array_intersect_key删除它们:

$rename = array('Customer PO#'=>'ID', 'Ship Via'=>'Carrier', 'Tracking Number'=>'TrackingNumber', 'Ship Date'=>'ShippingDate');
$default = array_fill_keys($rename, '');
$data = [
   'ShippingDate' => '11/27/2018',
   'Carrier' => 'abc',
   'TrackingNumber' => 'defg',
   'ID' => '1',
   'Product ID' => '123',
   'Customer Name' => 'someguy'
];
$ordered = array_merge($default, array_intersect_key($data, $default));

print_r($ordered);

输出

Array
(
    [ID] => 1
    [Carrier] => abc
    [TrackingNumber] => defg
    [ShippingDate] => 11/27/2018
)

Sandbox

基本上array_intersect_key将返回第一个数组中所有在第二个数组中具有匹配键的元素。因为我们已经创建了$default数组,这是我们希望将结果映射出来的方式,所以我们可以利用该数组使第二个数组相交并删除默认数组中没有的任何内容。

非常简单而优雅。

其他内容

最后一个注释,不确定为什么要像这样阅读CSV:

$csv = array_map('str_getcsv', file('file.csv')); 

最好将fgetcsv与文件句柄一起使用,因为在大文件上,file函数会将整个文件读入内存,而fgetcsv则一次读取文件的每一行然后回收内​​存,使您可以处理更大的文件。如果您确实需要/想要最后一个包含所有CSV数据的大型数组,则在遍历文件时将其存储在新数组中非常简单。

Bonus3-正确/更好的方式

$h = fopen('file.csv', 'f');
// schema  [inputKey => outputKey]
$map = ['Customer PO#'=>'ID', 'Ship Via'=>'Carrier', 'Tracking Number'=>'TrackingNumber', 'Ship Date'=>'ShippingDate'];
//create a default or empty array with the keys we want
$default = array_fill_keys($map, '');
$headers = [];

while(!feof($h)){
    $data = fgetcsv($h);
    //sometimes the last line ends with a \n new line
    if(!$data) break;

    if(empty($headers)){
        //if $headers are empty we haven't set them yet
        $headers = $data;
        //-- order the map to match the headers in the file --
        //merging also patches any holes for headers not in $map
        // array combine converts [0=>'Customer PO#'] to ['Customer PO#'=>'Customer PO#']
        // array merge replaces the value with the new header from $map if it exists, and it orders $map to match the files order
        $map = array_merge(array_combine($headers, $headers), $map);
        //bail and go to next line
        continue;
    }

    //merge headers and data (be careful of missing delimiters in the file)
    //$map values are the new headers, ordered to match the headers in the file
    $data = array_combine($map, $data);

    //re-order and remove elements
    $mapped = array_merge($default, array_intersect_key($data, $default));
    print_r($mapped);
}

请注意,我无法使用我没有的csv文件进行在线测试。也就是说,我们可以通过一些固定数据和稍作修改(例如更改为foreach而不是while并转储文件内容)来对其进行测试:

//so for testing purposes only
$canned = [
    ['Product ID','Customer Name','Ship Via','Tracking Number','Ship Date','Customer PO#'],
    ['prod', 'cust', 'ship', 'track', 'date', 'po']
];

$map = ['Customer PO#'=>'ID', 'Ship Via'=>'Carrier', 'Tracking Number'=>'TrackingNumber', 'Ship Date'=>'ShippingDate'];
$default = array_fill_keys($map, '');
$headers = [];

foreach($canned as $data){
    if(empty($headers)){
        $headers = $data;
        $map = array_merge(array_combine($headers, $headers), $map);
        continue;
    }

    $data = array_combine($map, $data);
    $mapped = array_merge($default, array_intersect_key($data, $default));
    print_r($mapped);
}

输出:

Array
(
    [ID] => po
    [Carrier] => ship
    [TrackingNumber] => track
    [ShippingDate] => date
)

Sandbox

如您在上面看到的,结果按$map数组排序并“过滤”。键的顺序相同,所有“额外”元素都将被删除。另外,如果$map中缺少元素(例如项目),但文件中没有,则这些元素将以空字符串作为值(因为$default = array_fill_keys(...,''))。但是这里最大的好处是,如果我们将$canned的数据顺序更改为这样的方式(日期移到末尾):

$canned = [
    ['Product ID','Customer Name','Ship Via','Tracking Number','Customer PO#','Ship Date'],
    ['prod', 'cust', 'ship', 'track', 'po', 'date']
];

它什么都不会影响,结果是一样的。这导致我进入:

一个被忽略的大问题是,如果CSV顺序不同于$rename(在您的示例中,我将其重命名为$map),那么所有内容都会被破坏。这是因为$renamed的键与文件中的实际标头之间没有关联。
也许您的文件顺序总是相同的,或者也许没有第一行标题行(我不知道没有看到文件),但是为什么在容易解释的情况下还是有机会。

使用与我一开始显示的相同的“技巧”,我们可以对$map数组重新排序以匹配实际文件中的标题,然后使用$map的值作为我们在array_combine重命名标题。然后,删除不需要的数据(array_intersect_key())并根据$default数组(array_merge)对其进行排序很简单。我应该提到我们在循环之前创建了$default数组,该数组在重新排序$map变量并将其合并到文件之前保留了顺序。

如果您不能告诉我我在处理CSV文件方面做了大量工作...

最后

这有点困扰我,因为它表明您可能有空的标头,从将它们用作数组键的角度来看,这是有问题的:

//from you original code
foreach($csv[0] as $col => $colname) {
   if(!empty($rename[$colname])) $csv[0][$col] = $rename[$colname];
}

因此,如果您可以使用空标题,则建议添加与本部分代码相似的内容:

  //fix empty headers
  while(!feof($h)){
    //...
    if(empty($headers)){
        $headers = $data; 
        //--new code to fix empty headers
        foreach($headers as $key=>&$value){
            if(empty($value)) $value = "_empty_$key";
        }
        //--end new code
        $map = array_merge(array_combine($headers, $headers), $map);
        continue;
    }
    //...
 }

Sandbox

此操作是通过引用$headers更新&$value,任何具有假值(例如空字符串)的标头都将更新为_empty_{key},其中{key}它是自然数组索引。因此,如果第一行是这样的:

"Product ID",,"Customer Name","Ship Via","Tracking Number","Ship Date","Customer PO#",

您的$headers将(在array_combine之后)

//if we didn't add placeholder we would lose one of the empty headers 
//when doing array_combine because array keys must be unique

[
  'Product ID' => 'Product ID',
  '_empty_1' => '_empty_1',
  'Customer Name' => 'Customer Name',
  'Ship Via' => 'Ship Via',
  'Tracking Number' => 'Tracking Number',
  'Ship Date' => 'Ship Date',
  'Customer PO#' =>  'Customer PO#',
  '_empty_7' =>  '_empty_7' 
];

这为它们提供了唯一的值,因此当将它们用作键时,您不会失去位置。例如,如果您有2个空标题,并且组合了$headers(如我之前显示的那样使用array_combine),则会丢失其中一个。无需删除这些占位符,因为无论如何array_intersect_key操作都会这样做。如果可能会出现问题,这也是检查和处理重复标题的好地方。

现在,我完全重写了您的代码,请尽情享受吧!

答案 1 :(得分:1)

如果这是您的数组,那么您需要做的就是将ShippingDate作为最后一项。

为其创建一个临时值,取消设置数组值,然后再次添加使其成为最后一项。

foreach($arr as $key => $sub){
    $temp = $sub['ShippingDate'];
    unset($arr[$key]['ShippingDate']);
    $arr[$key]['ShippingDate'] = $temp;
}

或使用array_shift:

foreach($arr as &$sub){
    $sub['ShippingDate'] = array_shift($sub);
}

答案 2 :(得分:1)

为使其足够灵活以允许进行任何重新排序,我采用了$rename数组,然后翻转(使用array_flip()),以便字段名称成为您想要的键。然后只需使用array_replace()覆盖CSV文件中的值...

$input = ["ShippingDate"=>"11/21/2018",
        "ID"=> "59098",
        "Carrier"=> "USPS First Class",
        "TrackingNumber"=> "940011020088289578733355"];

$rename = array('Customer PO#'=>'ID', 'Ship Via'=>'Carrier', 'Tracking Number'=>'TrackingNumber', 'Ship Date'=>'ShippingDate');
$reorder = array_flip($rename);
$output = array_replace($reorder, $input);

print_r( $output );

要将其链接到您已经拥有的代码中...

$rename = array('Customer PO#'=>'ID', 'Ship Via'=>'Carrier', 'Tracking Number'=>'TrackingNumber', 'Ship Date'=>'ShippingDate');
$csv = array_map('str_getcsv', file('a.txt'));
$reorder = array_flip($rename);

foreach($csv[0] as $col => $colname) {
    if(!empty($rename[$colname])) $csv[0][$col] = $rename[$colname];
}
$csv[0] = array_intersect_key($reorder, $csv[0]);
array_walk($csv, function(&$a) use ($reorder, $csv) {
    $a = array_replace($reorder, 
        array_combine($csv[0], array_intersect_key($reorder, $a)));

});

var_dump($csv);