Multidimensional array sorting procedure for csv files - php

There is an array, in PHP. It is setup like so
$array_var = array(array(1,2,3,4), array(5,6,7,8), array(3,5,3,9));
This array is from a csv file obtained using the fgetcsv function. If I was to echo out the array properly to display its contents I would make it so it shows like this:
field1 field2 field3 field4
1 2 3 4
5 6 7 8
3 5 3 9
etc etc..
Now I want to sort this array. But I want to sort only one of the columns in all of the arrays. Put another way, and for example, I want to take every third value in every array inside the main array and list them ascendingly, alphabetically. So for a specific case, we would take every value in field3, from the above table, and sort it. And also make it so that the end result of the sort will rearrange the columns so that they are correctly lined up with their values.
End result
field1 field2 field3 field4
1 2 3 4
3 5 3 9
5 6 7 8
etc etc..
How can this be accomplished?
The reason for the challenge is I am trying to remove duplicates from a single column in a csv file. I think the fastest way to do it is to sort the values and look for matches in range.

It's difficult to give you an exact answer since there are things you leave out from your explanation. For example, how should lines that are sorted by one of the columns but have differences in other columns be sorted internally? Should they be sorted by some other column, be left in the original order or could they be placed in an arbitrary order?
Given the way I interpret your question, I would probably define my own class for comparisons.
<?php
class ColumnCompare {
function __construct($column) {
$this->column = $column;
}
function compare($a, $b) {
if ($a[$this->column] == $b[$this->column]) {
return 0;
}
return ($a[$this->column] < $b[$this->column]) ? -1 : 1;
}
}
// Hard-coded input
$array_var = array(array(1,2,3,4), array(5,6,7,8), array(3,5,3,9));
$sort_by_col = 2;
// Create object for sorting by a particular column
$obj = new ColumnCompare($sort_by_col);
usort($array_var, array($obj, 'compare'));
// Write CSV to standard output
$sout = fopen('php://stdout', 'w');
foreach ($array_var as $fields) {
fputcsv($sout, $fields);
}
fclose($sout);
?>
In the end you pose another question, which is impossible to answer. If you remove duplicates from a single column, what is then supposed to happen with the rest of the line? Should only one line be kept, and in that case which one? You could also by "remove duplicates" mean that you want to remove the values and put NULL in their positions. If you want that particular problem solved, you need to provide some details.
If your CSV file is really simple, and all the values on any line with duplicates are identical (which was the case in your example before it was edited), you can easily run a command such as
sort myfile.csv | uniq
but I have a feeling that it's more complicated than that. uniq also has settings to return only the duplicate lines for example. It's also possible to write commands to retrieve a certain column from each line and operate on that. But like I said, it's not possible to construct such a command without more information.

Related

Php: How to pull a value from an array in a single field in MySQL

