PHP - strtok(), associative array relationship - php

I'm pretty familiar with the Strtok() function in PHP, and I have had no problem getting the function to work properly for strings in the past. However, I currently have to read a .csv text file (which I've done successfully) where each line is made of 6 fields like so: last name, first name, address, city, district, postal code\r\n <--carriage return and linefeed at the end
I have to use Strok() to split these by the delimiters and token the words as fields (i.e. last, first, address, etc.). I plan to use an associative array using the last name as the primary key so that I can plug the data into an HTML Table, which is created and working. My issue right now is splitting the file correctly, as it has about 200 lines made of those 6 fields, and storing the strings as fields properly for an array, so the data structure is where I'm having some issues. Here's what I have so far:
$inputFile = fopen("input.csv","r");
$delimiters = ",";
$token = strtok($inputFile, $delimiters);
$n=1;
while ($token){
echo "Token $n: $token <br>";
$token = strtok($delimiters);
$n++;
}
Obviously, the table is created below it but since I haven't done the data structure quite yet, I don't have the fields for it. I think my token loop may be incorrect for this issue, but I pulled some from an earlier example in my book and an exercise I did where my token process worked but the file structure was different. Thanks for any direction or help on this.

There are CSV functions in PHP, like fgetcsv, so it really is the wrong approach to reinvent the wheel.
Note that in your code you don't actually read the content of the file, as you only get a file pointer.
If you really need to do this with strtok, and your CSV is simple, in the sense that it does not have quoted strings, which could have embedded delimiter characters, you could use:
file_get_contents() to read the file content in one string. Of course, file() would make it easier for you, as it would already split lines. But I assume that if CSV functions are not allowable for you, then this will neither.
strtok for getting the fields, but at the end of the loop, not at the start, since the initial call with the double arguments already retrieves the first value before the loop.
Code:
$input = file_get_contents("input.csv");
$delimiters = ",\n\r";
$token = strtok($input, $delimiters);
$result = [];
$row = [];
while ($token){
echo "Token $token <br>";
$row[] = $token;
if (count($row) == 6) { // write record
$result[] = $row;
$row = [];
}
$token = str_replace('\r', '', strtok($delimiters));
}
print_r($result);
Note that this does not create an associative array. If you need that, then use this code:
$columns = ['last', 'first', 'address1', 'address2', 'address3', 'zip'];
and then in your loop, replace $row[] = $token by:
$row[$columns[count($row)]] = $token;
You can see that version run on eval.in. The output for the data you provided in comments is:
Array (
[0] => Array (
[last] => SELBY
[first] => AARON
[address1] => 1519 Santiago de los Caballeros Loop
[address2] => Mwene-Ditu
[address3] => East Kasai
[zip] => 22025
)
[1] => Array (
[last] => GOOCH
[first] => ADAM
[address1] => 230 Urawa Drive
[address2] => Adoni
[address3] => Andhra Pradesh
[zip] => 2738
)
)
Again, this is not advisable. You should use fgetcsv. That also deals better with strings that could have commas, double quotes or even newlines in them.

