Generating Ordered (Weighted) Combinations of Arbitrary Length in PHP - php

Given a list of common words, sorted in order of prevalence of use, is it possible to form word combinations of an arbitrary length (any desired number of words) in order of the 'most common' sequences. For example,if the most common words are 'a, b, c' then for combinations of length two, the following would be generated:
aa
ab
ba
bb
ac
bc
ca
cb
cc
Here is the correct list for length 3:
aaa
aab
aba
abb
baa
bab
bba
bbb
aac
abc
bac
bbc
aca
acb
bca
bcb
acc
bcc
caa
cab
cba
cbb
cac
cbc
cca
ccb
ccc
This is simple to implement for combinations of 2 or 3 words (set length) for any number of elements, but can this be done for arbitrary lengths? I want to implement this in PHP, but pseudocode or even a summary of the algorithm would be much appreciated!

Here's a recursive function that might be what you need. The idea is, when given a length and a letter, to first generate all sequences that are one letter shorter that don't include that letter. Add the new letter to the end and you have the first part of the sequence that involves that letter. Then move the new letter to the left. Cycle through each sequence of letters including the new one to the right.
So if you had gen(5, d)
It would start with
(aaaa)d
(aaab)d
...
(cccc)d
then when it got done with the a-c combinations it would do
(aaa)d(a)
...
(aaa)d(d)
(aab)d(d)
...
(ccc)d(d)
then when it got done with d as the 4th letter it would move it to the 3rd
(aa)d(aa)
etc., etc.
<?php
/**
* Word Combinations (version c) 6/22/2009 1:20:14 PM
*
* Based on pseudocode in answer provided by Erika:
* http://stackoverflow.com/questions/1024471/generating-ordered-weighted-combinations-of-arbitrary-length-in-php/1028356#1028356
* (direct link to Erika's answer)
*
* To see the results of this script, run it:
* http://stage.dustinfineout.com/stackoverflow/20090622/word_combinations_c.php
**/
init_generator();
function init_generator() {
global $words;
$words = array('a','b','c');
generate_all(5);
}
function generate_all($len){
global $words;
for($i = 0; $i < count($words); $i++){
$res = generate($len, $i);
echo join("<br />", $res);
echo("<br/>");
}
}
function generate($len, $max_index = -1){
global $words;
// WHEN max_index IS NEGATIVE, STARTING POSITION
if ($max_index < 0) {
$max_index = count($words) - 1;
}
$list = array();
if ($len <= 0) {
$list[] = "";
return $list;
}
if ($len == 1) {
if ($max_index >= 1) {
$add = generate(1, ($max_index - 1));
foreach ($add as $addit) {
$list[] = $addit;
}
}
$list[] = $words[$max_index];
return $list;
}
if($max_index == 0) {
$list[] = str_repeat($words[$max_index], $len);
return $list;
}
for ($i = 1; $i <= $len; $i++){
$prefixes = generate(($len - $i), ($max_index - 1));
$postfixes = generate(($i - 1), $max_index);
foreach ($prefixes as $pre){
//print "prefix = $pre<br/>";
foreach ($postfixes as $post){
//print "postfix = $post<br/>";
$list[] = ($pre . $words[$max_index] . $post);
}
}
}
return $list;
}
?>

I googled for php permutations and got: http://www.php.happycodings.com/Algorithms/code21.html
I haven't looked into the code if it is good or not. But it seems to do what you want.

I don't know what the term is for what you're trying to calculate, but it's not combinations or even permutations, it's some sort of permutations-with-repetition.
Below I've enclosed some slightly-adapted code from the nearest thing I have lying around that does something like this, a string permutation generator in LPC. For a, b, c it generates
abc
bac
bca
acb
cab
cba
Probably it can be tweaked to enable the repetition behavior you want.
varargs mixed array permutations(mixed array list, int num) {
mixed array out = ({});
foreach(mixed item : permutations(list[1..], num - 1))
for(int i = 0, int j = sizeof(item); i <= j; i++)
out += ({ implode(item[0 .. i - 1] + ({ list[0] }) + item[i..], "") });
if(num < sizeof(list))
out += permutations(list[1..], num);
return out;
}
FWIW, another way of stating your problem is that, for an input of N elements, you want the set of all paths of length N in a fully-connected, self-connected graph with the input elements as nodes.

