Convert complex string to array, multiple times by multiple delimiters - php

I have a string
Label 1|80|default
Label 2|100|default
---
Frontend|80|red
Backend|20|red
---
Another Item 1|20|blue
Another Item 2|20|blue
-
And another Item|20|blue
which should be converted to the following array
Array
(
[0] => Array
(
[items] => Array
(
[0] => Array
(
[label] => Label 1
[value] => 80
[color] => default
)
[1] => Array
(
[label] => Label 2
[value] => 100
[color] => default
)
)
)
[1] => Array
(
[items] => Array
(
[0] => Array
(
[label] => Frontend
[value] => 80
[color] => red
)
[1] => Array
(
[label] => Backend
[value] => 20
[color] => red
)
)
)
[2] => Array
(
[items] => Array
(
[0] => Array
(
[label] => And another Item
[value] => 20
[color] => blue
)
)
)
)
to be converted into json:
[{"items":[{"label":"Label 1","value":"80","color":"default"},{"label":"Label 2","value":"100","color":"default"}]},{"items":[{"label":"Frontend","value":"80","color":"red"},{"label":"Backend","value":"20","color":"red"}]},{"items":[{"label":"And another Item","value":"20","color":"blue"}]}]
Currently, my way of doing that string splitting to multiple arrays seems a bit complicated to me. At least, it is hard to read.
protected function convertPartsToObject($parts): array
{
$return = [];
$arr1 = explode("---", trim($parts));
foreach ($arr1 as $arr1Item) {
$arr2 = explode("-", trim($arr1Item));
foreach ($arr2 as $arr2Item) {
$arr3 = explode("\n", trim($arr2Item));
$group = [];
foreach ($arr3 as $arr3Item) {
$arr4 = explode("|", trim($arr3Item));
$item = [
'label' => $arr4[0],
'value' => $arr4[1],
'color' => $arr4[2]
];
$group['items'][] = $item;
}
}
$return[] = $group;
}
return $return;
}
Question:
Is there a better / more readable way to convert strings by multiple delimiters to an associative array? Maybe with a nested regex?
Background, just as an info:
This is part of a content management system, where I need some data as json in frontend (for react). And I don't want the editors to learn the json syntax...

