Merging CSV lines where column value is the same - php

I have a big CSV file with about 30 columns and 2.5K rows.
Some of the rows have exactly the same values except some columns.
I would like to merge those alike and concatenate with a comma between the values of the columns that are not the same.
Small example:
id name age kid
1 Tom 40 John
1 Tom 40 Roger
---merged becomes---
1 Tom 40 John, Roger
I can do this with PHP using lots and lots of fors and ifs but I am hoping that there's a more elegant and fast way.

This is a great beginner question for a common programming problem. What you'll want to do is a two step approach. First, parse the CSV into a data structure that you can easily modify, then loop over that structure and generate a new array that matches the output.
<?php
// Parse CSV into rows like:
$rows = array(
array(
'id' => 1,
'name' => 'Tom',
'age' => 50,
'kid' => 'John'
),
array(
'id' => 1,
'name' => 'Tom',
'age' => 50,
'kid' => 'Roger'
),
array(
'id' => 2,
'name' => 'Pete',
'age' => 40,
'kid' => 'Pete Jr.'
),
);
// Array for output
$concatenated = array();
// Key to organize over
$sortKey = 'id';
// Key to concatenate
$concatenateKey = 'kid';
// Separator string
$separator = ', ';
foreach($rows as $row) {
// Guard against invalid rows
if (!isset($row[$sortKey]) || !isset($row[$concatenateKey])) {
continue;
}
// Current identifier
$identifier = $row[$sortKey];
if (!isset($concatenated[$identifier])) {
// If no matching row has been found yet, create a new item in the
// concatenated output array
$concatenated[$identifier] = $row;
} else {
// An array has already been set, append the concatenate value
$concatenated[$identifier][$concatenateKey] .= $separator . $row[$concatenateKey];
}
}
// Do something useful with the output
var_dump($concatenated);

If you only have the data in a CSV file, I think that the easiest way to do what you want is build an associative array using the common data as key and modifing it if exists:
$array=[];
while ($a=fgetcsv($handle)){
if (isset($array[$a[0]."-".$a[1]."-".$a[2]])) {
$array[$a[0]."-".$a[1]."-".$a[2]].=",".$a[3];
}
else {
$array[$a[0]."-".$a[1]."-".$a[2]]=$a[3];
}
}

Related

Codeigniter Table Library Handle Empty / Null Values