Well, I was going to skip this question because fgetcsv(), but I was bored:
$lines = file($inputFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
$delimiters = ",";
foreach($lines as $line) {
$values = array(strtok($line, $delimiters));
while($token = strtok($delimiters)){
$values[] = $token;
}
$result[] = $values;
}
Read the file lines into an array
Loop to get each line and put the first token of the line into a values array
Loop the line and get all tokens and add to values array
Add values array to result array
I added an array_combine() because you said something about an associative array. You can use something like this if needed:
$result[] = array_combine(array('last name',
'first name',
'address',
'city',
'district',
'postal code'), $values);
If you wanted last name to be the key for each result line, which is not advisable as keys are unique and I don't think you can guarantee last names being unique:
$result[$values[0]] = $values;
//or to remove it from the array but use as the key
$result[array_unshift($values)] = $values;

Related

How to make key value by explode and arrange matching key values into one key?

I am recently facing a practical problem.I am working with ajax form submission and there has been some checkboxes.I need all checkboxes with same name as key value pair.Suppose there is 4 checkboxes having name attribute =checks so i want something like $arr['checks'] = array(value1, value2, ...)
So i am getting my ajax $_POST code as suppose like: name=alex&checks=code1&checks=code2&checks=code3
I am using below code to make into an array
public function smdv_process_option_data(){
$dataarray = array();
$newdataarray = array();
$new = array();
$notices = array();
$data = $_POST['options']; // data received by ajax
$dataarray = explode('&', $data);
foreach ($dataarray as $key => $value) {
$i = explode('=', $value);
$j = 1;
if(array_key_exists($i[0], $newdataarray)){
if( !is_array($newdataarray[$i[0]]) ){
array_push($new, $newdataarray[$i[0]]);
}else{
array_push($new, $i[1]);
}
$newdataarray[$i[0]] = $new;
}else{
$newdataarray[$i[0]] = $i[1];
}
}
die($newdataarray);
}
Here i want $newdataarray as like below
array(
'name' => 'alex',
'checks => array(code1, code2, code3),
)
But any how I am missing 2nd value from checks key array.
As I see it you only need to do two explode syntaxes.
The first on is to get the name and here I explode on & and then on name= in order to isolate the name in the string.
The checks is an explode of &checks= if you omit the first item with array_slice.
$str = 'name=alex&checks=code1&checks=code2&checks=code3';
$name = explode("name=", explode("&", $str)[0])[1];
// alex
$checks = array_slice(explode("&checks=", $str), 1);
// ["code1","code2","code3"]
https://3v4l.org/TefuG
So i am getting my ajax $_POST code as suppose like: name=alex&checks=code1&checks=code2&checks=code3
Use parse_str instead.
https://php.net/manual/en/function.parse-str.php
parse_str ( string $encoded_string [, array &$result ] ) : void
Parses encoded_string as if it were the query string passed via a URL and sets variables in the current scope (or in the array if result is provided).
$s = 'name=alex&checks=code1&checks=code2&checks=code3';
parse_str($s, $r);
print_r($r);
Output
Array
(
[name] => alex
[checks] => code3
)
You may think this is wrong because there is only one checks but technically the string is incorrect.
Sandbox
You shouldn't have to post process this data if it's sent correctly, as that is not included in the question, I can only make assumptions about it's origin.
If your manually creating it, I would suggest using serialize() on the form element for the data for AJAX. Post processing this is just a band-aid and adds unnecessary complexity.
If it's from a source outside your control, you'll have to parse it manually (as you attempted).
For example the correct way that string is encoded is this:
name=alex&checks[]=code1&checks[]=code2&checks[]=code3
Which when used with the above code produces the desired output.
Array
(
[name] => alex
[checks] => Array
(
[0] => code1
[1] => code2
[2] => code3
)
)
So is the problem here, or in the way it's constructed...
UPDATE
I felt obligated to give you the manual parsing option:
$str = 'name=alex&checks=code1&checks=code2&checks=code3';
$res = [];
foreach(explode('&',$str) as $value){
//PHP 7 array destructuring
[$key,$value] = explode('=', $value);
//PHP 5.x list()
//list($key,$value) = explode('=', $value);
if(isset($res[$key])){
if(!is_array($res[$key])){
//convert single values to array
$res[$key] = [$res[$key]];
}
$res[$key][] = $value;
}else{
$res[$key] = $value;
}
}
print_r($res);
Sandbox
The above code is not specific to your keys, which is a good thing. And should handle any string formatted this way. If you do have the proper array format mixed in with this format you can add a bit of additional code to handle that, but it can become quite a challenge to handle all the use cases of key[] For example these are all valid:
key[]=value&key[]=value //[key => [value,value]]
key[foo]=value&key[bar]=value //[key => [foo=>value,bar=>value]]
key[foo][]=value&key[bar][]=value&key[bar][]=value //[key => [foo=>[value]], [bar=>[value,value]]]
As you can see that can get out of hand real quick, so I hesitate to try to accommodate that if you don't need it.
Cheers!

Removing duplicates from merged array

I've been doing here some name processor, and I've run into small, kind of noob-ish problem.
I have CSV file with names and status, filters them only by 'Cool Ones" status, then i'm querying SQL, and getting another list of names that i have entered manually.
So here is code example, where i'm taking CSV file, filter, querying SQL, then it creates array, merges it and sorts alphabetically.
$nameFile = "names/$eid.csv";
$content = array_map('str_getcsv', file($nameFile));
$filteredData = array_filter($content, function($v){
return $v['1'] === 'Cool Ones'; },ARRAY_FILTER_USE_BOTH); //because in this file there are also 'Not Cool Ones'
$freePeople = array();
$sth = $DBcon->prepare("SELECT guestName, guestType FROM guestList WHERE forEvent = '$eid' ORDER BY 'guestName'");
$sth->execute();
$result2 = $sth->fetchAll(PDO::FETCH_NUM);
$listNames = array();
foreach($result2 as $row) {
$listNames[] = $row['0'];
$freeGuestName = $row['0'];
$freeGuestType = $row['1'];
}
$merged = array_merge($filteredData, $result2);
$sortedGuests = usort($merged, "sortGuestNames");
so my problem lies, that when outputing array, I'm getting duplicate results,
[50] => Array
(
[0] => John Down
[1] => Best Ones
)
[51] => Array
(
[0] => John Down
[1] => Cool Ones
)
Dunno what's next - i want that if my queried name is same as in this first CSV file, then hide this one, and show mine.
i was trying to unset key with
foreach($merged[$i]['0'] as $key => $value) {
if (in_array($merged[$i]['0'], $value)) {
unset($merged[$i]['0'][$key]);
}
}
but no luck, still outputing duplicates.
You can suggest better approach.
I've thought - maybe open CSV, query SQL and find my manual names - look up in opened CSV fields, append my status there, merge and push them to SQL database or new CSV file, where it could be outputted.
Thanks a lot!
A few things,
The thing we need to do is merge both arrays, but control which one overwrites the other. I am not sure if what you have does that now (in a reliable way) but one way to do that is to build 2 arrays. Both with the same structure, and the key as your unique field so we want this:
$csv = ['John Down' => ['John Down','Best Ones']];
$db = ['John Down' => ['John Down','Cool Ones']];
Then when we do array merge, the second argument will overwrite the first. So if we do
$csv = ['John Down' => ['John Down','Best Ones']];
$db = ['John Down' => ['John Down','Cool Ones']];
print_r(array_merge($csv, $db));
echo "\n";
print_r(array_merge($db, $csv));
Output:
// print_r(array_merge($csv, $db));
Array
(
[John Down] => Array
(
[0] => John Down
[1] => Cool Ones
)
)
//print_r(array_merge($db, $csv))
Array
(
[John Down] => Array
(
[0] => John Down
[1] => Best Ones
)
)
Sandbox
As you can see we can control which array is overwritten by the order we send them to array_merge in. The second one (or right one) overwrites the one to the left. So simply it reads from left to right.
So now what's the easiest way to get that structure from the DB? In PDO we can use FETCH_GROUP which takes the first column in the query and uses it as the top level key.
$sth = $DBcon->prepare("SELECT guestName, guestType FROM guestList WHERE forEvent = :eid GROUP BY guestName ORDER BY guestName");
//-- add `GROUP BY guestName` we don't want duplicates anyway
//-- no quotes see: ... ORDER BY 'guestName');
//-- use prepared statements
$sth->execute(['eid'=>$eid]);
$result2 = $sth->fetchAll(PDO::FETCH_NUM);
$result2 = array_column($result2, null, 0);
For the CSV you can build it that way when you read the file (by adding the key) and using fgetcsv or you can use this trick (also used above):
$csv = [['John Down','Best Ones']];
print_r(array_column($csv, null, 0));
Output
Array
(
[John Down] => Array
(
[0] => John Down
[1] => Best Ones
)
)
Sandbox
Which should give you basically what we need, then it's a simple matter of using array_merge.
One thing to mention is if your DB or CSV are not unique, you'll get some duplicate removal there too, you may have to account for.
Removing duplicates is fine, but you want to make sure to remove the correct duplicates in a repeatable and robust way. Using array_merge we can control that no mater the order the rows come in from the DB and the file.
Summery
So if we put this all together, this is all you should need:
$nameFile = "names/$eid.csv";
$content = array_map('str_getcsv', file($nameFile));
$filteredData = array_filter($content, function($v){
return $v['1'] === 'Cool Ones';
},ARRAY_FILTER_USE_BOTH); //because in this file there are also 'Not Cool Ones'
$sth = $DBcon->prepare("SELECT guestName, guestType FROM guestList WHERE forEvent = :eid GROUP BY guestName ORDER BY guestName");
$sth->execute(['eid'=>$eid]);
$result2 = $sth->fetchAll(PDO::FETCH_NUM);
$listNames = array_column($result2, 0);
$merged = array_merge(array_column($filteredData, null, 0), array_column($result2, null, 0));
$sortedGuests = usort($merged, "sortGuestNames");
So instead of adding code in patching over an issue, we went to the root cause and fixed it there and reduced the code by a few lines. This will work provided your CSV is in the correct format. guestName, guestType
Cheers!
http://php.net/manual/en/function.array-column.php
array_column ( array $input , mixed $column_key [, mixed $index_key = NULL ] ) : array
array_column() returns the values from a single column of the input, identified by the column_key. Optionally, an index_key may be provided to index the values in the returned array by the values from the index_key column of the input array.
input A multi-dimensional array or an array of objects from which to pull a column of values from. If an array of objects is provided, then public properties can be directly pulled. In order for protected or private properties to be pulled, the class must implement both the __get() and __isset() magic methods.
column_key The column of values to return. This value may be an integer key of the column you wish to retrieve, or it may be a string key name for an associative array or property name. It may also be NULL to return complete arrays or objects (this is useful together with index_key to reindex the array).
index_key The column to use as the index/keys for the returned array. This value may be the integer key of the column, or it may be the string key name. The value is cast as usual for array keys (however, objects supporting conversion to string are also allowed).
Assuming that you need unique user name, following is the solution.
Create a new blank users array.
Loop over the users array.
Append the users to new users array.
The key should be user name.
Hence, every time the same user comes, he will overwrite the previous one, removing duplicate.
Code:
$users = [
['John Down', 'Best Ones'],
['John Down', 'Cool Ones']
];
$newUsers = [];
if (! empty($users)) {
foreach ($users as $user) {
$newUsers[$user[0]] = $user[1];
}
}
echo '<pre>';print_r($newUsers);echo '</pre>';
// Output:
Array
(
[John Down] => Cool Ones
)
I solved my case:
I removed second key from merged array, then unserialize it, and mapped only unique ones! Everything is now working!
$input = array_map("unserialize", array_unique(array_map("serialize", $merged)));
Sometimes i really enjoy asking help for you, because it makes me think! To think deeper than ussual.

Splitting up an array formed by an api in PHP

I'm working on some stat tracking code for a game (yes, it's runescape. sue me).
I want to pull information from the high scores using an api which produces an array looking like this (captured using print_r)
Array
(
[getHiscore] => Array
(
[overall] => Array
(
[rank] => 61995
[lvl] => 2273
[totalxp] => 193310588
)
[attack] => Array
(
[rank] => 93406
[lvl] => 97
[totalxp] => 11747494
)
...and so on.
My question is how can i take what this api gives me and place it into a database table; I want to get an array such as this for a particular user and update their stats with it.
Would i use explode? It seems like the right idea to me but how would i actually use it to split the separate numbers and words?
My database is not totally realised yet, however each record will include a user name and then the level and total xp in each of 27 "skills". This almost certainly not best practice for database design but i'm as novice as they come so it's the best i can do.
You don't need to explode. It's an array and therefore it's already exploded. You need to iterate through it to build meaningful queries.
I'm not familiar with runescape so I don't know if your example is showing just the relevant fields of you need to discard some part. If it's the former, then you should try something like
$query="insert into stats_table (description, rank, lvl, totalexp) values (:description, :rank, :lvl, :totalext);";
$Statement = $DBConn->prepare($query);
foreach($fullArray as $description=>$subArray) {
$Statement->bindValue(':description',$description, PDO::PARAM_STR);
foreach($subArray as $key=>$value) {
$Statement->bindValue($key,$value, PDO::PARAM_STR);
}
$Statement->execute();
}
That will insert two rows containing
overall 61995 2273 193310588
attack 93406 97 11747494
PD: $DBConn is a PDO connection instance to whatever DB engine you're using.
Let's say this is all stored in the variable $ary:
$ary['getHiscore']['overall']['rank'] = 61995;
$ary['getHiscore']['overall']['lvl'] = 2273;
$ary['getHiscore']['overall']['totalxp'] = 193310588;
$ary['getHiscore']['attack']['rank'] = 93406;
$ary['getHiscore']['attack']['lvl'] = 97;
$ary['getHiscore']['attack']['totalxp'] = 11747494;
So, to get those values, it's:
$attackLevel = $ary['getHiscore']['attack']['lvl'];
If you
echo $attackLevel;
it would be 97.
Just pull the arrays out with a foreach loop and write some sql to insert the data. I don't know anything about your DB so if you want help there update your question please.
Simplest way to do this:
foreach($highscores as $highScoreType => $highScoreValues){
$rank = $highScoreValues['rank'];
$lvl = $highScoreValues['lvl'];
$totalXp = $highScoreValues['totalxp'];
//then it's up to you to put it into a table somehow.
}
$highScoreType will contain the values 'overall','attack',etc
This format of the foreach loop is extremely handy when iterating over an associative array (hash map/hash table, which is actually what all PHP arrays are underneath) will allow you to not only grab the value of the current pointed to entry, but also the key for that entry.
So, assume we have a single entry hashmap with key 'foo' and value 'bar'. If I want to show 'bar' I would simple
echo $map['foo'];//will output 'bar'
and if I put this into the above type of foreach loop
foreach($map as $fooKey => $barValue) {
echo $fooKey;//outputs 'foo'
echo $barValue;//outputs 'bar'
}

choosing a specific value from a series of values array php

I have an array which looks like this:
I need to have the array in this format for use later in the script.
//this is only 1 values set there are others that are returned.
Array
(
[DealerName] => Auto Bavaria Midrand MINI
[CustomersCounted] => 16
[Satisfied_Y] => 10
[Satisfied_N] => 6
[InterviewDate] => 2012-01-13
)
I have called the array $customerSatisfactionRatings which I loop through.
foreach($customerSatisfactionRatings as $customerSatisfactionRating) {
$content .= $customerSatisfactionRating';
}
This returns the correct values into the content variable.
What I am interested in is creating a string from the [Satisfied_Y] key.
an example of what I need is $content = '10,5,15,7,8,9,0,3';
I know how to make the string, but not how to extract only the [Satisfied_Y] key.
This makes me sad.
You use a mapping function to pull every Satisfied_Y column out of each $customerSatisfactionRatings item and then you join the results together:
$content = join(',', array_map(function($item) {
return $item['Satisfied_Y'];
}, $customerSatisfactionRatings));
This assumes that each item in $customerSatisfactionRatings is an array as described in your question.
See also: array_map()
I think it will work. Try this,
$satisfyY = array();
foreach($customerSatisfactionRatings as $customerSatisfactionRating) {
$content .= $customerSatisfactionRating;
$satisfyY = $customerSatisfactionRating['Satisfied_Y'];
}
Then implode it to make a string.
$sat_Y = implode(",", $satisfyY);
foreach($customerSatisfactionRatings as $temp)
if (isset($temp['Satisfied_Y']))
{
if (isset($content)) $content.=','.$temp['Satisfied_Y'];
else $content=$temp['Satisfied_Y'];
}

Change the array KEY to a value from sub array

This is the set of result from my database
print_r($plan);
Array
(
[0] => Array
(
[id] => 2
[subscr_unit] => D
[subscr_period] =>
[subscr_fee] =>
)
[1] => Array
(
[id] => 3
[subscr_unit] => M,Y
[subscr_period] => 1,1
[subscr_fee] => 90,1000
)
[2] => Array
(
[id] => 32
[subscr_unit] => M,Y
[subscr_period] => 1,1
[subscr_fee] => 150,1500
)
)
How can I change the $plan[0] to $plan[value_of_id]
Thank You.
This won't do it in-place, but:
$new_plan = array();
foreach ($plan as $item)
{
$new_plan[$item['id']] = $item;
}
This may be a bit late but I've been looking for a solution to the same problem. But since all of the other answers involve loops and are too complicated imho, I've been trying some stuff myself.
The outcome
$items = array_combine(array_column($items, 'id'), $items);
It's as simple as that.
You could also use array_reduce which is generally used for, well, reducing an array. That said it can be used to achieve an array format like you want by simple returning the same items as in the input array but with the required keys.
// Note: Uses anonymous function syntax only available as of PHP 5.3.0
// Could use create_function() or callback to a named function
$plan = array_reduce($plan, function($reduced, $current) {
$reduced[$current['id']] = $current;
return $reduced;
});
Note however, if the paragraph above did not make it clear, this approach is overkill for your individual requirements as outlined in the question. It might prove useful however to readers looking to do a little more with the array than simply changing the keys.
Seeing the code you used to assemble $plan would be helpful, but I'm going assume it was something like this
while ($line = $RES->fetch_assoc()) {
$plan[] = $line;
}
You can simply assign an explicit value while pulling the data from your database, like this:
while ($line = $RES->fetch_assoc()) {
$plan[$line['id']] = $line;
}
This is assuming $RES is the result set from your database query.
In my opinion, there is no simpler or more expressive technique than array_column() with a null second parameter. The null parameter informs the function to retain all elements in each subarray, the new 1st level keys are derived from the column nominated in the third parameter of array_column().
Code: (Demo)
$plan = array_column($plan, null, 'id');
Note: this technique is also commonly used to ensure that all subarrays contain a unique value within the parent array. This occurs because arrays may not contain duplicate keys on the same level. Consequently, if a duplicate value occurs while using array_column(), then previous subarrays will be overwritten by each subsequent occurrence of the same value to be used as the new key.
Demonstration of "data loss" due to new key collision.
$plans = array();
foreach($plan as $item)
{
$plans[$item['id']] = $item;
}
$plans contains the associative array.
This is just a simple solution.
$newplan = array();
foreach($plan as $value) {
$id = $value["id"];
unset($value["id"]);
$newplan[$id] = $value;
}

Categories