In my php script, I have a variable $data that contains an array that may have various number of elements.
The script picks one of the array elements at random and outputs it to the browser:
# count number of elements in $data
$n = count($data);
# pick a random number out of the number of elements in $data
$rand = rand(0, ($n - 1));
# output a random element
echo '<p> . trim($data[$rand]) . '</p>';
QUESTION: I want to improve that script, so that it doesn't output the same array element again until it runs out of array elements. For example, if an array contains elements numbered 0 to 9, and the script picked an array element #4, I want it to remember that, and next time the script runs, to exclude the element that was #4.
It could probably be done in many different ways, but I'm looking for the simplest and most elegant solution, and would be grateful for help from a PHP expert.
Save the numbers that were already picked in the user's session.
session_start();
$n = count($data);
// If the array isn't initialized, or we used all the numbers, reset the array
if( !isset( $_SESSION['used_nums']) || count( $_SESSION['used_nums']) == $n) {
$_SESSION['used_nums'] = array();
}
do{
$rand = rand(0, ($n - 1));
} while( isset( $_SESSION['used_nums'][$rand]));
echo '<p>' . trim($data[$rand]) . '</p>';
$_SESSION['used_nums'][$rand] = 1;
Or, perhaps a more clever way, using array_intersect_key and array_rand:
session_start();
$n = count($data);
// If the array isn't initialized, or we used all the numbers, reset the array
if( !isset( $_SESSION['used_nums']) || count( $_SESSION['used_nums']) == $n) {
$_SESSION['used_nums'] = array();
}
$unused = array_intersect_key( $data, $_SESSION['used_nums'];
$rand = array_rand( $unused);
echo '<p>' . trim($unused[$rand]) . '</p>';
$_SESSION['used_nums'][$rand] = 1;
You could shuffle the array and then simply iterate over it:
shuffle($data);
foreach ($data as $elem) {
// …
}
If you don’t want to alter the array order, you could simply shuffle the array’s keys:
$keys = array_keys($data);
shuffle($keys);
foreach ($keys as $key) {
// $data[$key]
}
You can use sessions to store the indexes you've used so far. Try this.
session_start();
$used = &$_SESSION['usedIndexes'];
// used all of our array indexes
if(count($used) > count($data))
$used = array();
// remove the used indexes from data
foreach($used as $index)
unset($data[$index]);
$random = array_rand($data);
// append our new index to used indexes
$used[] = $random;
echo '<p>', trim($data[$random]) ,'</p>';
$selection=$x[$randomNumber];
unset($x[$randomNumber]);
Related
I have this array:
$array = array( 57, 53, 52 );
I want to get all the unique combinations of those numbers (order of the items not relevant).
I want a result along the lines of:
// 57
// 57, 53
// 57, 53, 52
// 53
// 52
// 52, 57
I am using this function, but this returns every combination of the values and as I don't care for the order they are all the same result just in a different order:
function my_function( $array ) {
$combinations = array();
$words = sizeof( $array );
$combos = 1;
for( $i = $words; $i > 0; $i-- ) {
$combos *= $i;
}
while( sizeof( $combinations ) < $combos ) {
shuffle($array);
$combo = implode( " ", $array );
if( !in_array( $combo, $combinations ) ) {
$combinations[] = $combo;
}
}
return $combinations;
}
print_r( my_function( $array ) );
How can I achieve this?
<?php
function my_function($array){
$combs = [[]]; // adding empty set for better code clarity
sort($array); // sort the array to avoid verbose code to handle duplicate combinations
$set = [];
foreach($array as $index => $element){
$temp = [];
foreach($combs as $curr_comb){
$new_comb = $curr_comb;
$new_comb[] = $element;
$hashed_comb = implode(",",$new_comb);
if(!isset($set[$hashed_comb])){
$temp[] = $new_comb;
$set[$hashed_comb] = true;
}
}
$combs = array_merge($combs,$temp);
}
return array_slice($combs,1); // removing the empty set initially added
}
print_r(my_function([57,53,52]));
print_r(my_function([3,3,3]));
Demo: https://3v4l.org/f3IHs
We add an empty combination []before to make the code look simple.
We generate combinations by adding current element to previous generated set of combinations. Like, first it's [] which later becomes [],[57] which in turn later(in next iteration of first foreach loop) becomes [],[57],[53],[57,53] and so on.
We do an implode() and insert in the set to remember the combination to avoid duplicacy.
Since order doesn't matter, it seems like we could work our way through in order. Start with the first number and find all combinations, then move on to the second as our starting number, and so on. And since I love recursive functions (function recursion is its own reward), this is how I'd go about it:
function unique_combinations( $array, $prefix = '' ) {
$combinations = array();
$count = count($array);
// I use a for loop just to count the number of times to run. Since I'm using array_shift, the array will keep getting smaller
for( $i = 0; $i < $count; $i++ ) {
$number = array_shift($array);
// Grab our current result (this number and any prefixes
$this_result = trim( "$prefix $number" );
$combinations[] = $this_result;
// Now, if the array still has numbers in it, run the function on those numbers and combine results
if ( count($array) > 0 ) {
$combinations = array_merge($combinations, unique_combinations( $array, $this_result ) );
}
}
return $combinations;
}
print_r( unique_combinations( [57,58,59] ) );
A similar and also pretty short recursive approach with a single anonymous function, sort and an early array_unique. Should give you what you want, for simplicity the values are sorted in ascending order:
// all the logic
$getAllCombinations = function($array) {
$result = [];
sort($array);
$getOrderedCombinations = function ($combination_base, &$result) use (&$getOrderedCombinations) {
array_push($result,$combination_base);
if(count($combination_base) > 1) {
foreach($combination_base as $key => $val) {
$newcombo = $combination_base;
unset($newcombo[$key]);
$getOrderedCombinations($newcombo,$result);
}
}
};
$getOrderedCombinations($array,$result);
return array_unique($result,SORT_REGULAR);
};
// execution
$array = array( 57, 53, 52 );
var_dump($getAllCombinations($array));
I have tried for a long time but couldn't find a way to merge an array in to a new one.
Mostly I get lost in looping and matching.;(
I would like to recieve a php 5 method that can do the following:
Example 1
Lets say there is an array with url's like:
Array(
'a',
'a/b/c',
'a/b/c/d/e',
'a/y',
'b/z',
'b/z/q/',
)
Every last folder of the url's is the folder where a user has the right to view.
I would like to send the array to a method that returns a new array like:
Array[](
'a/c/e'
'a/y'
'z/q'
)
The method has combined some elements of the origninal array into one element.
This because there is a match in allowed ending folders.
Example 2
Array(
'projects/projectA/books'
'projects/projectA/books/cooking/book1'
'projects/projectA/walls/wall'
'projects/projectX/walls/wall'
'projects/projectZ/'
'projects/projectZ/Wood/Cheese/Bacon'
)
I would like to get a an array like:
Array[](
'books/book1'
'wall'
'wall'
'projectZ/Bacon'
)
Then it would be great (specialy in case of the 'wall' values) to have some references to the full path's of the original array.
Do it like below:-
<?php
$array = Array(
'projects/projectA/books',
'projects/projectA/books/cooking/book1',
'projects/projectA/walls/wall',
'projects/projectX/walls/wall',
'projects/projectZ/',
'projects/projectZ/Wood/Cheese/Bacon'
);// original array
$final_array =array(); // new array variable
foreach($array as $key=>$arr){ // iterate over original array
$exploded_string = end(array_filter(explode('/',$arr))); // get last-value from the url string
foreach($array as $ar){ // iterate again the original array to compare this string withh each array element
$new_exploded_string = end(array_filter(explode('/',$ar))); // get the new-last-values from url string again
if($arr !== $ar && strpos($ar,$exploded_string) !==false){ // if both old and new url strings are not equal and old-last-value find into url string
if($exploded_string == $new_exploded_string ){ // if both new-last-value and old-last-value are equal
$final_array[] = $exploded_string;
}else{
$final_array[] = $exploded_string.'/'.$new_exploded_string ;
}
}
}
}
print_r($final_array);
Output:-https://eval.in/846738
Well, there isn't a single built-in function for this ;)
$items = array(
'projects/projectA/books',
'projects/projectA/books/cooking/book1',
'projects/projectA/walls/wall',
'projects/projectX/walls/wall',
'projects/projectZ/',
'projects/projectZ/Wood/Cheese/Bacon',
'hold/mold/gold/sold/fold',
'hold/mold/gold',
'raja/maza/saza',
'raja/maza',
'mohit/yenky/client/project',
);
echo '$items = ' . nl2br(htmlspecialchars(print_r($items, true))); //Debug
// Sort, so the shorter basePath comes before the longer subPath
usort($items, function($a, $b) {
if (strlen($a) == strlen($b)) {
return 0;
} else {
return strlen($a) > strlen($b) ? 1 : -1;
}
});
$result = array();
while($basePath = array_shift($items)) { // As long as there is a next item
$basePath = rtrim($basePath, '/'); // Right trim extra /
foreach($items as $idx => $subPath) {
if (strpos($subPath, $basePath . '/') === 0) {
// $subPath begins with $basePath
$result[] = preg_replace('#.*/#', '', $basePath) . '/' . preg_replace('#.*/#', '', rtrim($subPath, '/'));
unset($items[$idx]); // Remove item from array, so it won't be matched again
continue 2; // Continue with next while($basePath = array_shift($items))
}
}
// No subPath found, otherwise continue would have called (skipping below code)
$result[] = preg_replace('#.*/#', '', $basePath);
}
echo '$result = ' . nl2br(htmlspecialchars(print_r($result, true))); //Debug
PHPFiddle: http://phpfiddle.org/main/code/ugq9-hy0i
You can avoid using nested loops (and, actually, you should avoid):
sort($array);
$carry = array_shift($array);
$result = [];
$i = 0;
$lastItem = array_reduce($array, function ($carry, $item) use (&$result, &$i) {
$result[$i] = isset($result[$i])
? array_merge($result[$i], [basename($carry)])
: [basename($carry)];
if (strpos($item, $carry) !== 0) {
$i += 1;
}
return $item;
}, $carry);
if (!empty($lastItem)) {
$result[$i] = isset($result[$i])
? array_merge($result[$i], [basename($lastItem)])
: [basename($lastItem)];
}
$result = array_map(function ($item) {
return implode('/', $item);
}, $result);
Here is working demo.
We use array_reduce here to get access to the previously processed item. Also, PHP has function basename, that retrieves the basename. So you can use it and do not reinvent the wheel.
I need to convert my array:
$tdata = array(11,3,8,12,5,1,9,13,5,7);
Into a string like this:
11-3-8-12-5-1-9-13-5-7
I was able to get it work with:
$i = 0;
$predata = $tdata[0];
foreach ($tdata as $value)
{
$i++;
if ($i == 10) {break;}
$predata.='-'.$tdata[$i];
}
But was wondering if there is an easier way?
I tried something like:
$predata = $tdata[0];
foreach ($tdata as $value)
{
if($value !== 0) {
$predata.= '-'.$tdata[$value];
}
}
but it result in bunch of Undefined Offset errors and incorrect $predata at the end.
So I want to learn once and for all:
How to loop through the entire array starting from index 1 (while excluding index 0)?
Is there a better approach to convert array into string in the fashion described above?
Yes there is a better approach to this task. Use implode():
$tdata = array(11,3,8,12,5,1,9,13,5,7);
echo implode('-', $tdata); // this glues all the elements with that particular string.
To answer question #1, You could use a loop and do this:
$tdata = array(11,3,8,12,5,1,9,13,5,7);
$out = '';
foreach ($tdata as $index => $value) { // $value is a copy of each element inside `$tdata`
// $index is that "key" paired to the value on that element
if($index != 0) { // if index is zero, skip it
$out .= $value . '-';
}
}
// This will result into an extra hypen, you could right trim it
echo rtrim($out, '-');
I've got a string here with names of students (leerlingen) and im trying to follow the exercise here.
The code shows the length of the full string.
Next up would be use a loop to check who has the longest name, but how to implement strlen() in a loop?
// change the string into an array using the explode() function
$sleerlingen = "Kevin,Maarten,Thomas,Mahamad,Dennis,Kim,Joey,Teun,Sven,Tony";
$namen = explode(" ", $sleerlingen);
echo $namen[0];
echo "<br><br>";
//determin the longest name by using a loop
// ask length
$arraylength = strlen($sleerlingen);
sleerlingen = $i;
for ($i = 1; $i <= 10; $i++) {
echo $i;
}
echo $arraylength;
?>
You used bad separator in your explode function, in string there is no space.
This should work (I didn't try it). In foreach loop you check current length with the longest one and if the current is longer, just save it as longest.
<?php
$sleerlingen = "Kevin,Maarten,Thomas,Mahamad,Dennis,Kim,Joey,Teun,Sven,Tony";
$names = explode(',', $sleerlingen);
$longest;
$longest_length = 0;
foreach ($names as $item) {
if (strlen($item) > $longest_length) {
$longest_length = strlen($item);
$longest = $item;
}
}
echo 'Longest name: ' . $longest . ', ' . $longest_length .' chars.';
?>
You can create a custom sort function to sort the array based on the strings length. Then you can easily take the first key in the array.
<?php
$sleerlingen = "Kevin,Maarten,Thomas,Mahamad,Dennis,Kim,Joey,Teun,Sven,Tony";
$namen = explode(",", $sleerlingen); // changed the space to comma, otherwise it won't create an array of the string.
function sortByLength($a,$b){
return strlen($b)-strlen($a);
}
usort($namen,'sortByLength');
echo $namen[0];
?>
I have two arrays that I'm comparing and I'd like to know if there is a more efficient way to do it.
The first array is user submitted values, the second array is allowed values some of which may contain a wildcard in the place of numbers e.g.
// user submitted values
$values = array('fruit' => array(
'apple8756apple333',
'banana234banana',
'apple4apple333',
'kiwi435kiwi'
));
//allowed values
$match = array('allowed' => array(
'apple*apple333',
'banana234banana',
'kiwi*kiwi'
));
I need to know whether or not all of the values in the first array, match a value in the second array.
This is what I'm using:
// the number of values to validate
$valueCount = count($values['fruit']);
// the number of allowed to compare against
$matchCount = count($match['allowed']);
// the number of values passed validation
$passed = 0;
// update allowed wildcards to regular expression for preg_match
foreach($match['allowed'] as &$allowed)
{
$allowed = str_replace(array('*'), array('([0-9]+)'), $allowed);
}
// for each value match against allowed values
foreach($values['fruit'] as $fruit)
{
$i = 0;
$status = false;
while($i < $matchCount && $status == false)
{
$result = preg_match('/' . $match['allowed'][$i] . '/', $fruit);
if ($result)
{
$status = true;
$passed++;
}
$i++;
}
}
// check all passed validation
if($passed === $valueCount)
{
echo 'hurray!';
}
else
{
echo 'fail';
}
I feel like I might be missing out on a PHP function that would do a better job than a while loop within a foreach loop. Or am I wrong?
Update: Sorry I forgot to mention, numbers may occur more than 1 place within the values, but there will only ever be 1 wildcard. I've updated the arrays to represent this.
If you don't want to have a loop inside another, it would be better if you grouped your $match regex.
You could get the whole functionality with a lot less code, which might arguably be more efficient than your current solution:
// user submitted values
$values = array(
'fruit' => array(
'apple8756apple',
'banana234banana',
'apple4apple',
'kiwi51kiwi'
)
);
$match = array(
'allowed' => array(
'apple*apple',
'banana234banana',
'kiwi*kiwi'
)
);
$allowed = '('.implode(')|(',$match['allowed']).')';
$allowed = str_replace(array('*'), array('[0-9]+'), $allowed);
foreach($values['fruit'] as $fruit){
if(preg_match('#'.$allowed.'#',$fruit))
$matched[] = $fruit;
}
print_r($matched);
See here: http://codepad.viper-7.com/8fpThQ
Try replacing /\d+/ in the first array with '*', then do array_diff() between the 2 arrays
Edit: after clarification, here's a more refined approach:
<?php
$allowed = str_replace("*", "\d+", $match['allowed']);
$passed = 0;
foreach ($values['fruit'] as $fruit) {
$count = 0;
preg_replace($allowed, "", $fruit, -1, $count); //preg_replace accepts an array as 1st argument and stores the replaces done on $count;
if ($count) $passed++;
}
if ($passed == sizeof($values['fruit']) {
echo 'hurray!';
} else {
echo 'fail';
}
?>
The solution above does not remove the need for a nested loop, but it merely lets PHP do the inner loop, which may be faster (you should actually benchmark it)