counting row values with different ids in multidimensional array - php

I know there are a lot of topics about this, but just couldn't find any that would actually help me solve the problem.
Ok so i am having a problem with counting rows and values in multidimensional array (imported via .csv)
I am trying to achieve something like...
Array ( [0] => Array ( [0] => 1 [1] => 6278,31 [2] => 4)
[1] => Array ( [0] => 2 [1] => 2,0 [2] => 2)
[2] => Array ( [0] => 3 [1] => 3,01 [2] => 3)
)
where [0] would be buyer id, 1 is total number of buys and [2] total cost.
My current code:
if(($s_open = fopen("sales.csv", "r")) !== FALSE)
{
$s_count = count(file("sales.csv"));
while(($sales = fgetcsv($s_open, 100, ";")) !== FALSE)
{
...
}
fclose($s_open);
}
Thanks in advance.

You could group your data by creating an array with the buyer_id as index.
Each time you find a new buyer, you could create a small array ('id','count','amount').
Then, sum the values of the current row into that array.
const KEY_ID = 0; // shortcut for array indexes
const KEY_AMOUNT = 1;
const KEY_COUNT = 2;
$buyers = []; // final array
if (($s_open = fopen("sales.csv", "r")) !== FALSE)
{
$s_count = count(file("sales.csv"));
while(($sales = fgetcsv($s_open, 100, ";")) !== FALSE)
{
// Extract data from row:
[$id, $buyer_id, , $amount] = $sales;
// If the buyer doesn't exists in the array,
// create a new array with required format:
if (!isset($buyers[$buyer_id])) {
$buyers[$buyer_id] = [
KEY_ID => $buyer_id,
KEY_COUNT => 0,
KEY_AMOUNT => 0,
];
}
// Update values:
$buyers[$buyer_id][KEY_COUNT]++;
$buyers[$buyer_id][KEY_AMOUNT] += $amount;
}
fclose($s_open);
}
// Re-index values:
$buyers = array_values($buyers);

Using this mock data in.csv:
id, buyer_id, amount
1, 1, 100.55
2, 1, 500.1
3, 2, 50.55
4, 3, 1.0
I wrote the following PHP to parse it into a nicely formatted array (if you don't want to have named keys, you can just remove the header logic):
<?php
// Each line into an array
$csv = file(__DIR__ . DIRECTORY_SEPARATOR . 'in.csv');
// First line is the header
$header = str_getcsv(array_shift($csv));
// When the CSV header is `id, buyer_id, amount`, remove the whitespace
$header = array_map(function ($item) {
return trim($item);
}, $header);
// Get the CSV data as an array of arrays
$out = array_map(function ($line) use ($header) {
$data = str_getcsv($line);
return array_combine($header, $data);
}, $csv);
print_r($out);
Which prints the following to your screen:
Array
(
[0] => Array
(
[id] => 1
[buyer_id] => 1
[amount] => 100.55
)
[1] => Array
(
[id] => 2
[buyer_id] => 1
[amount] => 500.1
)
[2] => Array
(
[id] => 3
[buyer_id] => 2
[amount] => 50.55
)
[3] => Array
(
[id] => 4
[buyer_id] => 3
[amount] => 1.0
)
)

Try this
$data = [];
if(($s_open = fopen("sales.csv", "r")) !== FALSE) {
$s_count = count(file("sales.csv"));
while( ([$id, $buyer_id, $amount] = fgetcsv($s_open, 100, ";")) !== FALSE ){
if( $id !== 'id' ){
$data[$buyer_id] = [
$buyer_id,
($data[$buyer_id][1] ?? 0) + 1,
($data[$buyer_id][2] ?? 0) + $amount
];
}
}
fclose($s_open);
$data = array_values($data);
}
// print_r($data);

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)

Parse diverse array of numbers for value and number