I'm using Codeigniter 3 and the table library in order to display some data in the following format;
+---------------+---------------------+
| id | 102 |
+---------------+---------------------+
| First Name | Ross |
+---------------+---------------------+
| Last Name | Bing |
+---------------+---------------------+
| Title | Doctor |
+---------------+---------------------+
| Timestamp | 2019-01-18 10:17:05 |
+---------------+---------------------+
| Member Number | |
+---------------+---------------------+
A vardump of $tableData is;
Array
(
[0] => Array
(
[id] => 102
[firstname] => Ross
[lastname] => Bing
[title] => Doctor
[timestamp] => 2019-01-18 10:17:05
[member_no] =>
)
)
The PHP code I use to generate the HTML table is;
$tableData = $this->My_model->getData();
$heading = array(
'id' => 'ID',
'firstname' => 'First Name',
'lastname' => 'Last Name',
'title' => 'Title',
'timestamp' => 'Date Submitted',
'member_no' => 'Member Number'
);
$fields = array_keys($tableData[0]);
$rows = array();
foreach($fields as $key => $field) {
$rows[$key][0] = array(
'data' => '<strong>' . $heading[$field] . '</strong>'
);
foreach($tableData as $key2 => $item) {
$rows[$key][$key2 + 1] = $item[$field];
}
}
foreach($rows as $row) {
$this->table->add_row($row);
}
The above code works fine, however if a row is empty (see member_no above) i'd like to do one of two things (whichever is easiest);
hide the table row completely
display not available in the table cell
How can I achieve this?
I would do something like this:
$tableData = array (
0 =>
array (
'id' => 102,
'lastname' => 'Bing',
'title' => 'Doctor',
'timestamp' => '2019-01-1810:17:05',
'member_no' => null,
'firstname' => 'Ross', //intentionally moved to show ordering
'foobar' => 'blah' //added for example, this will be removed by array_intersect_key
),
);
$heading = array(
'id' => '<strong>ID</strong>',
'firstname' => '<strong>First Name</strong>',
'lastname' => '<strong>Last Name</strong>',
'title' => '<strong>Title</strong>',
'timestamp' => '<strong>Date Submitted</strong>',
'member_no' => '<strong>Member Number</strong>'
);
//create a default array
//this takes the keys from $heading, and makes an array with all the values as 'not available'
// ['id' => 'not available','lastname' => 'not available', ... ]
$default = array_fill_keys(array_keys($heading), 'not available');
$rows = [];
foreach($tableData as $key => $row) {
//remove all elements with strlen of 0 (in this case 'member_no')
$row = array_filter($row, function($item){return strlen($item);});
//removes 'foobar' or anything that has a key not in $heading
$row = array_intersect_key($row, $heading);
//combine $default and $data (empty items in $row are filled in from default)
//such as items removed by array_filter above
//the key order will match $default, which matches $headings
$row = array_merge($default, $row);
$rows[] = $row;
}
foreach($heading as $key=>$value) {
print_r(array_merge([['data'=>$value]], array_column($rows, $key)));
}
Output
Array
(
[0] => Array
(
[data] => <strong>ID</strong>
)
[1] => 102
//[2] => 108
//...
)
....
Sandbox
I kept these separate so it would be a bit easier to read, but there is no reason you cannot do it this way.
//...
$default = array_fill_keys(array_keys($heading), 'not available');
foreach($tableData as $key => $row) $rows[] = array_merge($default, array_intersect_key(array_filter($row, function($item){return strlen($item);}), $heading));
foreach($heading as $key=>$value) print_r(array_merge([['data'=>$value]],array_column($rows, $key)));
Sandbox
I had to guess a bit on what the end result was, so I ran your original code and it gave me this:
Array
(
[0] => Array
(
[data] => <strong>ID</strong>
)
[1] => 102
//[2] => 108
//...
)
....
In my code you can replace the print_r with this call $this->table->add_row([..array data..]);. Where array data is the stuff in the print_r call. You could make this a variable, but what's the point if its only used here. That eliminates a few of those loops (see below) and A few other advantages:
key order of $headings is preserved, elements appear where they do in the $headings array, regardless of where they are in $tableData. This allows easy re-ording of the data, too, for example: you could even map this to a dynamic array, I do this in CSV files, which allows users to change the order of the headers and columns. They can even rename the headers, because the way the key => value pairing works my_key => their_key...
Data missing from $tableData is defaulted to not available pulled in from $default, in theory you could map this manually to different things. For example: you could default timestamp to the current time by doing $default['timestamp'] = date('Y-m-d H:i:s'); right after creating it with array_fill_keys.
Extra data in $tableData not defined in $headings is removed. Which is good for forward compatibility.
And it's A bit easier to make sense of (once you know how it works) because there are less "transient" variables and less fiddling with the keys ect...
Basically what I am doing is giving control over to the $headings array, in your original code. You do this somewhat by looping over the keys (fields), but this complicates things later like this $rows[$key][$key2 + 1]. It leaves you open to undefined array index issues, if the data changes at a later time, such as adding a new field to the DB.
The order of the output is dependent on the data in $tableData which is less intuitive (and less useful) then if it depends on $headings.
Here is an example of these issues with the original code:
//for example if your data changes to this and run your original code
$tableData = array (
0 =>
array (
'id' => 102,
'lastname' => 'Bing',
'title' => 'Doctor',
'timestamp' => '2019-01-1810:17:05',
'member_no' => null,
'firstname' => 'Ross', //intentionally moved to show ordering
'foo' => 'bar' //added this undefined in $headings
),
);
You'll get this notice and also find the last 2 elements are:
<br />
<b>Notice</b>: Undefined index: foo in <b>[...][...]</b> on line <b>30</b><br />
//... all other elements ...
//in orignal: displayed in the order of $tableData
//in my code: order is based on $headings, so this would be moved to the correct location
Array(
[0] => Array (
[data] => <strong>First Name</strong>
)
[1] => Ross
)
//in orignal: $headings['foo'] is not defined so we have no label here
//in my code: this element is removed and doesn't get rendered
Array(
[0] => Array(
[data] => <strong></strong>
)
[1] => bar
)
Sandbox (Orignal Code)
While these things may never cause an issue, it highlights my point about basing the output off of $headings and not $tableData. Things tend to change, and this way if you add/remove a field from this data, you wont have to worry about it breaking this page etc...
The combination of array_fill_keys, array_intersect_key and array_merge can be used to map the headers (As I shown above) of one array to another. You can use array_combine($headings, $row) to physically swap them and you would get something like this:
[
[
'<strong>ID</strong>' => 102,
'<strong>First Name</strong>' => 'Ross',
//...
],
[...]
]
It works great for CSV files (which is what I figured it out on) and anything else you need to remap the keys for.
Anyway, hope it helps you!