In my database I have a table named posts. In it, the columns are:
Postid - 1
PostBody - my first post
Likers - 1,2,3,4,5,
Now what am trying to do is, in the likers field, am trying to pull the individual number without pulling all the data on the field. So if I want to retrieve likers No.3, I want the output to be 3 when I echo it out.
I have tried FIND_IN_SET(), and strstr(). All I get is the whole array, 1-5, being echoed. And because of that, I cannot match it with other variables(I.e. $var = 3;) which have the matching number.
Is there a way of doing this?
Thanx
If you really want to do it this way (read my comment first), what you can do is retrieve the comma-seperated data, explode it into a php array, and then use the array_search built-in function:
$likers_attribute_data = '1,2,3,4,5,';
$likers_array = explode(',', $likers_attribute_data);
// check if user 3 is a liker:
if (array_search('3', $likers_array) {
// he has :)
} else {
// he hasn't :(
}
Again, this is a very sloppy and inefficient way of doing business. Please just go with the solution in my comment!

Merging values of duplicate names (keys) in associative array (PHP)

Say that I have an associative array of different types of fruits in baskets and the amount in each basket ('Banana'=>50 and etc) and I need to know how many of each type of fruit there is, but I don't know the possible types of fruit before I start the loop that generates the array. This may result in duplicate entries such as:
$baskets = array(
"Apple"=>"35",
"Banana"=>"37",
"Apple"=>"43"
);
Question A: Is it even possible to make the second "Apple" entry or will it result in an error?
The answer was: Not possible, it would overwrite the first one with 43
Question B1: Assuming that the answer to A is "It's possible", how do I merge the two so that $baskets becomes ("Apple"=>"78","Banana"=>"37")?
Question B2: Assuming that the answer to A is "Not possible", how do I go about figuring out if there's already apples in the array and add the amount to the entry rather than attempting to add it as a new point in the array?
An answer to B2 will obviously make B1 obsolete even if A is "possible".
If neither B1 nor B2 are possible, then is there some other way that I can keep track of the amount of each type of fruit as I loop through the baskets?
Thanks for all of the helpful answers.
Q A: No
Q B:
if(isset($baskets["apple"]))
{
$baskets["apple"] = $baskets["apple"] + VALUE
}
You will never get the duplicate keys in an array, there can only be unique keys. You will be only overwriting values for the key of duplicate fruits.
Answer B: due to the reason mentioned above, you will have to check in your loop whether the key exists in your resulting array, and if it does, add the new value to the existing one, otherwise store the value under this key.
Answer to B1: see answer to A.
Answer to B2: array_key_exists or isset
The algorithm depends on the way your data is stored. Maybe there's a more clever way possible.
Question A: Is it even possible to make the second "Apple" entry or will it result in an error?
No, would it be so hard to try it out?
$basket['apple']=35;
$basket['apple']=43;
print $basket['apple']; // the output will be 43
The second assignment overwrites the former.
how do I merge the two
You could simply do this:
$basket['apple']+=35;
$basket['apple']+=43;
print $basket['apple'];
The problem with this is that the first time you reference an entry in the array, a PHP warning will be triggerred as it does not yet exist. While you should have your error reporting disabled (but error logging enabled) on your production system, this causes performance and support issues, hence a better solution is....
function add_to($basket, $product, $amount)
{
if (isset($basket[$product])) {
$basket[$product]+=$amount;
} else {
$basket[$product]=$amount;
}
}
add_to($basket, 'apple', 35);
add_to($basket, 'apple', 43);
print $basket['apple']; // output will be 78
It is not possible to have the same key ('Apple') more then once in an associative array. If you are looping through and come across 'Apple' twice, the second entry will override the first:
array['Apple'] = 35
array['Banana']= 37
array['Apple' = 43
would result in an array:
( 'Apple' => 43, 'Banana' = 37 )
See Labradorcode's answer for the correct implementation and don't forget to cast your string values as ints!
EDIT to elaborate on his answer
for($i = 0; i < count(list_of_fruits){
if(!array_key_exists($fruit, $basket){
$basket[$fruit] = 0;
}
$basket[$fruit] = $basket[$fruit] + (int)$num_fruits;
something like that

Convert a little different multidimensional arrays in same multidimensional array

I'm using an API from a contributer for my hotel booking engine. Whenever i need some information from his system, i execute CURL and i get the response in XML. Then i apply json_decode(json_encode(simplexml_load_string($response)), true)
and i get the response in array. So far so good, but there is a specific problem. The returned XML for the same function doesn't have always the same format. It depends on the parameters of the request. I 'll use a specific example to make this clear.
When i need to get the availability of my room/rooms, i send a request with a range of days. There are 4 different situations here.
1 room - 1 day
1 room - multiple days
Multiple rooms - 1 day
Multiple rooms - Multiple days
In situation (1) the array is like this:
In situation (2) the array is like this:
In situation (3) the array is like this:
In situation (4) the array is like this:
As you can see, if there is 1 room, there is no 0 key as an index and the information seems like "1 level up". Same situation for days. If you request for more than 1 day, then all days have their index (0, 1, 2 etc..).
The question is: Is there a way to convert every response to the array like situation 4?
I mean if there is 1 room, place it's information under RoomAvailability['0']. If there are more, place them like RoomAvailability['1'], RoomAvailability['2'] etc...
Same thing for the DayAvailability. If there is one one day place the day's information under DayAvailability['0']. Many days? DayAvailability['1'], DayAvailability['2'] etc...
With this trouble the only thing i could do to check if there are many rooms was:
if (isset($response['RoomsAvailability']['RoomAvailability']['0'])) {
//Do staff
}
But that's pretty bad! I don't want to waste time and conditions if there is 1 room or 50, 1 day or 30!
Any1 can help?
Just do a pre-check if they're array, and if not, encapsulate it in an array.
First do that for the main array (in this case the second RoomsAvailability)
an array
if(array_key_exists('#attributes', ($response['RoomsAvailability']['RoomAvailability']))
{
$response['RoomsAvailability']['RoomAvailability'] = [$response['RoomsAvailability']['RoomAvailability']];
}
After that you need to run each room and do the same for the days
foreach($response['RoomsAvailability']['RoomAvailability'] as &$item)
{
if(array_key_exists('DayAvailability', $item))
{
$item = [$item];
}
}
Full code:
// check if the base room availability is a single room, if it is convert into an array
if(array_key_exists('#attributes', ($response['RoomsAvailability']['RoomAvailability']))
{
$response['RoomsAvailability']['RoomAvailability'] = [$response['RoomsAvailability']['RoomAvailability']];
}
// now do the same for all the days in rooms
foreach($response['RoomsAvailability']['RoomAvailability'] as &$item)
{
if(array_key_exists('DayAvailability', $item))
{
$item = [$item];
}
}
// now do you stuff
NOTICE: on the foreachI've added an & at &$item so that the changes in the foreach do have effect outside of it
I answer my own question because i found out what my problem is.
In this line json_decode(json_encode(simplexml_load_string($response)), true) if a node has children, the array is like $array[0][something], $array[1][something] etc... but if there is only one child the array goes like $array[something] without the 0 key! That was totally unexpected!
After a lot of search, it seems like there is no abstract and proper way to convert any XML to a php array, keep each node's attributes and keep the same structure as the XML was. Please correct me if i'm wrong.
I decided to use SimpleXMLElement php.net link, pretty simple and easy to use.

when selecting random values from arrays, make sure two certain ones do not appear next to each other

I have an array with 18 values in it from which I select random values using $array[rand(0,17)]. I put these randomly selected values next to each other on the page. Within the array are 6 sets of values that I do not want to be put next to each other on the page. Is there any way that I can detect when the pairs are together and select new values because of that
warning: Do you know for sure that you won't get any degenerate cases where there are no possible orderings of the array? For example, if you won't allow the pairs [1,2] or [2,1] and the array you get is [1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2], the you're out of luck. There's no way to display the array in the way you want, and a method like I describe below will never terminate.
I would use shuffle($array) and then iterate through the shuffled array one item at a time, to find out whether any value is "incompatible" with the item before it. If so, just reshuffle the array and try again. You can't predict how many tries it will take to get a shuffled array that works, but the amount of time it takes should be negligible.
To detect whether two values are compatible, I'd suggest making an array that contains all incompatible pairs. For example, if you don't want to have the consecutive pairs 1 and 3 or 2 and 5, then your array would be:
$incompatible = array(
array(1,3),
array(2,5) );
Then you'd iterate over your shuffled array with something like:
for ($i=1; i<count($array)-1; i++;) {
$pair = $array[i, i+1]; // this is why the for loop only goes to the next-to-last item
if in_array($pair, $incompatible) {
// you had an incompatible pair in your shuffled array.
// break out of the for loop, re-sort your array, and try again.
}
}
// if you get here, there were no incompatible pairs
// so go ahead and print the shuffled array!
Or use with unset() for remove the keys, or use by Session, for next skip.

How to ungroup an element in an multi-dimentional array?

I have a function that gets users from a MySQL database, the results rows can be 1 or multiple rows.
What annoys me is that when i'm only looking to get 1 result from the db, it returns me a multi-dimentional array like this:
$result = array([0]=>array('foo'=>'bar'))
And makes me write nasty code like:
$e = $result[0]['foo'] // instead of $result['foo']
I'm pretty sure that many people came across this situation, i thought it would be cool if i can check if there is only one row returned and then append to $result an ungrouped version of it so i can use it when i'm looking for only 1 row. so it'd be like this:
$result = array(
[0] => array('foo'=>'bar'), // will keep the multi-dimentional version
'foo' => 'bar' // and append the ungrouped version of $result here
);
How to do that?
if ( count($result) === 1)
{
$result = $result[0];
}
Sounds to me like something's a little wonky in your API.
If you have a function that can return 1 to n results from a database, then it should be accessed through array accessors (such as $result[0]['foo']). Alternatively, you could store the result in a temp var, such as:
$o = $result[0];
print_r($o['foo']);
The problem with doing what you're asking is that you're munging your return data on a special case basis, which is not only confusing to those who may use your code, but it's very error-prone, potentially leaving a user wondering why they can't access the 0th element of $result.
Functions that return a single element should return a single element. Functions that return multiple elements should return multiple elements (and not have their data changed on special cases).

Categories