This is an alternative to your current code. Instead of the nested explode() it just splits the input into lines and then processes the lines. For --- it just adds the current data into the result, - removes the current stored data(not sure if it should), else it extracts the data (I've used the ability to explode to an associative array as a shorthand, depends if you like it or not you can use your current method)...
function convertPartsToObject ( $parts ) : array {
$return = [];
$buffer = [];
foreach ( explode( PHP_EOL, $parts ) as $line ) {
if ( trim($line) == "---" ) {
$return[]['items'] = $buffer;
$buffer = [];
}
elseif ( trim($line) == "-" ) {
$buffer = [];
}
else {
[$arr4['label'], $arr4['value'], $arr4['color']] = explode("|", trim($line));
$buffer[] = $arr4;
}
}
// Add last items
$return[]['items'] = $buffer;
return $return;
}

Rather than maintaining temporary arrays to be pushed into a parent array when they are "completed" and after the loop is finished, this is an excellent case for implementing a reference variable. Think of the reference variable as a magical soccer goal -- once declared, you can close your eyes, kick your soccer ball and no matter where the ball travels, it will swish into the back of the net as desired. When you want to have a new soccer goal, just destroy the old one (unset()) and create a new one with the same technique.
This technique reduces the code length and is easily read by humans -- so long as you understand how reference variables work.
I should include a caveat, that if your input array is empty, you'll probably want an early return in your method so that the reference variable never pushes any entries into your result array.
Code: (Demo)
function stringToGroupedArray(string $string): array
{
$result = [];
$result[]['items'] = &$items;
foreach (explode(PHP_EOL, $string) as $line) {
if ($line[0] === '-') { // or however you want to identify the separator
unset($items);
$result[]['items'] = &$items;
} else {
$items[] = array_combine(['label', 'value', 'color'], explode('|', $line, 3));
}
}
return $result;
}

Related

PHP array merging, ignore certain duplicated keys and let them be included in the inner created arrays

I'm merging together arrays with the same set inner array name, changing the key value to the order number then creating further inner arrays for items that are not duplicated with this code...
function readCSV($csvFile)
{
$line_of_text = [];
$file_handle = fopen($csvFile, 'r');
//skip csv headers
//fgetcsv($file_handle);
//fgetcsv($file_handle);
fgetcsv($file_handle);
while (!feof($file_handle)) {
$tmp = fgetcsv($file_handle, 1024);
if (isset($line_of_text[$tmp[0]])) {
foreach ($tmp as $k => $v) {
if (array_key_exists($k, $line_of_text[$tmp[0]])) {
if (!is_array($line_of_text[$tmp[0]][$k])) {
$kVal = $line_of_text[$tmp[0]][$k];
$line_of_text[$tmp[0]][$k] = [];
$line_of_text[$tmp[0]][$k][] = $kVal;
}
$line_of_text[$tmp[0]][$k][] = $v;
$line_of_text[$tmp[0]][$k] = array_unique($line_of_text[$tmp[0]][$k]);
$line_of_text[$tmp[0]][$k] = array_filter($line_of_text[$tmp[0]][$k]);
if (count($line_of_text[$tmp[0]][$k]) == 1) {
$line_of_text[$tmp[0]][$k] = array_values($line_of_text[$tmp[0]][$k]);
$line_of_text[$tmp[0]][$k] = $line_of_text[$tmp[0]][$k][0];
}
if (empty($line_of_text[$tmp[0]][$k])) {
$line_of_text[$tmp[0]][$k] = null;
}
} else {
$line_of_text[$tmp[0]][$k] = null;
}
}
$line_of_text[$tmp[0]][0] = $tmp[0];
} else {
$line_of_text[$tmp[0]] = $tmp;
}
}
fclose($file_handle);
return array_filter(array_values($line_of_text));
}
// Set path to CSV file
$csvFile = 'my.csv';
$csv = readCSV($csvFile);
//$csv is your array
foreach($csv as $key => $value){
if(!array_key_exists(#$value[0],$arr)){
$arr[#$value[0]] = [];
}
$arr[#$value[0]] = array_merge($arr[#$value[0]],$value);
}
echo "<pre>";
print_r($arr);
echo '</pre>';
This turns..
Array
(
[0] => Array
(
[0] => 15304
[1] => item1
[2] => qty = 1
)
[1] => Array
(
[0] => 15304
[1] => item2
[2] => qty = 1
)
[2] => Array
(
[0] => 15305
[1] => itemX
[2] => qty = 2
)
}
into
Array
(
[15304] => Array
(
[0] => 15304
[1] => Array
(
[0]item1
[1]item2
)
[2] => qty = 1
)
[15305] => Array
(
[0] => 15305
[1] => itemX
[2] => qty = 2
)
}
So because qty = 1 is the same its getting filtered out, when what I need is..
Array
(
[15304] => Array
(
[0] => 15304
[1] => Array
(
[0]item1
[1]item2
)
[2] => Array
(
[0]qty = 1
[1]qty = 1
)
)
[15305] => Array
(
[0] => 15305
[1] => itemX
[2] => qty = 2
)
}
How can I exclude certain ones from the "remove duplicates" part so they are repeated in an inner-array as in my last example? This is needed as they are directly linked to other items with an inner array, so if for example the item1 inner array now has 6 items the qty now needs to also have all 6 items in the inner array, even if they are the same.
Your current code is good for two cases:
Reading all data as is, without modification
Reading all data and performing a universal modification
Since what you need is a conditional modification, it seems like you would be better off creating the structure of the array manually. Doing so would add another benefit: code clarity. You should always strive for descriptive code, so building your array with descriptive associative keys would make the intent of the code clearer.
Proposed solution based off the example data (rough sketch that you should tailor to your specific needs):
function readCSV($csvFile)
{
$output = [];
$fileHandle = fopen($csvFile, 'r');
$header = fgetcsv($fileHandle);
while (!feof($fileHandle)) {
$fileRow = fgetcsv($fileHandle, 1024);
$orderId = $fileRow[0];
// skip this row if it's empty (the first field contains no id)
if (empty($orderId)) {
continue;
}
/*
$fileRow[3] is "Buyer name", the first field that's present in one type of row
(the one containing common properties of the order). By checking if it's empty,
we identify the contents of the row - not empty means order row with common
properties, empty means item row with specific item properties.
*/
if (!empty($fileRow[3])) {
// no need to repeat the id inside the array - it's already stored in the key
$output[$orderId] = [
'order_number' => $fileRow[1],
'buyer_username' => $fileRow[2],
'buyer_name' => $fileRow[3],
// here you can continue explicitly adding any property you need
];
} else {
// add a new item entry
$output[$orderId]['items'][] = [
'item_number' => $fileRow[20],
'item_title' => $fileRow[21],
'quantity' => $fileRow[24],
'price' => $fileRow[25],
// here you can continue explicitly adding any property you need
];
}
}
fclose($fileHandle);
return $output;
}
Now all the items of your order are neatly stored as subarrays, each containing only data specific to that item, which makes it really easy to iterate:
foreach($orders[$orderId]['items'] as $item)

Creating an associative array from one-dimension array

Was not really sure on what question's title should be here...
Sample .csv:
tennis,soccer,sports
car,plane,things
jeans,shirt,things
My final, ideal, outcome should be an array that looks like this:
Array
(
[sports] => Array
(
[0] => tennis
[1] => soccer
)
[things] => Array
(
[0] => car
[1] => plane
[2] => jeans
[3] => shirt
)
)
Here is my most recent attempt to achieve the outcome above (after many tries):
<?php
$f_name = 'test.csv';
// Stores all csv data
$csv_data = array_map('str_getcsv', file($f_name));
$c = count($csv_data);
$tmp = array();
$data_for_email = array();
for ($i = 0; $i < $c; $i++) {
// Remove last element and make it a key
$le = array_pop($csv_data[$i]);
$tmp[$le] = $csv_data[$i];
$data_for_email = array_merge_recursive($data_for_email, $tmp); // MEMORY ERROR
}
print_r($data_for_email);
?>
This is what I get as a result:
Array
(
[sports] => Array
(
[0] => tennis
[1] => soccer
[2] => tennis
[3] => soccer
[4] => tennis
[5] => soccer
)
[things] => Array
(
[0] => car
[1] => plane
[2] => jeans
[3] => shirt
)
)
As you can see, I get duplicates of .csv's line 1 in [sports] array.
More detailed description of my requirement:
Each line has 3 fields.
3rd field becomes a key in a new associative array.
Two remaining fields (1st and 2nd) become values for that key.
Because multiple lines may (and do) contain identical 3rd field (while combination of 1st and 2nd fields are always different), I need to then merge all these duplicate keys' values into 1.
P.S. I could parse that array (to remove duplicate values) afterwards, but the real .csv file is large and it becomes too slow to process it, and I receive the following error at the line which I marked with // MEMORY ERROR:
Fatal Error: Allowed Memory Size of 134217728 Bytes Exhausted
I tried increasing the memory limit but I'd prefer to avoid this if possible.
Should be a little easier. No need for array_merge_recursive:
foreach($csv_data as $row) {
$key = array_pop($row);
if(!isset($data_for_email[$key])) {
$data_for_email[$key] = [];
}
$data_for_email[$key] = array_merge($data_for_email[$key], $row);
}
More memory efficient would be:
Not reading the whole file in memory. fgetcsv reads one line at a time
Avoiding a recursive merge
Code:
$handle = fopen($f_name, 'r');
if (!$handle) {
// Your error-handling
die("Couldn't open file");
}
$data_for_email = array();
while($csvLine = fgetcsv($handle)) {
// Remove last element and make it a key
$le = array_pop($csvLine);
if (isset($data_for_email[$le])) {
$data_for_email[$le] = array_merge($data_for_email[$le], $csvLine);
} else {
$data_for_email[$le] = $csvLine;
}
}
fclose($handle);
You just need to initialize $tmp in every loop which will resolve your problem. Check below code:
for ($i = 0; $i < $c; $i++) {
// Remove last element and make it a key
$le = array_pop($csv_data[$i]);
$tmp = []; //Reset here
$tmp[$le] = $csv_data[$i];
$data_for_email = array_merge_recursive($data_for_email, $tmp); // MEMORY ERROR
}
Hope it helps you.
Use the name for the key to get a unique list. It is cheaper than merge if there is a lot of data.:
$handle = fopen('test.csv', 'r');
$res = [];
while ($data = fgetcsv($handle)) {
list($first, $second, $type) = $data;
$res[$type] = ($res[$type] ?? []);
array_map(function($e)use(&$res, $type) {
$res[$type][$e] = $e;
}, [$first, $second]);
}
output:
Array
(
[sports] => Array
(
[tennis] => tennis
[soccer] => soccer
)
[things] => Array
(
[car] => car
[plane] => plane
[jeans] => jeans
[shirt] => shirt
)
)
i made something, too, but now the others were faster. :D
I've made it oop, it doesn't quite come out what you wanted but maybe it helps you further.
I have not come any further now, unfortunately, wanted to show it to you anyway :)
Here is your index.php ( or whatever the file is called. )
<?php
include "Data.php";
$f_name = 'in.csv';
// Stores all csv data
$csv_data = array_map('str_getcsv', file($f_name));
$c = count($csv_data);
$tmp = array();
$data_for_email = array();
foreach ($csv_data as $data){
$key = array_pop($data);
array_push($data_for_email,new Data($data,$key));
}
foreach ($data_for_email as $data){
array_push($tmp,$data->getValue());
}
foreach ($tmp as $value){
print_r($value);
echo "<br>";
}
and here the class Data:
<?php
class Data
{
private $value = [];
public function __construct($data, $key)
{
$this->value[$key]=$data;
}
/**
* #return array
*/
public function getValue()
{
return $this->value;
}
}
as output you bekome something like that:
Array ( [sports] => Array ( [0] => tennis [1] => soccer ) )
Array ( [things] => Array ( [0] => car [1] => plane ) )
Array ( [things] => Array ( [0] => jeans [1] => shirt ) )
ps:
surely there is another function that summarizes the same keys, but somehow i don't find anything now...
I hope it helps :)

Read the categories and subcategories in csv format and prepare multidimensional array

In the PHP, I need to convert the below image CSV file in this array format:
This code for creating categories and subcategories. If client will add the extra categories and subcategories its need to work as per that. So I need the dynamic one.
Array Format :
Array
(
[0] => Cateory1
[Cateory1] => Array
(
[0] => SubCategory11
[1] => SubCategory12
[2] => SubCategory13
[3] => SubCategory14
)
[1] => Cateory2
[Cateory2] => Array
(
[0] => SubCategory21
[1] => SubCategory22
[2] => SubCategory23
[SubCategory23] => Array
(
[0] => SubCategory221
[1] => SubCategory222
)
[3] => SubCategory24
)
[2] => Cateory3
[Cateory3] => Array
(
[0] => SubCategory31
[1] => SubCategory32
[2] => SubCategory33
)
[3] => Cateory4
[Cateory4] => Array
(
[0] => SubCategory41
[SubCategory41] => Array
(
[0] => SubCategory411
[SubCategory411] => Array
(
[0] => SubCategory4111
[SubCategory4111] => Array
(
[0] => SubCategory41111
)
)
)
)
)
Kindly help me to achieve this one.
Thanks.
Although i agree with Pitchinnate, I had some spare time and I hate parsing that sort of csv rubbish, so I thought I give it a try. Here is a piece of code that might get you going. I did not test or optimize anything, so if this doesn't work as expected, it might still point you in the right direction.
// I assume, $imput is a twodimensional array containing the csv data
// the multi dimensional result array
$result = array();
// accumulated categories of current-1 line
$lastCat = array();
foreach($input as $line) {
// accumulated categories of current line
$cat = array();
//First we create an array in $cat that contains the full "breadcrumbs" per line e.g.
// ['category1', 'SubCategory11', 'SubCategory114', ...]
$marker = PHP_INT_MAX;
for ($i=0; $i<count($line); $i++) {
if ($marker < $i) {
$cat[$i] = '';
} else if ($line[$i] != '') {
$cat[$i] = $line[$i];
$marker = $i;
}
}
// then using these category breadcrumbs, we create the result arrays
// you could do this using recursion if you want
$tmp = $result;
for ($i=0; $i<count($cat); $i++) {
// if we haven't seen this category yet, create it
// a bit bulky, but necessary as you want the categories with numeric indices as well
if (!isset($lastCat[$i]) || $cat[$i] != $lastCat[]) {
$tmp[] = $cat[$i];
// Check if there are still subcategories left in this line...
if (!empty($cat[$i+1])) {
//... and if so, create the category named index if not already existing
if (!array_key_exists($cat[$i], $tmp)) {
$tmp[$cat[$i]] = array();
}
$tmp = $tmp[$cat[$i]];
} else {
// ... and if not, we are finished with this line
break;
}
}
}
$lastCat = $cat;
}

Getting multi dimensional array to create new arrays based on index value

I am having a terrible time getting this to work I have been struggling with it for a couple hours now. Can someone please help me? I have included a fiddle.
I believe my problem is in this string:
$$salesAndOwner[$i]["$salesAndOwner[$i]".$l] = $salesAndOwner[$i.$l][$param] = $values[$l];
Basically I have the following multidimensional array:
[sales] => Array
(
[FirstName] => Array
(
[0] => salesFirst1
[1] => salesFirst2
)
[LastName] => Array
(
[0] => salesLast1
[1] => salesLast2
)
)
[decisionmaker] => Array
(
[FirstName] => Array
(
[0] => dmFirst1
[1] => dmFirst2
)
[LastName] => Array
(
[0] => dmLast1
[1] => dmLast2
)
)
)
I need this to be reorganized like I did with the following array:
Array
(
[additionallocations0] => Array
(
[Address] => Address1
[State] => State1
)
[additionallocations1] => Array
(
[Address] => Address2
[State] => State2
)
)
Here is the original:
Array
(
[additionallocations] => Array
(
[Address] => Array
(
[0] => Address1
[1] => Address2
)
[State] => Array
(
[0] => State1
[1] => State2
)
)
This is how I reorganize the above array:
if(isset($_POST['additionallocations'])) {
$qty = count($_POST['additionallocations']["Address"]);
for ($l=0; $l<$qty; $l++)
{
foreach($_POST['additionallocations'] as $param => $values)
{
$additional['additionallocations'.$l][$param] = $values[$l];
}
}
And this is what I am using for the sales and decisionmaker array. If you notice I have an array that contains sales and decisionmaker in it. I would like to be able to sort any future arrays by just adding its primary arrays name. I feel I am close to solving my problem but I can not get it to produce right.
$salesAndOwner = array(0 => "sales", 1 => "decisionmaker");
for($i = 0; $i < 2; $i++){
$qty = count($_POST[$salesAndOwner[$i]]["FirstName"]);
for ($l=0; $l<$qty; $l++)
{
foreach($_POST[$salesAndOwner[$i]] as $param => $values)
{
$$salesAndOwner[$i]["$salesAndOwner[$i]".$l] = $salesAndOwner[$i.$l][$param] = $values[$l];
}
}
}
In the above code I hard coded 'sales' into the variable I need it to make a variable name dynamically that contains the sales0 decisionmaker0 and sales1 decisionmaker1 arrays so $sales and $decisionmaker
I hope this makes sense please let me know if you need any more info
Let's break it down. Using friendly variable names and spacing will make your code a lot easier to read.
Remember. The syntax is for you to read and understand easily. (Not even just you, but maybe future developers after you!)
So you have an array of groups. Each group contains an array of attributes. Each attribute row contains a number of attribute values.
PHP's foreach is a fantastic way to iterate through this, because you will need to iterate through (and use) the index names of the arrays:
<?php
$new_array = array();
// For each group:
foreach($original_array as $group_name => $group) {
// $group_name = e.g 'sales'
// For each attribute in this group:
foreach($group as $attribute_name => $attributes) {
// $attribute_name = e.g. 'FirstName'
// For each attribute value in this attribute set.
foreach($attributes as $row_number => $attribute) {
// E.g. sales0
$row_key = $group_name . $row_number;
// if this is the first iteration, we need to declare the array.
if(!isset($new_array[$row_key])) {
$new_array[$row_key] = array();
}
// e.g. Array[sales0][FirstName]
$new_array[$row_key][$attribute_name] = $attribute;
}
}
}
?>
With this said, this sort of conversion may cause unexpected results without sufficient validation.
Make sure the input array is valid (e.g. each attribute group has the same number of rows per group) and you should be okay.
$salesAndOwner = array("sales", "decisionmaker");
$result = array();
foreach ($salesAndOwner as $key) {
$group = $_POST[$key];
$subkeys = array_keys($group);
$first_key = $subkeys[0];
foreach ($group[$first_key] as $i => $val) {
$prefix = $key . $i;
foreach ($subkeys as $subkey) {
if (!isset($result[$prefix])) {
$result[$prefix] = array();
}
$result[$prefix][$subkey] = $val;
}
}
}
DEMO
Try
$result =array();
foreach($arr as $key=>$val){
foreach($val as $key1=>$val1){
foreach($val1 as $key2=>$val2){
$result[$key.$key2][$key1] = $val2;
}
}
}
See demo here

most common values in a multidemensional array

My question is maybe repetitive but I really find it hard. (I have read related topics)
This is the array :
Array
(
[0] => Array
(
[legend] => 38440
)
[1] => Array
(
[bestw] => 9765
)
[2] => Array
(
[fiuna] => 38779
)
[3] => Array
(
[adam] => 39011
)
[4] => Array
(
[adam] => 39011
)
[5] => Array
(
[adam] => 39011
)
)
I have tried many ways to handle this array and find out the most common value. The result I expect is "adam"
$countwin = array_count_values($winnernames);
$maxwin = max($countwin);
$mostwinner = array_keys($countswin, $maxwin);
EDIT : My array is like this array("A"=>"1" , "B"=>"2" , "A"=>"1") it should return A is the most common
How about iterating your array and counting the values you've got?
$occurences = array();
foreach ($data as $row) {
foreach ($row as $key => $score) {
if (empty($occurences[$key])) {
$occurences[$key] = 1;
} else {
$occurences[$key]++;
}
}
}
and then sorting that
arsort($occurences);
and grabbing the first element of the sorted array
$t = array_keys($occurences);
$winner = array_shift($occurences);
You may try this code. A brute force method indeed. But a quick search made me to find this an useful one:
function findDuplicates($data,$dupval) {
$nb= 0;
foreach($data as $key => $val)
if ($val==$dupval) $nb++;
return $nb;
}
EDIT : Sorry, I misinterpreted your question! But it might be the first hint of finding the one with maximum counter.

Categories