PHP: sorting/searching data that has 2 keys (or an array of arrays by a sub-array key)?

I've hit this a few times and never found the best way to tackle it. It's easiest to illustrate with a concrete example. Sample data:
product_id display_name display_order
---------- ------------ -------------
"samgal3" "Samsung Galaxy 3" 0
"motorazrh" "Motorola Razr HD" 1
"iphone5" "Apple iphone 5" 2
etc
The actual arrays are often small (<20 items), though not always, and have guaranteed unique keys/values. Each item has a unique sort key (order it's listed in, for html table/enumeration), a unique internal key (for item lookup), and a human-readable display name.
Typically I hit this issue when a list of options is used on a form. The same data is used both to populate dropdown boxes on forms, and also to validate the submitted $GET/POST data. When generating the form, it needs to be enumerated/listed in 'sort' order to create the SELECT box options in order. When a form is submitted it needs to be searchable by 'product_id' (to validate "...&action=view&product_id=elephant..." is a product in the list).
If I use 'sort'=>array(other data) as the key, then displaying by 'sort' is easy but searching within $data[*]['product_id'] is hard *(ie identify $KEY if it exists, having $data[$KEY]['product_id'] == 'htcvox')*. If I use 'product_id'=>array(other data) as the key then searching whether 'samgal3' is in the array and finding its data is easy but there's no simple way to walk/enumerate the array by 'sort' to create the form.
I guess I can do a custom search/sort where the search/sort key for any member $i in $data is $i['product_id'] or $i['sort'] but it's clumsy and I've never done it before. Simplicity counts as the code will be open source.
I'm expecting to code the data as an array of arrays, like this:
$data = array(
0 => array('product_id'=>'samgal3', 'display_name' => 'Samsung Galaxy 3'),
1 => array('product_id'=>'motorazrh', 'display_name' => 'Motorola Razr HD'),
...
or
$data = array(
'samgal3' => array('sort'=>0, 'display_name' => 'Samsung Galaxy 3'),
'motorazrh' => array('sort'=>1, 'display_name' => 'Motorola Razr HD'),
...
Put the same problem differently, given an 2D array of arrays: $data = array(array1, array2, array3, ....); where all of array1, array2, array3, ... contain a key/field with a fixed name, is there an easy way to search/sort the nested arrays on $ARRAY[**]['named_field']?
What you want is essentially multiple indexes into a single array, just like you would have multiple indexes on a single table in a relational database. (Sorting is a separate but related concern.)
Let's start with this basic data structure, a simple set of arrays with no particular importance attached to the keys:
$data = array(
array('display_order'=> 0, 'product_id'=>'samgal3', 'display_name' => 'Samsung Galaxy 3'),
array('display_order'=> 1, 'product_id'=>'motorazrh', 'display_name' => 'Motorola Razr HD'),
array('display_order'=> 2, 'product_id'=>'a', 'display_name' => 'a'),
array('display_order'=> 3, 'product_id'=>'c', 'display_name' => 'c'),
array('display_order'=> 4, 'product_id'=>'d', 'display_name' => 'd'),
array('display_order'=> 5, 'product_id'=>'b', 'display_name' => 'b'),
array('display_order'=> 6, 'product_id'=>'q', 'display_name' => 'q'),
array('display_order'=> 7, 'product_id'=>'f', 'display_name' => 'f'),
);
You can create an explicit index easily and then use it to get $data items:
$product_id_idx = array_flip(array_map(function($item){return $item['product_id'];}, $data));
$samgal3_array = $data[$product_id_idx['samgal3']]; // same as $data[0]
For sorting you can use the oft-forgotten array_multisort. Look at example 3 in the documentation. The trick is to create arrays to sort on and include the full data set as the last argument. For example:
array_multisort(array_keys($product_id_idx), SORT_ASC, SORT_STRING, $data);
$data is now sorted by product key. The original numeric array keys of $data are lost, however, which means our $product_id_idx is not usable anymore. Thus it's best to sort a copy of the data array if you want to keep using your indexes.
We can combine both these approaches into a single class to preserve our sanity:
class MultiIndex {
protected $array;
protected $indexes = array();
protected $indexdefs = array();
function __construct($array, $indexdefs)
{
$this->array = $array;
$this->indexdefs = $indexdefs;
foreach ($indexdefs as $name => $column) {
$this->indexes[$name] = $this->makeIndex($column);
}
}
function makeIndex($column)
{
$index = array();
foreach ($this->array as $k => $v) {
$index[$v[$column]] = $k;
}
return $index;
}
function get($key, $index=null)
{
$datapk = ($index===null) ? $key : $this->indexes[$index][$key];
return $this->array[$datapk];
}
function getIndex($index)
{
return $this->indexes[$index];
}
function getData()
{
return $this->array;
}
function indexedBy($index)
{
$indexed = array();
$indexedcolumn = $this->indexdef[$index];
foreach ($this->indexes[$index] as $indexk => $arrayk) {
$newarray = $this->array[$arrayk];
unset($newarray[$indexedcolumn]);
$indexed[$indexk] = $newarray;
}
return $indexed;
}
function sortedBy(/*multisort args*/)
/* with strings converted to arrays corresponding to index of same name */
{
$args = func_get_args();
foreach ($args as $n => $arg) {
if (is_string($arg)) {
$args[$n] = array_keys($this->indexes[$arg]);
}
}
$sorted = $this->array;
$args[] = $sorted;
call_user_func_array('array_multisort', $args);
return $sorted;
}
}
Example of use:
$dataidx = new MultiIndex($data, array('id'=>'product_id', 'disp'=>'display_order'));
var_export($dataidx->sortedBy('disp', SORT_STRING, SORT_ASC));
var_export($dataidx->indexedBy('id'));
var_export($dataidx->get('samgal3', 'id'));
This should be a very basic base to build upon, and should be fine for small arrays. For simplicity's sake MultiIndex's data is immutable and keys are always array indexes. Some obvious ways to enhance this are:
Make the $indexdefs accept a callable which returns a key for an item, instead of just a string/int naming an array key. This allows you to create indexes on any shape of data and even to create indexes which don't correspond to the data directly. (E.g., index by number of characters in the display name, or by the date+time of an array that keeps date and time separate, etc.)
Remove the requirement that an index key have only a single value. (Right now all indexes you make are assumed to be unique.)
Allow you to declare a SORT_* datatype for an index and have it automatically included in the arguments to array_multisort in MultiIndex::sortedBy().
Include sparse indexes: if you have very common or very rare values, or just have a very large dataset and you want to save memory, you can make it so only certain values are indexed. If an item isn't found in an index, fall back to a full scan of unindexed items in the data.
Add sensible interface implementations.
Allow the MultiIndex to have multiple backends, so you can have multiple indexes on any array-like structure (e.g. a dbm key-value store, a cloud store like DynamoDB, memcached, etc), and manipulate them all with the same object interface.
Make MultiIndex hold mutable data and update indexes incrementally and automatically as the data changes.
I'll keep this code on a gist for easy forking.
use http://www.php.net/usort to generate a custom user defined sort.
Example:
<?php
//added a few more values
$data = array(
0 => array('product_id'=>'samgal3', 'display_name' => 'Samsung Galaxy 3'),
1 => array('product_id'=>'motorazrh', 'display_name' => 'Motorola Razr HD'),
2 => array('product_id'=>'a', 'display_name' => 'a'),
3 => array('product_id'=>'c', 'display_name' => 'c'),
4 => array('product_id'=>'d', 'display_name' => 'd'),
5 => array('product_id'=>'b', 'display_name' => 'b'),
6 => array('product_id'=>'q', 'display_name' => 'q'),
7 => array('product_id'=>'f', 'display_name' => 'f'),
);
function cmp($a,$b){
return strcasecmp($a['display_name'],$b['display_name']);
}
usort($data,'cmp');
var_export($data);
http://codepad.viper-7.com/3mY8nU

Understanding the basics of multidimensional arrays

I am new to using multidimensional arrays with php, I have tried to stay away from them because they confused me, but now the time has come that I put them to good use. I have been trying to understand how they work and I am just not getting it.
What I am trying to do is populate results based on a string compare function, once I find some match to an 'item name', I would like the first slot to contain the 'item name', then I would like to increment the priority slot by 1.
So when when I'm all done populating my array, it is going to have a variety of different company names, each with their respective priority...
I am having trouble understanding how to declare and manipulate the following array:
$matches = array(
'name'=>array('somename'),
'priority'=>array($priority_level++)
);
So, in what you have, your variable $matches will point to a keyed array, the 'name' element of that array will be an indexed array with 1 entry 'somename', there will be a 'priority' entry with a value which is an indexed array with one entry = $priority_level.
I think, instead what you probably want is something like:
$matches[] = array(name => 'somename', $priority => $priority_level++);
That way, $matches is an indexed array, where each index holds a keyed array, so you could address them as:
$matches[0]['name'] and $matches[0]['priority'], which is more logical for most people.
Multi-dimensional arrays are easy. All they are is an array, where the elements are other arrays.
So, you could have 2 separate arrays:
$name = array('somename');
$priority = array(1);
Or you can have an array that has these 2 arrays as elements:
$matches = array(
'name' => array('somename'),
'priority' => array(1)
);
So, using $matches['name'] would be the same as using $name, they are both arrays, just stored differently.
echo $name[0]; //'somename';
echo $matches['name'][0]; //'somename';
So, to add another name to the $matches array, you can do this:
$matches['name'][] = 'Another Name';
$matches['priority'][] = 2;
print_r($matches); would output:
Array
(
[name] => Array
(
[0] => somename
[1] => Another Name
)
[priority] => Array
(
[0] => 1
[1] => 2
)
)
In this case, could this be also a solution with a single dimensional array?
$matches = array(
'company_1' => 0,
'company_2' => 0,
);
if (isset($matches['company_1'])) {
++$matches['company_1'];
} else {
$matches['company_1'] = 1;
}
It looks up whether the name is already in the list. If not, it sets an array_key for this value. If it finds an already existing value, it just raises the "priority".
In my opinion, an easier structure to work with would be something more like this one:
$matches = array(
array( 'name' => 'somename', 'priority' => $priority_level_for_this_match ),
array( 'name' => 'someothername', 'priority' => $priority_level_for_that_match )
)
To fill this array, start by making an empty one:
$matches = array();
Then, find all of your matches.
$match = array( 'name' => 'somename', 'priority' => $some_priority );
To add that array to your matches, just slap it on the end:
$matches[] = $match;
Once it's filled, you can easily iterate over it:
foreach($matches as $k => $v) {
// The value in this case is also an array, and can be indexed as such
echo( $v['name'] . ': ' . $v['priority'] . '<br>' );
}
You can also sort the matched arrays according to the priority:
function cmp($a, $b) {
if($a['priority'] == $b['priority'])
return 0;
return ($a['priority'] < $b['priority']) ? -1 : 1;
}
usort($matches, 'cmp');
(Sourced from this answer)
$matches['name'][0] --> 'somename'
$matches['priority'][0] ---> the incremented $priority_level value
Like David said in the comments on the question, it sounds like you're not using the right tool for the job. Try:
$priorities = array();
foreach($companies as $company) {
if (!isset($priorities[$company])) { $priorities[$company] = 0; }
$priorities[$company]++;
}
Then you can access the priorities by checking $priorities['SomeCompanyName'];.

PHP Nested Navigation

I am building out a database-driven navigation, and I need some help in a method to build my data structure. I'm not very experienced with recursion, but that is most likely the path this will take. The database table has an id column, a parent_id column, and a label column. The result of calling the method provides me with the data structure. The way my data structure should result in the following:
Records with a parent_id of 0 are assumed to be root elements.
Each root element contains an array of children if a child exists which holds an array of elements containing the parent_id equal to the root element id.
Children may contain a children array containing parent_ids equal to the immediate child (this would be the recursive point)
When a record exists that contains a parent_id which isn't 0, it gets added to the array of the children elements.
Here is how the data structure should look:
$data = array(
'home' => array(
'id' => 1,
'parent_id' => 0,
'label' => 'Test',
'children' => array(
'immediatechild' => array(
'id' => 2,
'parent_id' => 1,
'label' => 'Test1',
'children' => array(
'grandchild' => array(
'id' => 3,
'parent_id' => 2,
'label' => 'Test12',
))
))
)
);
Here's something I came up with in a few moments. Its not correct, but its what I want to use and Id like some help fixing it.
<?php
// should i pass records and parent_id? anything else?
function buildNav($data,$parent_id=0)
{
$finalData = array();
// if is array than loop
if(is_array($data)){
foreach($data as $record){
// not sure how/what to check here
if(isset($record['parent_id']) && ($record['parent_id'] !== $parent_id){
// what should i pass into the recursive call?
$finalData['children'][$record['label'][] = buildNav($record,$record['parent_id']);
}
}
} else {
$finalData[] = array(
'id' => $data['id'],
'parent_id' => $parent_id,
'label' => $data['label'],
)
}
return $finalData
}
Thanks for the help!
Simplest solution (assuming you've got the data stored in relational represenation using the parent id as a FK to indicate the hierarchy) is to just brute force it:
$start=array(
array('parent_id'=>0, 'title'=>'Some root level node', 'id'=>100),
array('parent_id'=>0, 'title'=>'Other root level node', 'id'=>193),
array('parent_id'=>100, 'title'=>'a child node', 'id'=>83),
....
);
// NB this method will work better if you sort the list by parent id
$tree=get_children($start, 0);
function get_children(&$arr, $parent)
{
static $out_index;
$i=0;
$out=array();
foreach($arr as $k=>$node) {
if ($node['parent_id']==$parent) {
++$i;
$out[$out_index+$i]=$node;
if (count($arr)>1) {
$out[$out_index+$i]['children']=get_children($arr, $node['id']);
}
unset($arr[$k]);
}
$out_index+=$i;
if ($i) {
return $out;
} else {
return false;
}
}
But a better solution is to use an adjacency list model for the data in the database. As an interim solution you might want to serialize the tree array and cache it in a file rather than parse it every time.

How do I select 10 random things from a list in PHP?

I know how to select one random item from an array, but how about ten random items from an array of, say, twenty items? (In PHP.)
What makes it a little more complicated is that each item actually has two parts: a filename, and a description. Basically, it's for a webpage that will display ten random images each time you reload. The actual format of this data doesn't really matter, although it's simple enough that I'd prefer to contain it in flat-text or even hard code it rather than set up a database. (It's also not meant to change often.)
You could shuffle the array and then pick the first ten elements with array_slice:
shuffle($array);
$tenRandomElements = array_slice($array, 0, 10);
You can select one or more random items from an array using array_rand() function.
If you don't want to shuffle() the entire array (perhaps because your array is relatively large), you can call array_rand() to populate an array of keys then filter the input array using that array of keys.
Code: (Demo)
$input = [
['filename' => 'a.php', 'description' => 'foo'],
['filename' => 'b.php', 'description' => 'fooey'],
['filename' => 'c.php', 'description' => 'bar'],
['filename' => 'd.php', 'description' => 'berry'],
['filename' => 'e.php', 'description' => 'buoy'],
['filename' => 'f.php', 'description' => 'barf'],
['filename' => 'g.php', 'description' => 'food'],
['filename' => 'h.php', 'description' => 'foodie'],
['filename' => 'i.php', 'description' => 'boof'],
['filename' => 'j.php', 'description' => 'boogey'],
['filename' => 'k.php', 'description' => 'fudge'],
['filename' => 'l.php', 'description' => 'fudgey'],
['filename' => 'm.php', 'description' => 'barge'],
['filename' => 'n.php', 'description' => 'buggy'],
['filename' => 'o.php', 'description' => 'booger'],
['filename' => 'p.php', 'description' => 'foobar'],
['filename' => 'q.php', 'description' => 'fubar'],
['filename' => 'r.php', 'description' => 'goof'],
['filename' => 's.php', 'description' => 'goofey'],
['filename' => 't.php', 'description' => 'boofey'],
];
var_export(
array_intersect_key(
$input,
array_flip(
array_rand($input, 10)
)
)
);
The output, you will notice, has only 10 rows of randomly selected data in it.
Different from shuffle(), because $input is nominated first in array_intersect_key(), the "random" items are actually in their original order.
Even if you iterate array_rand()'s returned array with a classic loop, the results will still be ordered by their position in the original array.
Code: (Demo)
$randoms = [];
foreach (array_rand($input, 10) as $key) {
$randoms[] = $input[$key];
}
var_export($randoms);
If the position of the random elements is important, you should call shuffle() on the randomly selected results.
Note that the PHP manual says the following for array_rand():
[array_rand()] uses a pseudo random number generator that is not suitable for cryptographic purposes.
When picking only one entry, array_rand() returns the key for a random entry. Otherwise, an array of keys for the random entries is returned. This is done so that random keys can be picked from the array as well as random values.
If multiple keys are returned, they will be returned in the order they were present in the original array.
Trying to pick more elements than there are in the array will result in an E_WARNING level error, and NULL will be returned.
If you aren't sure how many random items will be selected or how many items are in the array, then use min($yourNumberOfRandomItemsToReturn, count($yourArray)).
An array of arrays in PHP should be a good strategy. You can keep the data for these array in any way you like (hard-coded, XML, etc) and arrange it in the arrays as such:
Array {
Array (item0) { filename,description, weight,...}
Array (item1) { filename,description, weight,...}
Array (item2) { filename,description, weight,...}
}
You can then use the array_rand function to randomly remove items from the array. Creating a weight value for each entry will allow you to pick one entry over another using some sort of priority strategy (e.g. randomly get 2 items from the array, check weight, pick the one with a greater weight and replace the other)
I have some code that sort of does what you ask for. I store a list of sponsorship links in a text file, and pick them at random. But, if I want to skew the set, I use multiple the links ;-)
Sponsors file:
Example
Example
BBC
Google
PHP:
$sponsor_config = 'sponsors.txt';
$trimmed = file($sponsor_config, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
$op = array();
$limit = 20; // insurance
$loops = 0;
$needed = 4;
$op[] = '<div id="sponsorship"><!-- start sponsorship -->';
$selected = array();
while ( (count($selected) < $needed) AND ($loops <= $limit)) {
$choice = rand(0, count($sponsors)-1);
if(!in_array($sponsors[$choice], $selected)) $selected[] = $sponsors[$choice];
$loops++;
}
foreach($selected as $k => $selection) {
$op[] = '<p class="sponsorship bg_'.($k%3+1).'">Click the link below to<br />visit our Site Sponsor:<br />'.$selection.'</p>';
}
$op[] = '</div><!-- end sponsorship -->';
return join("\n",$op)."\n";
V. quick and v.v. dirty... but it works

Categories