I'm assuming that when saying it's easy for fixed length, you're using m nested loops, where m is the lenght of the sequence (2 and 3 in your examples).
You could use recursion like this:
Your words are numbered 0, 1, .. n, you need to generate all sequences of length m:
generate all sequences of length m:
{
start with 0, and generate all sequences of length m-1
start with 1, and generate all sequences of length m-1
...
start with n, and generate all sequences of length m-1
}
generate all sequences of length 0
{
// nothing to do
}
How to implement this? Well, in each call you can push one more element to the end of the array, and when you hit the end of the recursion, print out array's contents:
// m is remaining length of sequence, elements is array with numbers so far
generate(m, elements)
{
if (m == 0)
{
for j = 0 to elements.length print(words[j]);
}
else
{
for i = 0 to n - 1
{
generate(m-1, elements.push(i));
}
}
}
And finally, call it like this: generate(6, array())

Related

How to transpose music chords with PHP?

I was wondering how would one create a function, in PHP, which is used for transposing some music chords.
I will try to explain how it works in music theory. I hope I don't forget something. If there are some mistakes, please help me to correct it.
1. The simple chords.
The simple chords are almost as simple as an alphabet and it goes like this:
C, C#, D, D#, E, F, F#, G, G#, A, A# B
From B it loops all over again to C. Therefore, If the original chord is E and we want to transpose +1, the resulting chord is F. If we transpose +4, the resulting chord is G#.
2. Expanded chords.
They work almost like the simple chords, but contain a few more characters, which can safely be ignored when transposing. For example:
Cmi, C#7, Dsus7, Emi, Fsus4, F#mi, G ...
So again, as with the simple chords, if we transpose Dsus7 + 3 = Fsus7
3. Non-root bass tone.
A problem arises when the bass plays a different tone than the chord root tone. This is marked by a slash after the chord and also needs to be transposed. Examples:
C/G, Dmi/A, F#sus7/A#
As with examples 1 and 2, everything is the same, but the part after the slash needs transpose too, therefore:
C/G + 5 = F/C
F#sus7/A# + 1 = Gsus7/B
So basically, imagine you have a PHP variable called chord and the transpose value transpose. What code would transpose the chord?
Examples:
var chord = 'F#sus7/C#';
var transpose = 3; // remember this value also may be negative, like "-4"
... code here ...
var result; // expected result = 'Asus7/E';
I have found an existed question on StackOverflow, at here. They talk about algorithm for chord-progressions.
How do I transpose music chords with PHP, by increasing or decreasing by semitones?
A quick solution:
<?php
// produces the expected result
echo transpose("F#sus7/C#",3);
function transpose($chord,$transpose)
{
// the chords
$chords = array("C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B");
$result = "";
// get root tone
$root_arr = explode("/",$chord);
$root = strtoupper($root_arr[0]);
// the chord is the first character and a # if there is one
$root = $root[0].((strpos($root, "#") !== false)?"#":"");
// get any extra info
$root_extra_info = str_replace("#","",substr($root_arr[0],1)); // assuming that extra info does not have any #
// find the index on chords array
$root_index = array_search($root,$chords);
// transpose the values and modulo by 12 so we always point to existing indexes in our array
$root_transpose_index = floor(($root_index + $transpose) % 12);
if ($root_transpose_index < 0)
{
$root_transpose_index += 12;
}
$result.= $chords[$root_transpose_index].$root_extra_info;
if(count($root_arr)>1)
{
// get the non root tone
$non_root = $root_arr[1];
// the chord is the first character and a # if there is one
$non_root = strtoupper($non_root[0]).((strpos($non_root, "#") !== false)?"#":"");
// get any extra info
$non_root_extra_info = str_replace("#","",substr($root_arr[1],1)); // assuming that extra info does not have any #
// find the index on chords array
$non_root_index = array_search($non_root,$chords);
// transpose the values and modulo by 12 so we always point to existing indexes in our array
$non_root_transpose_index = floor(($non_root_index + $transpose) % 12);
if ($non_root_transpose_index < 0)
{
$non_root_transpose_index += 12;
}
$result.= "/".$chords[$non_root_transpose_index].$non_root_extra_info;
}
return $result;
}
https://3v4l.org/Cd9Pg
lots of room for improvement in code, i just tried to code it to be easy to understand.
Here my regex idea with preg_replace_callback (use of anonymous function requires PHP 5.3).
function transpose($str, $t=0)
{
// the chords
$chords = ["C","C#","D","D#","E","F","F#","G","G#","A","A#","B"];
// set transpose, return if none
$t = (int)$t % 12 + 12; if($t % 12 == 0) return $str;
// regex with callback
return preg_replace_callback('~[A-G]#?~', function($m) use (&$chords, &$t) {
return $chords[(array_search($m[0], $chords) + $t) % 12];
}, $str);
}
Demo at eval.in (for testing the regex pattern [A-G]#? see regex101)
echo transpose("Cmi, C#7, Dsus7, Emi, Fsus4, F#mi, G C/G, Dmi/A, F#sus7/A#", -3);
Ami, A#7, Bsus7, C#mi, Dsus4, D#mi, E A/E, Bmi/F#, D#sus7/G
Okay, so there are a few things you want to handle.
First, you want to be able to loop around in the array. That's easy: use the modulus operator, which in php is %.
function transpose($chord, $increment) {
$map = array('A', 'A#', 'B', 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#');
// Get the index of the given chord
$index = array_search($chord, $map);
if($index === false)
return false;
// Get the transposed index and chord
$transpose_index = ($index + $increment) % count($map);
if($transpose_index < 0)
$transpose_index += count($map);
return $map[$transpose_index];
}
Second, you want to be able to strip out the actual chords that matter to you. You can do this using a regular expression (RegEx):
function transposeFull($chords, $increment) {
// This RegEx looks for one character (optionally followed by a sharp).
// .\#?
// This RegEx looks for an optional series of characters which are not /
// [^\/]*
// Put them together to get a RegEx that looks for an expanded chord
// (.\#?)([^\/]*)
// Then, do it again, but add a / first, and make it optional.
// (\/(.\#?)([^\/]*))?
$regex = '%(.\#?)([^\/]*)(\/(.\#?)([^\/]*))?%';
// Note that the () allow us to pull out the matches.
// $matches[0] is always the full thing.
// $matches[i] is the ith match
// (so $matches[3] is the whole optional second chord; which is not useful)
$matches = array();
preg_match($regex, $chords, $matches);
// Then, we get any parts that were matched and transpose them.
$chord1 = (count($matches) >= 2) ? transpose($matches[1], $increment) : false;
$expanded1 = (count($matches) >= 2) ? $matches[2] : '';
$chord2 = (count($matches) >= 5) ? transpose($matches[4], $increment) : false;
$expanded2 = (count($matches) >= 6) ? $matches[5] : '';
// Finally, put it back together.
$chords = '';
if($chord1 !== false)
$chords .= $chord1.$expanded1;
if($chord2 !== false)
$chords .= '/'.$chord2.$expanded2;
return $chords;
}

Create fixed length non-repeating permutation of larger set

I know this topic is much discussed but I can't seem to find any implementation that fits my needs.
I have the following set of characters:
a b c d e f g h
I want to get all possible permutations or combinations (non repeating), but on a limited (variable) set of characters, meaning if I input the characters and the number 2, the results should look like
ab ba ac ca ad da ae ea af fa ag ga ah ha
bc cb bd db be eb bf fb bg gb bh hb
cd dc ce ec cf fc cg gc ch hc
de ed df fd dg gd dh hd
ef fe eg ge eh he
fg gf fh hf
gh hg
I hope you understand where I'm going with this. I currently have an implementation that gives me the permutations of all characters, but I can't wrap my head around how to implement a limited space for those permutations:
public function getPermutations($letters) {
if (strlen($letters) < 2) {
return array($letters);
}
$permutations = array();
$tail = substr($letters, 1);
foreach ($this->getPermutations($tail) as $permutation) {
$length = strlen($permutation);
for ($i = 0; $i <= $length; $i++) {
$permutations[] = substr($permutation, 0, $i) . $letters[0] . substr($permutation, $i);
}
}
return $permutations;
}
If you only need one element at a time, you can save on memory by generating each element individually.
If we wanted to generate a random string in your set of expected outputs, we could use this algorithm:
Given a set of characters S, and a desired output length K:
While the output has less than K characters:
Pick a random number P between 1 and |S|.
Append the P'th character to the output.
Remove the P'th character from S.
where |S| is the current number of elements in S.
We can actually encode this sequence of choices into an integer. One way to do that is to change the algorithm as such:
Given a set of characters S, and a desired output length K:
Let I = 0.
While the output has less than K characters:
I = I * (|S| + 1).
Pick a random number P between 1 and the number of elements in S.
I = I + P.
Append the P'th character to the output.
Remove the P'th character from S.
After running this algorithm, the value I will uniquely encode this particular sequence of choices. It basically encodes this as a mixed-radix number; one digit uses base N, the next uses N-1, and so on until the last digit which is base N-K+1 (N being the number of letters in the input).
Naturally, we can also decode this again, and in PHP, that would be something like this:
// Returns the total number of $count-length strings generatable from $letters.
function getPermCount($letters, $count)
{
$result = 1;
// k characters from a set of n has n!/(n-k)! possible combinations
for($i = strlen($letters) - $count + 1; $i <= strlen($letters); $i++) {
$result *= $i;
}
return $result;
}
// Decodes $index to a $count-length string from $letters, no repeat chars.
function getPerm($letters, $count, $index)
{
$result = '';
for($i = 0; $i < $count; $i++)
{
$pos = $index % strlen($letters);
$result .= $letters[$pos];
$index = ($index-$pos)/strlen($letters);
$letters = substr($letters, 0, $pos) . substr($letters, $pos+1);
}
return $result;
}
(Note that for simplicity, this particular decoding algorithm does not correspond exactly to the encoding algorithm I previously described, but maintains the desirable property of a given $index mapping to a unique result.)
To use this code, you would do something like this:
$letters = 'abcd';
echo '2 letters from 4:<br>';
for($i = 0; $i < getPermCount($letters, 2); $i++)
echo getPerm($letters, 2, $i).'<br>';
echo '<br>3 letters from 4:<br>';
for($i = 0; $i < getPermCount($letters, 3); $i++)
echo getPerm($letters, 3, $i).'<br>';
?>
$strings = get_perm( range('a', 'h'), 4 );
function get_perm( $a, $c, $step = 0, $ch = array(), $result = array() ){
if( $c == 1 ){ //if we have last symbol in chain
for( $k = 0; $k < count( $a ); $k++ ){
if( #in_array( $k, $ch ) ) continue; // if $k exist in array we already have such symbol in string
$tmp = '';
foreach( $ch as $c ) $tmp .= $a[$c]; // concat chain of previous symbols
$result[] = $tmp . $a[$k]; // and adding current + saving to our array to return
}
}else{
for( $i = 0; $i < count( $a ); $i++ ){
if( #in_array( $i, $ch ) ) continue;
$ch[$step] = $i; // saving current symbol for 2 things: check if that this symbol don't duplicate later and to know what symbols and in what order need to be saved
get_perm( $a, $c-1, $step+1, $ch, &$result );
// recursion,
// decrementing amount of symbols left to create string,
// incrementing step to correctly save array or already used symbols,
// $ch - array of already used symbols,
// &$result - pointer to result array
}
}
return $result;
}
NOTICE
a-h with 6 symbols = 20k values in array
a-z with 4 symbols = 358799 values in array
So a-z with 10 symbols will die for sure =) It will require too much memory.
You need to try to save output to file or database if you would need big amount of values. Or extend memory limit to php but not sure if this is best way.

Counting possibilities in a char. combination using a consecutive repetition criterion

In PHP, given
the final string length
the range of characters it can use
min consecutive repetition count possible
how can you calculate the number of matches that fits these criteria?To draw a better picture…
$range = array('a','b','c');
$length = 2; // looking for 2 digit results
$minRep = 2; // with >=2 consecutive characters
// aa,bb,cc = 3 possibilities
another one:
$range = array('a','b','c');
$length = 3; // looking for 3 digit results
$minRep = 2; // with >=2 consecutive characters
// aaa,aab,aac,baa,caa
// bbb,bba,bbc,abb,cbb
// ccc,cca,ccb,acc,bcc
// 5 + 5 + 5 = 15 possibilities
// note that combos like aa,bb,cc are not included
// because their length is smaller than $length
last one:
$range = array('a','b','c');
$length = 3; // looking for 3 digit results
$minRep = 3; // with >=3 consecutive characters
// aaa,bbb,ccc = 3 possibilities
So basically, in the 2nd example the 3rd criterion made it catch e.g. [aa]b in aab because a was repeating consecutively more than once, whereas [a]b[a] wouldn't be a match because those a's are separate.
Needless to say, none of the variables is static.
Got it. All credit to leonbloy #mathexchange.com.
/* The main function computes the number of words that do NOT contain
* a character repetition of length $minRep (or more). */
function countStrings($rangeLength, $length, $minRep, &$results = array())
{
if (!isset($results[$length]))
{
$b = 0;
if ($length < $minRep)
$b = pow($rangeLength, $length);
else
{
for ($i = 1; $i < $minRep; $i++)
$b += countStrings($rangeLength, $length - $i, $minRep, $results);
$b *= $rangeLength - 1;
}
$results[$length] = $b;
}
return $results[$length];
}
/* This one answers directly the question. */
function printNumStringsRep($rangeLength, $length, $minRep)
{
$n = (pow($rangeLength, $length)
- countStrings($rangeLength, $length, $minRep));
echo "Size of alphabet : $rangeLength<br/>"
. "Size of string : $length<br/>"
. "Minimal repetition : $minRep<br/>"
. "<strong>Number of words : $n</strong>";
}
/* Prints :
*
Size of alphabet : 3
Size of string : 3
Minimal repetition : 2
Number of words : 15
*
*/
printNumStringsRep(3, 3, 2);
I think it is best to handle this with math.
$range = array('a','b','c');
$length = 3; // looking for 3 digit results
$minRep = 2; // with >=2 consecutive characters
$rangeLength = count($range);
$count = (pow($rangeLength,$length-$minRep+1) * ($length-$minRep+1)) - ($rangeLength * ($length-$minRep)); // is the result
Now, $count is getting true result for three situation. But it may not be general formula and need to improve.
Try to explain it:
pow($rangeLength,$length-$minRep+1)
in this, we count repetitive characters like as one. For instance, in second example that you gave, we think in aab, aa is a one character. Because, two characters need to change together. We think now there is two character like xy. So there is same possibilities for both character a, b, and c namely 3 ($rangeLength) possible value for two characters($length-$minRep+1). So 3^2=9 is possible situations for second example.
We calculate 9 is for just xy not yx. For this, we multiply length of xy ($length-$minRep+1). And then we have 18.
It can be seemed that we calculated the result, but there is a repeat in our calculation. We didn't reckon with this situation: xy => aaa and yx => aaa. For this, we calculate and substract repeated results
- ($rangeLength * ($length-$minRep))
So after this, we get result.
As i said begining of the description, this formula may need to improve.
With Math, work becomes really complex. But, there is always a way, even not beautiful as much as Math. We can create all possible strings with php and control them with regexp like below:
$range = array('a','b','c');
$length = 3;
$minRep = 2;
$rangeLength = count($range);
$createdStrings = array();
$matchedStrings = array();
function calcIndex(){
global $range;
global $length;
global $rangeLength;
static $ret;
$addTrigger = false;
// initial values
if(is_null($ret)){
$ret = array_fill(0, $length, 0);
return $ret;
}
for($i=$length-1;$i>=0;$i--){
if($ret[$i] == ($rangeLength-1)) {
if($i==0) return false;
$ret[$i] = 0;
}
else {
$ret[$i]++;
break;
}
}
return $ret;
}
function createPattern()
{
global $minRep;
$patt = '/(.)\\1{'.($minRep-1).'}/';
return $patt;
}
$pattern = createPattern();
while(1)
{
$index = calcIndex();
if($index === false) break;
$string = '';
for($i=0;$i<$length;$i++)
{
$string .= $range[$index[$i]];
}
if(!in_array($string, $createdStrings)){
$createdStrings[] = $string;
if(preg_match($pattern, $string)){
$matchedStrings[] = $string;
}
}
}
echo count($createdStrings).' is created:';
var_dump($createdStrings);
echo count($matchedStrings).'strings is matched:';
var_dump($matchedStrings);

Increment through A-Za-z0-9 in expanding string with PHP

I'm trying to generate strings in PHP with a group of valid characters, cycling through them and appending an extra character on the end of the string, until maximum length is reached. For example, desired output:
a,b,c,d,e,f,aa,ab,ac,ad,ae,af,ba,bb,bc,bd,be,bf,ca,cb..etc
This is my PHP function so far:
<?php
$chars = Array('a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',
'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','X','Y','Z',
'1','2','3','4','5','6','7','8','9','0');
$maxlen = 10;
$input = $chars[0];
while (1):
echo buildInput($maxlen, $chars, $input) . "\n";
endwhile;
function buildInput($maxlen, $chars, $previous)
{
if (array_search(substr($previous, -1), $chars) == sizeof($chars) - 1):
// end of input cycle reached, add another character
$previous = $previous . $chars[0];
endif;
if (strlen($previous) > $maxlen):
die('Max length reached');
endif;
// Remove last character, and append incremented char
$input = substr($previous, 0, -1);
$input = $input . $chars[array_search(substr($previous, -1), $chars)+1];
return $input;
}
?>
It only increments the last character of the string which gets to 0, then appends 'a' and starts over but without trying all the other possible permutations.
Could someone help me with a better method?
Is this the kind of thing you want?
<?php
$chars = array('a','b','c');
$max_length = 3;
function build($base_arr, $ctr) {
global $chars;
global $max_length;
$combos = array();
foreach ($base_arr as $base) {
foreach ($chars as $char) {
echo $base, $char, '<br />';
$combos[] = $base.$char;
}
}
if ($ctr < $max_length) {
build($combos, $ctr + 1);
}
}
foreach ($chars as $char) {
echo $char, '<br />';
}
build($chars, 2);
?>
It'll give you: a, b, c, aa, ab, ac, ba, bb, bc, ca, cb, cc, ..., bcc, caa, cab, cac, cba, cbb, cbc, cca, ccb, ccc.
Your array is so large, though, that using this method on it would take up way too much memory to work. Out of 62 characters (A-Z, a-z, 0-9), the number of possible 10-character permutations is 8.4 x 10^17; so hopefully, you'll be able to find a more efficient method or figure out a way to get the result you want without having to cycle through such a large array. I hope you find what you're looking for!
If you limit yourself to 0-9,a-z (only lower case), then you could use base_convert for this and do it in one line:
for($i = 0; $i < 1000; $i++) echo base_convert($i, 10, 36) . '<br/>';
Here's a demo.
This will print 200 letters: a,b,c,d,...,aa,...,cq
The buildString function will build our string from the least significant number (right) to the most significant (left). By performing a modulus division, you will find the array position of the next character. Add this character to the front of your string, and divide
your number by the size of your array (which is the base number in your character based number system), ignoring the rest.
To explain the method using our normal 10-based number system and the input of 123, you would simply pick the last digit, 3, divide the input by 10, pick the last digit 2, divide the input by 10, pick the last digit 1, divide the input by 10. The input is now 0 and your output is ready...
$chars = array('a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q'
,'r','s','t','u','v','w','x','y','z','A','B','C','D','E','F','G','H','I','J'
,'K','L','M','N','O','P','Q','R','S','T','U','V','X','Y','Z','1','2','3','4'
,'5','6','7','8','9','0');
$numChars = count($chars);
// Output numbers from 1 to 200 (a to cq)
for($i = 1; $i <= 200; $i++) {
echo buildString($i).'<br>';
}
// Will also work fine for large numbers - output "dxSA"
echo buildString(1000000).'<br>';
function buildString($int) {
global $chars;
global $numChars;
$output = '';
while($int) {
$output = $chars[($int-1) % $numChars] . $output;
$int = floor(($int-1) / $numChars);
}
return $output;
}
If you have access to gmp extension and PHP 5.3.2+ this will work for the charset you specified:
$result = strtr(
gmp_strval(gmp_init($i, 10), 62),
'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
);

function to generate 26 (A-Z) * 26 (A-Z) * 26 (A-Z)

I'd like to create a function which will generate a 3 char key string after each loop.
There are 26 chars in the alphabet and I'd like to generate totally unique 3 char keys (A-Z).
The output would be 17,576 unique 3 char keys (A-Z) - not case sensitive.
Can anyone give me an idea on how to create a more elegant function without randomly generating keys and checking for duplicate keys?
Is it possible
Thank you.
If I read your question correctly you want to have a function that returns a key of the form "ABC" where each of the letters is selected randomly but the same combination of letters is never issued twice.
Do you mean never issued twice per execution of the code or never issued twice "ever"?
Either way I would look at generating all possible combinations, shuffling them and then storing them either in a member array or a file/database depending on your needs. You will also need to store an index which you simply increment each time you issue a key.
<?php
private $keys = array();
private $keyIndex = -1;
function generateKeys()
{
$this->keys = array();
$this->keyIndex = -1;
for ($i=1; $i<=26; $i++)
{
for ($j=1; $j<=26; $j++)
{
for ($k=1; $k<=26; $k++)
{
$this->keys[] = sprintf('%c%c%c', 64+$i, 64+$j, 64+$k);
}
}
}
shuffle($this->keys);
return $this->keys;
}
function getKey()
{
$this->keyIndex++;
return $this->keys[$this->keyIndex];
}
?>
var use any math library to generate a random number from 0 to 26 cubed - 1, then assign the letters based on the value of that random integer...
in the following, the backslash represents integer division, i.e., drop any fractional remainder
first character = ascii(65 + integer modulus 26)
second character = ascii(65 + (integer \ 26) modulus 26
third character = ascii(65 + ((integer \ 26) \ 26) modulus 26
ahh, thx to #Graphain I realize you want to eliminate any chance of picking the same three character combination again... welll then here's a way...
Create a collection (List?) containing 676 (26*26) 32 bit integers, all initialized to 2^26-1 (so bits 0-25 are all set = 1). Put 26 of these integers into each of 26 inner dictionaries so this becomes an dictionary of 26 dictionaries each of which has 26 of these integers. Label the inner dictionaries A-Z. Within each inner array, label the integers A-Z.
Randomly pick one of 26 outer arrays (this sets the first character).
From the array chosen randomly pick one of it's contained inner arrays. This sets the second character.
Then randomly pick a number from 0 to n, (where n is the count of bits in the integer that are still set to 1)... Which bit in the number determines the last character.
Set that bit to zero
If all bits in the integer have been set to zero, remove integer from array
If this inner array is now empty, (all integers are gone) remove it from outer array.
Repeat from step 2 until outer array is empty or you get tired...
Here's some sample code (not tested):
public class RandomAlphaTriplet
{
private static readonly Dictionary<char, Dictionary<char, int>> vals =
new Dictionary<char, Dictionary<char, int>>();
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static readonly Random rnd = new Random(DateTime.Now.Millisecond);
private const int initVal = 0x3FFFFFF;
static RandomAlphaTriplet()
{
foreach (var c in chars)
vals.Add(c, chars.ToDictionary(
ic => ic, ic => initVal));
}
public static string FetchNext()
{
var c1 = chars[rnd.Next(vals.Count)];
var inrDict = vals[c1];
var c2 = chars[rnd.Next(inrDict.Count)];
var intVal = inrDict[c2];
var bitNo = rnd.Next(BitCount(intVal));
var bitPos = 0;
while (bitNo > 0)
{
if ((intVal & 0x0001) > 0) bitNo--;
bitPos++;
intVal <<= 1;
}
var c3 = chars[bitPos];
inrDict[c2] &= ~(1 << bitPos);
if (inrDict[c2] == 0) inrDict.Remove(c2);
if (vals[c1].Count == 0) vals.Remove(c1);
return string.Concat(c1, c2, c3);
}
private static int BitCount(int x)
{ return ((x == 0) ? 0 : ((x < 0) ? 1 : 0) + BitCount(x << 1)); }
}
I can make it to only one key if you are interested. (I don't know if it is possible at all to have no key to check for duplication).
function getAlpha($Index) {
$Alphabets = 'abcdefghijklmnopqrstuvwxyz';
$Char = substr($Alphabets, $Index, 1);
return $Char;
}
function getRandom($Index) {
$RandOne = $Index % 26;
$RandTwo = ($Index / 26 ) % 26;
$RandThree = ($Index / 26*26) % 26;
$AlphaOne = getAlpha($RandOne);
$AlphaTwo = getAlpha($RandTwo);
$AlphaThree = getAlpha($RandThree);
$Rand = $AlphaOne.$AlphaTwo.$AlphaThree;
return $Rand;
}
$Rands = array();
function getRandom() {
global $Rands;
while (true) {
$RandNum = rand(0, 26*26*26 - 1);
if (isset($Rands[$RandNum]))
continue;
$Rand = getRandom($RandNum);
$Rands[$RandNum] = $Rand;
return $Rand;
}
}
This only need one key to look up.
Hope this helps.
Ehrm is this not “simple” combinatorial math? (Select all combinations of 3 out of 26? Or permutations of 3 out of 26?)

Categories