I am using PHP 7.3.5 and I have the following set of array values:
$valueArr = ['-4.2%', '51.0', '90K', '0.5%', '0.74|2.6', '-1.2B', '779B', '215K', '92.2%', '42.8B', '1.49T', '1690B', '-10.8B', '0.38|3.9', '102.4', '1.00%', '0.07|1.3'];
Basically I want for each of these values the number and the "type", so if it is a percentage then I would like to get -4.2 and percentage.
I tried to create a minimum example (however the below code is no real good example ;( ), but I am stuck at the data structure level as some array keys have two inputs, such as '0.74|2.6':
<?php
$valueArr = ['-4.2%', '51.0', '90K', '0.5%', '0.74|2.6', '-1.2B', '779B', '215K', '92.2%', '42.8B', '1.49T', '1690B', '-10.8B', '0.38|3.9', '102.4', '1.00%', '0.07|1.3'];
$resArr = array();
$structureArr = array(
'value1' => "",
'number1' => "",
'value2' => "",
'number2' => ""
);
foreach ($valueArr as $key => $v) {
if (1 === preg_match('/%/', $valueArr[$key])) {
preg_match('!\d+\.*\d*!', $valueArr[$key], $structureArr['number1']);
$structureArr['value1'] = 'percentage';
}
/*
if (1 === preg_match('|', $valueArr[$key])) {
$str = explode("|", $valueArr[$key]);
$value1 = 'number';
$number1 = $str[0];
$value2 = 'number';
$number2 = $str[1];
}
if (1 === preg_match('', $valueArr[$key])) {
}
*/
array_push($resArr, $structureArr);
}
print_r($resArr);
/*
Wanted Result
Array
(
[0] => Array
(
[0] => -4.2
[1] => 'percentage'
)
[1] => Array
(
[0] => 51.0
[1] => 'number'
)
[2] => Array
(
[0] => 90000
[1] => number
)
[3] => Array
(
[0] => 0.5
[1] => percentage
)
[4] => Array
(
[0] => 0.74
[1] => number
[2] => 2.6
[3] => number
)
...
*/
I would highly appreciate your input on how to structure this array input.
Appreciate your replies!
If you join the array on a space and replace pipes | with a space, then you have a list of numbers and their symbol (if any) separated by a space. Then just match your numbers and whatever symbol comes after it. Then you just match the number index with the symbol index. I used an array to map the symbol to the word and number if none:
$string = str_replace('|', ' ', implode(' ', $valueArr));
preg_match_all('/([\d.-]+)([^\s]*)/', $string, $matches);
$types = ['%'=>'percent','K'=>'thousand','M'=>'million','B'=>'billion','T'=>'trillion'];
foreach($matches[1] as $k => $v) {
$t = $types[$matches[2][$k]] ?? 'number';
$result[] = [$v, $t];
}
This yields an array like this, with each number that was joined by a pipe with it's own element:
Array
(
[0] => Array
(
[0] => -4.2
[1] => percent
)
[1] => Array
(
[0] => 51.0
[1] => number
)
[2] => Array
(
[0] => 90
[1] => thousand
)
///etc...
If you need a floating point number then just change:
$result[] = [(float)$v, $t];
This expands on my comment. Not sure if it's the most optimal solution or not.
Rough outline...
Create array mapping suffix to multiplier. Loop through source array. explode on |. Loop through result. If last character is %, strip it, value=value and type=percentage, else, strip last char, use it as array index (if it is an available index), value=value*multiplier and type=number.
$resArr = array();
$multipliers = array("K" => 1000, "M" => 1000000, "B" => 1000000000, "T" => 1000000000000);
$valueArr = ['-4.2%', '51.0', '90K', '0.5%', '0.74|2.6', '-1.2B', '779B', '215K', '92.2%', '42.8B', '1.49T', '1690B', '-10.8B', '0.38|3.9', '102.4', '1.00%', '0.07|1.3'];
foreach($valueArr as $index => $value)
{
$parts = explode("|", $value);
$resArr[$index] = array();
foreach($parts as $part)
{
$lastChar = substr($part, -1);
if($lastChar == "%")
{
$resArr[$index][] = substr($part, 0, -1);
$resArr[$index][] = "percentage";
}
else if(in_array($lastChar, array_keys($multipliers)))
{
$multiple = $multipliers[$lastChar];
$resArr[$index][] = (substr($part, 0, -1))*$multiple;
$resArr[$index][] = "number";
}
else
{
$resArr[$index][] = $part;
$resArr[$index][] = "number";
}
}
}
var_dump($resArr);
DEMO

Dynamic associative Array - list, count, sum, min, max

I've got an array with about 40 keys. I'd like to have a small function that returns a summary array.
Right now I've got the following that works:
foreach ($all_data as $value){
$new_array[ $value['location'] ][ $value['manufacturer'] ][ $value['model'] ] += 1;
}
This returns an array with everything I need. However, the location, manufacturer and model could be changed up for a bunch of other values.
what I am trying to do is have something simple as:
$new_array = summarize($all_data,array('location','manufacturer','model','count'),array('list','list','list','count') );}
where this summarize function would build the call. I think I just need a bit of help on how to get it to run the string as code for this array. Otherwise I get
$current_selection = "[ $row_item['location'] ][ $row_item['manufacturer'] ][ $row_item['model'] ]"
$return_array{$current_selection} += 1;
Where the end goal is to have a function like:
function summarize($data_array, $fields_array, $process_array){
//data_array = associative multi-dimensional data array
//fields = values to pull from the data_array
//process = array specifying whether to list, sum, count, average, max, min
$return_array = array();
$current_selection = "";
foreach($fields_array as $field){
$current_selection .= '[ $row_item[\'' . $field . '\'] ]';
}
foreach ($data_array as $row_item){
//dynamic = DOES NOT WORK
$return_array[$current_selection] += 1;//eval? create function? abstract?
//another attempt
${'return_array' . $current_selection} += 1;
//Manual = Does work
//$return_array[ $row_item['location'] ][ $row_item['manufacturer'] ][ $row_item['model'] ] += 1;
}
}
Thanks for any help on how to do an indirect reference.
JC
RESOLUTION
The final version that managed to resolve this looks like the following, thanks to user: check, for getting me on the correct path.
function summarize($data_array, $fields_array, $process_array){
$return_array = array();
$i = 0;
foreach ($data_array as $row){
$ii = 0;
$temp = array();
$temp2 = array();
foreach($fields_array as $key=>$field){
if($process_array[$ii] == 'list') $temp[$ii] = $row[$field];
if($process_array[$ii] == 'count') $temp2[$ii] = 1;
if($process_array[$ii] == 'sum') $temp2[$ii] = $row[$field];
$ii++;
}
$unique = true;
$ii = 0;
foreach($return_array as $row2){
if(array_intersect_key($row2,$temp) == $temp){//$row2 == $temp){
$unique = false;
break;
}
$ii++;
}
if($unique){
$return_array[$i] = $temp;
if(!empty($temp2)) $return_array[$i] = array_merge($temp,$temp2);
$i++;
}else{
if(!empty($temp2)){
foreach($temp2 as $key => $value){
if($process_array[$key] == 'sum') $temp2[$key] = $return_array[$ii][$key] + $value;
if($process_array[$key] == 'count') $temp2[$key] = $return_array[$ii][$key] + 1;
if($process_array[$key] == 'max') $temp2[$key] = ($return_array[$ii][$key] < $value) ? $value : $return_array[$ii][$key];
if($process_array[$key] == 'min') $temp2[$key] = ($return_array[$ii][$key] > $value) ? $value : $return_array[$ii][$key];
//TODO:(JC) 'average' - need to create a count field if not present (or always despite and assume overhead of extra computations).
// - then just calculate the 'sum' and divide by the counter as a last step before returning the array.
}
$return_array[$ii] = array_merge($temp,$temp2);
}
}
}
print_r($return_array);
return $return_array;
}
Which gives the following result:
/*
CALL: summarize($data,array('location','manufacturer','model','model','volume','colourvolume'),array('list','list','list','count','sum','sum') );
[0] = location
[1] = manufacturer
[2] = model
[3] = model count
[4] = mono volume sum
[5] = colour volume sum
*/
Array
(
[0] => Array
(
[0] =>
[1] => HP
[2] => LaserJet 4000
[3] => 3
[4] => 3000
[5] => 0
)
...
[17] => Array
(
[0] => Room 114
[1] => CANON
[2] => iR3235
[3] => 1
[4] => 4012
[5] => 0
)
[18] => Array
(
[0] => Room 115
[1] => LEXMARK
[2] => T652
[3] => 1
[4] => 20
[5] => 0
)
)
alternatively, if I assume that's $field_array contains sequentially key fields from root to sub key, you can loop your $field_array within $data_array loop
function summarize($data_array, $fields_array, $process_array){
$return_array = array();
foreach ($data_array as $row){
$temp = array();
foreach($fields_array as $key=>$field){
$temp = $key==0?$row[$field]:$temp[$field];
}
if(!empty($temp)) $return_array[] = $temp;
}
return $return_array;
}
and this is my array, will summarize with these function
$array = array(
array("multi"=>array("dimensional"=>array("array"=>"foo1"))),
array("multi"=>array("dimensional"=>array("array"=>"foo2"))),
array("multi"=>array("dimensional"=>array("array"=>"foo3"))),
array("multi"=>array("dimensional"=>array("array"=>"foo4"))),
array("multi"=>array("dimensional"=>array("array"=>"foo5"))),
array("multi"=>array("dimensional"=>array("array"=>"foo6"))),
array("multi"=>array("dimensional"=>array("array"=>"foo7"))),
array("multi"=>array("dimensional"=>array("array"=>"foo8"))),
array("multi"=>array("dimensional"=>array("array"=>"foo9")))
);
print_r(summarize($array,array("multi","dimensional","array"),NULL));
Ouput
Array ( [0] => foo1 [1] => foo2 [2] => foo3 [3] => foo4 [4] => foo5 [5] => foo6 [6] => foo7 [7] => foo8 [8] => foo9 )

Loop through a CSV file

How can i do a foreach with a CSV file.
Suppose i have fopen a csv file which looks like so ---
Array
(
[0] => Category ID
[1] => Category
[2] => Country
[3] => Original price
[4] => Reduce price
)
Array
(
[0] => 11
[1] => Phone cases
[2] => Denmark
[3] => 90,99
[4] => 120
)
Array
(
[0] => 13
[1] => Shoes
[2] => Denmark
[3] => 180,99
[4] => 200
)
So how can i loop through all the data's in store them in a array.
I have tried like so, but it does not work.
$all_data = array();
foreach ($result as $key => $obj) {
$doc = array();
$doc['id'] = $obj[$key];
$doc['category'] = $obj[$key];
$doc['country'] = $obj[$key];
$doc['original_price'] = $obj[$key];
$doc['reduce_price'] = $obj[$key];
// array push all documents into $all_data
$all_data [] = $doc;
}
Anyone knows how i can loop through those data!
Just using the PHP documentation :
if (($handle = fopen("yourfile.csv", "r")) !== FALSE) {
$all_data = array();
while (($data = fgetcsv($handle, 1000, ",")) !== FALSE) {
if($i==0){ continue; $i=1; }
// Remove the first iteration as it's not "real" datas
$all_data[] = array(
'id' => $data[0],
'category' => $data[1],
'country' => $data[2],
'original_price' => $data[3],
'reduce_price' => $data[4],
);
}
fclose($handle);
}
It must achieve what you want. It's always better using the built-in functions. If you can't use this one, let me know.
EDIT :
As #Blag said :
you can use to file_get_contents() and str_getcsv()
But this is a different approach that'll load the full file in a string (file_get_contents) and parse the string in the second time (str_getcsv).
This should give you an idea of how to proceed :
$objsOut=array();
foreach ($result as $key => $obj) {
$objOut = array(
'id'=>$obj[0]
);
$objsOut[]=$objOut;
}

Need some help making a php search tree

Im making a tree to store words and an associated number array in php. I need it to look something like this:
Words: apple, ant
[a] => Array
(
[p] => Array
(
[p] => Array
(
[l] => Array
(
[e] => Array
(
[0] => Array
(
[0] => 0
[1] => 0
[2] => 1
[3] => 2
[4] => 3
[5] => 4
)
)
)
)
)
[n] => Array
(
[t] => Array
(
[0] => Array
(
[0] => 0
[1] => 1
[2] => 2
[3] => 0
[4] => 0
[5] => 4
)
)
)
)
Of course apple and ant need to share the same [a] index. Im close, but I cant figure out how to properly keep track of the tree index so 'apple' gets into the tree fine but 'ant' is inserted as 'nt'. Heres my code at the moment:
private function insertWordsIntoTree()
{
foreach ($this->words as $word)
{
$characters = preg_replace('/[0-9]+/', '', $words);
$points = $this->getPoints($word);
$this->tree = $this->buildTree($characters, $points, $this->tree);
}
print_r($this->tree);
}
private function buildTree($characters, array $points, array $tree)
{
for ($i = 0; $i < strlen($characters); $i++)
{
$character = $characters[$i];
$remaining_characters = substr($characters, $i + 1);
if (strlen($characters) === 1)
{
$child = [];
$child[$character] = [$points];
return $child;
}
elseif (!isset($tree[$character]))
{
$tree[$character] = $this->buildTree($remaining_characters, $points, []);;
break;
}
else
{
$this->buildTree($remaining_characters, $points, $tree[$character]);
}
}
return $tree;
}
Im pretty sure the problem is at the else statement...I dont think Im keeping track of the current tree index properly. Any help would be much appreciated.
Here's a simple approach that passes the recursion off to php:
$tree = array();
foreach($words as $word) {
$characters = array_reverse(str_split($word));
$temp = array();
foreach($characters as $index => $character) {
if($index == 0) {
$temp[] = getPoints($word);
}
$temp = array(
$character => $temp
);
}
$tree = array_merge_recursive($tree, $temp);
}

Categories