I am after a function which will let me sort a bunch of filenames over 4 arrays.
However, the a file must always end up in the same array - even if the number of files change.
Eg if I have files
myfile.html
anotherfile.html
morefiles.html
test.html
and my arrays
array1, array2, array3, array4
If I run this function then
array1 might get myfile.html and anotherfile.html
If I run it again and add some more files (or less files, like not pass anotherfile.html) then I would still expect array1 to get myfile.html
So just need some way to hash the filename which I can then use to point to a certain array, so not a random or one that checks how many files are in each array needs to be consistent.
There are many different ways to solve a task like this, what is below is a very basic introduction to the topic. If it is useful, great otherwise I hope it at least gives an idea what where you might want to go (or not!).
This example simple takes a hash of the filename (in this case MD5 just because you're probably familiar with it). The sscanf just gets the first character of the hash and turns it into a number between 0 and 15 (since md5() returns a hexadecimal number). Since we only want to distribute between four arrays, the modulus operator (%) is used so that $num will always result in 0, 1, 2 or 3 which is then used as an array key (c.f. your $array1, $array2, etc.).
$files = array('a.html','b.html','c.html','d.html');
$arrays = 4;
$array = array_fill(0, $arrays, array());
// For each file name put it into the appropriate slot in $array
foreach ($files as $filename) {
sscanf(md5($filename), '%1x', $hex);
$key = $hex % $arrays;
$array[$key][] = $filename;
}
// See what happened
var_dump($array);
For this particular example, the resulting array (which you can push into your separate variables if you like) has the following structure:
$array = array(
0 => array()
1 => array('c.html')
2 => array('d.html')
3 => array('a.html', 'b.html')
);
Related
I've come to a mathematical problem which for I can't program the logic.
Let me explain it with an example:
Let's say I have 4 holes and 3 marbles, the holes are in order and my marbles are A,B and C and also in order.
I need to get every posible ORDERED combination:
ABC4
AB3C
A2BC
1ABC
This is very simple, but what if the number of holes changes? Let's say now I have 5 holes.
ABC45
AB3C5
A2BC5
1ABC5
AB34C
A2B4C
1AB4C
A23BC
1A3BC
12ABC
Now let's say we have 5 holes and 4 marbles.
ABCD5
ABC4D
AB3CD
A2BCD
1ABCD
And this can be any number of holes and any number of marbles.
The number of combinations is given by:
$combinations = factorial($number_of_holes)/(factorial($number_of_marbles)*factorial($number_of_holes-$number_of_marbles)))
(Here it is the factorial function in case you need it)
function factorial($number) {
if ($number < 2) {
return 1;
} else {
return ($number * factorial($number-1));
}
}
What I need and can't figure out how to program, is a function or a loop or something, that returns an array with the position of the holes, given X numbers of holes and Y number of marbles.
For first example it would be: [[4],[3],[2],[1]], for second: [[4,5],[2,5],[1,5],[3,4],[2,4],[1,5],[2,3],[1,3],[1,2]], for third: [[5],[4],[3],[2],[1]].
It doesn't have to be returned in order, I just need all the elements.
As you can see, another approach is the complementary or inverse or don't know how to call it, but the solution is every combinations of X number of free holes given Y number of holes, so, If I have 10 holes, and 5 marbles, there would be 5 free holes, the array returned would be every combination of 5 that can be formed with (1,2,3,4,5,6,7,8,9,10), which are 252 combinations, and what I need is the 252 combinations.
Examples for the 2nd approach:
Given an array=[1,2,3,4], return every combination for sets of 2 and 3.
Sets of 2
[[1,2],[1,3],[1,4],[2,3],[2,4],[3,4]]
Sets of 3
[[1,2,3],[1,2,4],[1,3,4],[2,3,4]]
What I need is the logic to do this, I'm trying to do it in PHP, but I just can't figure out how to do it.
The function would receive the array and the set size and would return the array of sets:
function getCombinations($array,$setize){
//magic code which I can't figure out
return array(sets);
}
I hope this is clear enough and someone can help me, I've been stuck for several days now, but it seems to be just too much for me to handle by myself.
This post, PHP algorithm to generate all combinations of a specific size from a single set, is for all possible combinations, repeating the elements and order doesn't matter, its a good lead, I did read it, but it doesn't solve my problem, it's very different. I need them without repeating the elements and ordered as explained.
Let's say if I have already a set of [3,4] in my array, I don't want [4,3] as an other set.
Here's a recursive solution in PHP:
function getCombinations($array, $setsize){
if($setsize == 0)
return [[]];
// generate combinations including the first element by generating combinations for
// the remainder of the array with one less element and prepending the first element:
$sets = getCombinations(array_slice($array, 1), $setsize - 1);
foreach ($sets as &$combo) {
array_unshift($combo, $array[0]);
}
// generate combinations not including the first element and add them to the list:
if(count($array) > $setsize)
$sets = array_merge($sets, getCombinations(array_slice($array, 1), $setsize));
return $sets;
}
// test:
print_r(getCombinations([1, 2, 3, 4], 3));
Algorithm works like this:
If setsize is 0 then you return a single, empty combination
Otherwise, generate all combinations that include the first element, by recursively generating all combinations off the array excluding the first element with setsize - 1 elements, and then prepending the first element to each of them.
Then, if the array size is greater than setsize (meaning including the first element is not compulsory), generate all the combinations for the rest of the list and add them to the ones we generated in the second step.
So basically at each step you need to consider whether an element will be included or excluded in the combination, and merge together the set of combinations representing both choices.
Firstly, I'm still a beginner to PHP so my terminology may be a bit wrong - please let me know and I'll amend the question.
Task:
I have a function which I'm looking to test to see how long it takes to run at large scale. I need to pass it data in the following format:
$data = [
[ 'A', 'B', 'C', 'D' ],
[ 'C', 'B' ],
[ 'C', 'B' ],
];
As you can see, the number of items in an array can vary - although they are drawn from an overall set (range of integers or letters).
For my testing purposes, I'd like to be able to change the number of items in each nested array.
I also need to be able to change how many arrays are created.
Example tests I'd like to perform
e.g.
Run one test with a small number of arrays, but a large amount of
data within each.
Run a second test with a large number of arrays, but
a small amount of data in each
A third with huge numbers of items
and arrays.
The story so far
I was Googling and know I could use range() to create an array that count sequentially (or with a certain step). But I have to set the upper and lower bounds for each array.
I figure I could use a do.. while loop to add X number of arrays within $data, but I'm not sure how I can vary the amount of data within each array.
For the function to work, I need there to be either a letter or integer repeated. In other words: I couldn't have the first array count from 1-10, the next 11-21. It's as if all the data is drawn from the pool of integers 1-10,000,000.
Bonus points if the data can be randomized in order in each array.
Really appreciate any guidance and pointers on what to use / research - I'm sure this is a totally n00b question.
Many thanks in advance.
Generate a random range:
range(mt_rand(0, 100), mt_rand(101, 1000))
Generate an array of letters from a range (65 = A, 90 = Z):
array_map('chr', range(65, 90))
Generate a random order:
$data = range(..);
shuffle($data);
Take a random slice of an array:
$data = range(..);
$data = array_slice($data, mt_rand(0, count($data) - 1), mt_rand(1, count($data)));
Generate arrays of random length:
for ($i = 0, $length = mt_rand(0, 100); $i < $length; $i++) {
$data[] = ..;
}
You can nest two of those to generate randomly long arrays of randomly long arrays.
Now combine all these techniques as needed to spit out the kind of test data you want.
This is a question on ability of PHP to use arrays in the way I want.
Consider arrays below:
1) array(10, 20, 30, 40, 50, 60, 70); //aka "shorthand"
2) array(10 => 1, 20 => 1, 30 => 1, 40 => 1, 50 => 1, 60 => 1, 70 => 1);
For my purposes they both have the same numbers essentially, just represented differently.
Naturally, I prefer the first way, as it lists just the numbers I need. It also makes more sense to me as my values are the numbers that I want to operate on. In #2 the values are all 1s, and if I need to loop through my "values of interest", I actually need to loop through array keys. In my legacy code however my arrays are a tad longer and tend to use function in_array, which has big oh of N. I needed a faster lookup on values of the array, so I rewrote the array as in way #2 and used function array_key_exists, which I presume is big oh of 1. Due to large codebase I am basically ending up with two array representations that require different code to work on them. And I don't like that.
I prefer to write my arrays as in #1... So in some ideal world I could write arrays as in #1 but have big oh of 1 lookup on them, when I need to check whether a value exists in array.
Is there such a way? If not, what is the next best thing?
Update: using array flip option:
3) if (in_array(10, [10,20,30,40,50,60,70])) {}
4) if (array_key_exists(10, array_flip([10,20,30,40,50,60,70]))) {}
You can use hashes/dictionaries to achieve a theoretical O(1) speed. Your suggestion #2 is capable of doing that.
Hashes/dictionaries are O(1) best case and average case, O(N) in worst case.
If you are bonded to the use of arrays, the answer to your question depends on whether the array's content is changed frequently or not.
Array doesn't change frequently
If the array is not changed frequently, you could use the function array_flip once, in order to make a searchable version of your array and keep it for as long as possible.
In this way you have once an operation that is O(n), and every time you search you have operations that are near to O(1), using array_key_exists.
Array changes frequently
In this case to use in_array, wich is O(n), is almost equivalent to (if not better than) using array_flip with array_key_exists, since array_flip is O(n) itself and array_key_exists is O(1) at best, and you would have to perform both of them everytime.
It would be an interesting solution to keep the array sorted, and then use an optimized search algorithm, that, with an ordered array would be at worst O(log n).
Binary search could be a good search algorithm for that purpose:
function binarySearch($needle, $array)
{
$start = 0;
$end = count($array) - 1;
while ($start <= $end) {
$middle = (int) ($start + ($end - $start) / 2);
if ($needle < $array[$middle]) {
$end = $middle - 1;
} else if ($needle > $array[$middle]) {
$start = $middle + 1;
} else {
return $needle;
}
}
return false;
}
This binary search sample is taken from here.
To keep the array sorted you would have to sort it the first time (sort function should be fine for that, since you should use it once), and then add new items splitting the array where the element should be placed, put the element at the end of the first part of the array and merge the arrays back.
This would make the array maintainance more expensive, but would improve search performance.
I have a PHP script where I have an array of integers, let's say $forbidden.
I want to get a random integer from 1 to 400 that is not in $forbidden.
Of course, I don't want any loop which breaks when rand gives a working result. I'd like something more effective.
How do you do this ?
Place all forbidden numbers in an array, and use array_diff from range(1,400). You'll get an array of allowed numbers, pick a random one with array_rand().
<?php
$forbidden = array(2, 3, 6, 8);
$complete = range(1,10);
$allowed = array_diff($complete, $forbidden);
echo $allowed[array_rand($allowed)];
This way you're removing the excluded numbers from the selection set, and nullifying the need for a loop :)
Produce an array of the allowed numbers. Find out the number in this array. Select one of those randomly.
I have an array, such like:
$hex = array("0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f");
I want to return 6 random elements as a string (eg. 1a3564):
$random_color = array_rand($hex,6);
I thought imploding $random_color would do the trick:
echo implode($random_color);
But array_rand() stores positions of elements in parent array, not this array elements, so I get something like:
259111213 instead of 259bcd.
I know this does exactly what I want:
echo $hex[$random_color[0]];
echo $hex[$random_color[1]];
echo $hex[$random_color[2]];
echo $hex[$random_color[3]];
echo $hex[$random_color[4]];
echo $hex[$random_color[5]];
But:
is there any way to store array elements within array_rand()? Why it stores elements' positions instead of elements in the first place?
what's the best way to do what I want to achieve?
why does array_rand() NEVER choose a letter as the first element, and almost never as the second/third (99% of generated colors look like 11111a 12345c 123456)?
Random colors should be generated in simplier way:
printf('%02x%02x%02x',mt_rand(0,255),mt_rand(0,255),mt_rand(0,255));
or
printf('%06x',mt_rand(0,16777215));
If you need to save color to variable, use sprintf instead of printf
Since the items are all different, you can turn them into keys rather than values, then use array_rand on the result:
implode('', array_rand(array_flip($hex), 6));
However, there may be a better way of achieving your overall goal. For example, if the overall goal allows for repetitions of digits, simply generate a random number from 0 through 0xFFFFFF and convert to a hex string:
dechex(mt_rand(0, 0xFFFFFF));
Why it stores elements' positions instead of elements in the first place?
From the manual page:
This is done so that you can pick random keys as well as values out of the array.
why does array_rand() NEVER choose a letter as the first element, and almost never as the second/third (99% of generated colors look like 11111a 12345c 123456)?
array_rand uses rand (php_rand, in the C source). Depending on your system, php_rand is rand, random or lrand48. rand is a particularly poor random number generator.
array_rand() returns the keys of the randomly picked elements (see manual, section Return Values).
In order for it to work as expected, use array_flip() to retrieve the keys:
$random_color = array_rand(array_flip($hex), 6);
As for the "strange" results where there are almost no letters first elements, IDEOne and my server seem to reproduce these findings. A local machine running in my office (still running Debian etch / PHP 5.2.9) seems to disagree and evenly distribute elements from $hex... Seems to be a PHP version thing?
you are close try this:
$hex = array("0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f");
shuffle($hex);
echo sub_str(implode('',$hex),0,6);
If you don't need to maintain the order of the $hex array, you could substitute this with shuffle(). Something like this (codepad example):
<?php
$hex = array("0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f");
shuffle($hex);
echo implode(array_slice($hex, 0, 6));
If you just want a random string of hex digits, you could also do something like
substr(md5(time()),-6);
or
substr(md5(uniqid()),-6);
You would get similar results without having to mess with the array.
is there any way to store array elements within array_rand()? Why it
stores elements' positions instead of elements in the first place?
According to array_rand documentation, it returns the array 'keys', not the 'values'. Since your array is not an associative array, the keys are numbers. You'd need to do (untested):
$result = "";
$random_color = array_rand($hex, 6);
foreach ($random_color as $randomIndex) {
$result = $result . $hex[$randomIndex];
}
Don't use array shuffle or array_rand because elements cannot repeat with that approach. That's not what you are trying to do.
what's the best way to do what I want to achieve?
If you want to generate a random color, you can use:
$color = '';
while(strlen($c) < 6) {
$color .= sprintf("%02X", mt_rand(0, 255));
}
why does array_rand() NEVER choose a letter as the first element, and
almost never as the second/third (99% of generated colors look like
11111a 12345c 123456)?
You may need to initialize the random numbers generator, but this is just a guess (see Timur's comment to this answer).
mt_srand((double)microtime()*1000000);