Implement ROT13 with PHP - php

I found a string after reading funny things about Jon Skeet, and I guessed that it was in ROT13. Before just checking my guess, I thought I'd try and decrypt it with PHP. Here's what I had:
$string = "Vs lbh nfxrq Oehpr Fpuarvre gb qrpelcg guvf, ur'q pehfu lbhe fxhyy jvgu uvf ynhtu.";
$tokens = str_split($string);
for ($i = 1; $i <= sizeof($tokens); $i++) {
$char = $tokens[$i-1];
for ($c = 1; $c <= 13; $c++) {
$char++;
}
echo $char;
}
My string comes back as AIaf you aasakaead ABruacae Sacahnaeaiaer to adaeacrypt tahais, ahae'ad acrusah your sakualal waitah ahais alaauagah.
My logic seems quite close, but it's obviously wrong. Can you help me with it?

Try str_rot13.
http://us.php.net/manual/en/function.str-rot13.php
No need to make your own, it's built-in.

Here is a working implementation, without using the nested loop. You also don't need to split the string into an array, since you can index individual characters just like an array with strings in PHP.
You need to know that ASCII upper-case characters range from 65 - 99, and lower-case characters range from 97 - 122. If the current character is in one of those ranges, add 13 to its ASCII value. Then, you check if you should have rolled over to the beginning of the alphabet. If you should've rolled over, subtract 26.
$string = "Vs lbh nfxrq Oehpr Fpuarvre gb qrpelcg guvf, ur'q pehfu lbhe fxhyy jvgu uvf ynhtu.";
for ($i = 0, $j = strlen( $string); $i < $j; $i++)
{
// Get the ASCII character for the current character
$char = ord( $string[$i]);
// If that character is in the range A-Z or a-z, add 13 to its ASCII value
if( ($char >= 65 && $char <= 90) || ($char >= 97 && $char <= 122))
{
$char += 13;
// If we should have wrapped around the alphabet, subtract 26
if( $char > 122 || ( $char > 90 && ord( $string[$i]) <= 90))
{
$char -= 26;
}
}
echo chr( $char);
}
This produces:
If you asked Bruce Schneier to decrypt this, he'd crush your skull with his laugh.

If you want to do this yourself, instead of using an existing solution, you need to check whether each letter is at the first or second half of the alphabet. You can't naively add 13 (also, why are you using a loop to add 13?!) to each character. You must add 13 to A-M and subtract 13 from N-Z. You must also, not change any other character, like space.
Alter your code to check each character for what it is before you alter it, so you know whether and how to alter it.

This is not working because z++ is aa
$letter = "z";
$letter++;
echo($letter);
returns aa not a
EDIT: A possible alternative solution not using the built in is
$string = "Vs lbh nfxrq Oehpr Fpuarvre gb qrpelcg guvf, ur'q pehfu lbhe fxhyy jvgu uvf ynhtu.";
$tokens = str_split($string);
foreach($tokens as $char)
{
$ord = ord($char);
if (($ord >=65 && $ord <=90 ) || ($ord >= 97 && $ord <= 122))
$ord = $ord+13;
if (($ord > 90 && $ord < 110) || $ord > 122)
$ord = $ord - 26;
echo (chr($ord));
}

Just a few years late to the party, but I thought I'd give you another option to do this
function rot13($string) {
// split into array of ASCII values
$string = array_map('ord', str_split($string));
foreach ($string as $index => $char) {
if (ctype_lower($char)) {
// for lowercase subtract 97 to get character pos in alphabet
$dec = ord('a');
} elseif (ctype_upper($char)) {
// for uppercase subtract 65 to get character pos in alphabet
$dec = ord('A');
} else {
// preserve non-alphabetic chars
$string[$index] = $char;
continue;
}
// add 13 (mod 26) to the character
$string[$index] = (($char - $dec + 13) % 26) + $dec;
}
// convert back to characters and glue back together
return implode(array_map('chr', $string));
}

Related

using chr + rand to generate a random character (A-Z)

I'm using the following to generate a random character from A-Z, but it's occasionally generating the # symbol. Any idea how to prevent this? Maybe the character range is incorrect?
$letter = chr(64+rand(0,26));
Use this it's easier.
Upper Case
$letter = chr(rand(65,90));
Lowercase
$letter = chr(rand(97,122));
ascii chart
The code below generates a random alpha-numeric string of $length. You can see the numbers there for what you need.
function izrand($length = 32) {
$random_string="";
while(strlen($random_string)<$length && $length > 0) {
$randnum = mt_rand(0,61);
$random_string .= ($randnum < 10) ?
chr($randnum+48) : ($randnum < 36 ?
chr($randnum+55) : $randnum+61);
}
return $random_string;
}
update: 12/19/2015
Here is an updated version of the function above, it adds the ability to generate a random numeric key OR an alpha numeric key. To generate numeric, simply add
the second paramater as true.
Example Usage
$randomNumber = izrand(32, true); // generates 32 digit number as string
$randomAlphaNumeric = izrand(); // generates 32 digit alpha numeric string
Typecast to Integer
If you want to typecast the number to integer, simply do this after you
generate the number. NOTE: This will drop any leading zeros if they exist.
$randomNumber = (int) $randomNumber;
izrand() v2
function izrand($length = 32, $numeric = false) {
$random_string = "";
while(strlen($random_string)<$length && $length > 0) {
if($numeric === false) {
$randnum = mt_rand(0,61);
$random_string .= ($randnum < 10) ?
chr($randnum+48) : ($randnum < 36 ?
chr($randnum+55) : chr($randnum+61));
} else {
$randnum = mt_rand(0,9);
$random_string .= chr($randnum+48);
}
}
return $random_string;
}
ASCII code 64 is #. You want to start at 65, which is A. Also, PHP's rand generates a number from min to max inclusive: you should set it to 25 so the biggest character you get is 90 (Z).
$letter = chr(65 + rand(0, 25));
$range = range('A', 'Z');
$index = array_rand($range);
echo $range[$index];
You could use, given you could generate from a-Z:
$range = array_merge(range('A', 'Z'),range('a', 'z'));
$index = array_rand($range, 1);
echo $range[$index];
The below code will generate an alphanumric string of 2 letters and 3 digits.
$string = strtoupper(chr(rand(65, 90)) . chr(rand(65, 90)) . rand(100, 999));
echo $string;

Looping though A-Z with Do while loop in PHP

I was trying to loop though a-z with a do while loop.
I know that I also can do that with foreach and forloop.
$char = 'a';
do {
echo $char;
$char++;
} while ($char <= 'z');
Why is that giving the output:
abcdefghijklmnopqrstuvwxyzaaabacadaeafagahaiajakalamanaoapaqarasatauavawaxayazbabbbcbdbebfbgbhbibjbkblbmbnbobpbqbrbsbtbubvbwbxbybzcacbcccdcecfcgchcicjckclcmcncocpcqcrcsctcucvcwcxcyczdadbdcdddedfdgdhdidjdkdldmdndodpdqdrdsdtdudvdwdxdydzeaebecedeeefegeheiejekelemeneoepeqereseteuevewexeyezfafbfcfdfefffgfhfifjfkflfmfnfofpfqfrfsftfufvfwfxfyfzgagbgcgdgegfggghgigjgkglgmgngogpgqgrgsgtgugvgwgxgygzhahbhchdhehfhghhhihjhkhlhmhnhohphqhrhshthuhvhwhxhyhziaibicidieifigihiiijikiliminioipiqirisitiuiviwixiyizjajbjcjdjejfjgjhjijjjkjljmjnjojpjqjrjsjtjujvjwjxjyjzkakbkckdkekfkgkhkikjkkklkmknkokpkqkrksktkukvkwkxkykzlalblcldlelflglhliljlklllmlnlolplqlrlsltlulvlwlxlylzmambmcmdmemfmgmhmimjmkmlmmmnmompmqmrmsmtmumvmwmxmymznanbncndnenfngnhninjnknlnmnnnonpnqnrnsntnunvnwnxnynzoaobocodoeofogohoiojokolomonooopoqorosotouovowoxoyozpapbpcpdpepfpgphpipjpkplpmpnpopppqprpsptpupvpwpxpypzqaqbqcqdqeqfqgqhqiqjqkqlqmqnqoqpqqqrqsqtquqvqwqxqyqzrarbrcrdrerfrgrhrirjrkrlrmrnrorprqrrrsrtrurvrwrxryrzsasbscsdsesfsgshsisjskslsmsnsospsqsrssstsusvswsxsysztatbtctdtetftgthtitjtktltmtntotptqtrtstttutvtwtxtytzuaubucudueufuguhuiujukulumunuoupuqurusutuuuvuwuxuyuzvavbvcvdvevfvgvhvivjvkvlvmvnvovpvqvrvsvtvuvvvwvxvyvzwawbwcwdwewfwgwhwiwjwkwlwmwnwowpwqwrwswtwuwvwwwxwywzxaxbxcxdxexfxgxhxixjxkxlxmxnxoxpxqxrxsxtxuxvxwxxxyxzyaybycydyeyfygyhyiyjykylymynyoypyqyrysytyuyvywyxyyyz
instead of just:
abcdefghijklmnopqrstuvwxyz
From the documentation:
PHP follows Perl's convention when dealing with arithmetic operations on character variables and not C's. For example, in PHP and Perl $a = 'Z'; $a++; turns $a into 'AA', while in C a = 'Z'; a++; turns a into '[' (ASCII value of 'Z' is 90, ASCII value of '[' is 91). Note that character variables can be incremented but not decremented and even so only plain ASCII alphabets and digits (a-z, A-Z and 0-9) are supported.
Try something like this:
for($i = 0, $char = 'a'; $i < 26; $i++, $char++) {
echo $char;
}
Because
<?php
$char = 'z';
var_dump(++$char); //string(2) "aa"
var_dump('aa' <= 'z'); //bool(true)
var_dump('za' <= 'z'); //bool(false)
DEMO
Personally I'd just use a loop from 97 (ascii value for a) to 122 (ascii value for z):
for ($i = 97; $i <= 122; $i++) {
echo chr($i);
}
You cannot compare z with 26 or some kind of number. You need something to compare it with the number. The function ord() does it. So, you can do something like:
$char = 'a';
do {
echo $char;
$char++;
} while (ord($char) <= ord('z'));
An alternative to the above options is the code below.
<?php
$i = '0';
while($i < '26') {
echo chr(97 + $i);
$i++;
?>
<?php
foreach(range('a', 'z') as $char) {
echo "$char ";
}
Simple range view a-z.

Ranking document based on searched terms

How can I implement this
tf-idf(WORD) = occurrences(WORD,DOCUMENT) / number-of-words(DOCUMENT) * log10 ( documents(ALL) / ( 1 + documents(WORD, ALL) ) )
into my PHP codings for ranking search results?
Can refer here for the current codings:
https://stackoverflow.com/a/8574651/1107551
I only understand part of what you are asking for, but I think I can help you with the occurrences(WORD,DOCUMENT) / number-of-words(DOCUMENT) part:
<?php
function rank($word, $document)
{
// Swap newlines for spaces, you'll see why
$document = str_replace("\n",' ',$document);
// Remove special characters except '-' from the string
for($i = 0; $i <= 127; $i++)
{
// Space is allowed, Hyphen is a legitimate part of some words. Also allow range for 0-9, A-Z, and a-z
// Extended ASCII (128 - 255) is purposfully excluded from this since it isn't often used
if($i != 32 && $i != 45 && !($i >= 48 && $i <=57) && !($i >= 65 && $i <= 90) && !($i >= 97 && $i <= 122))
$document = str_replace(chr($i),'',$document);
}
// Split the document on spaces. This gives us individual words
$tmpDoc = explode(' ',trim($document));
// Get the number of elements with $word in them
$occur = count(array_keys($tmpDoc,$word));
// Get the total number of elements
$numWords = count($tmpDoc);
return $occur / $numWords;
}
?>
I'm sure there are more efficient ways to do this, but there are surely also much worse ways.
Note: I did not test the PHP code

PHP function to loop thru a string and replace characters for all possible combinations

I am trying to write a function that will replace characters in a string with their HTML entity encoded equivalent.
I want it to be able to go through all the possible combinations for the given string, for example:
go one-by-one
then combo i.e.. 2 at a time, then three at a time, till you get length at a time
then start in combo split, i.e.. first and last, then first and second to last
then first and last two, fist and second/third last
So for the characters "abcd" it would return:
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
etc.......... so on and so forth till there are no other combinations
Any ideas, or has anyone seen a function somewhere I could modify for this purpose?
loop from 0 to 2^length - 1. On each step, if Nth bit of the loop counter is 1, encode the Nth character
$str = 'abcd';
$len = strlen($str);
for($i = 0; $i < 1 << $len; $i++) {
$p = '';
for($j = 0; $j < $len; $j++)
$p .= ($i & 1 << $j) ? '&#' . ord($str[$j]) . ';' : $str[$j];
echo $p, "\n";
}
There are 2^n combinations, so this will get huge fast. This solution will only work as long as it fits into PHP's integer size. But really who cares? A string that big will print so many results you'll spend your entire life looking at them.
<?php
$input = 'abcd';
$len = strlen($input);
$stop = pow(2, $len);
for ($i = 0; $i < $stop; ++$i)
{
for ($m = 1, $j = 0; $j < $len; ++$j, $m <<= 1)
{
echo ($i & $m) ? '&#'.ord($input[$j]).';' : $input[$j];
}
echo "\n";
}
How about this?
<?php
function permutations($str, $n = 0, $prefix = "") {
if ($n == strlen($str)) {
echo "$prefix\n";
return;
}
permutations($str, $n + 1, $prefix . $str[$n]);
permutations($str, $n + 1, $prefix . '&#' . ord($str[$n]) . ';');
}
permutations("abcd");
?>

Invert case of all letters in a string (uppercase to lowercase and lowercase to uppercase)

How can I swap around / toggle the case of the characters in a string, for example:
$str = "Hello, My Name is Tom";
After I run the code I get a result like this:
$newstr = "hELLO, mY nAME Is tOM";
Is this even possible?
If your string is ASCII only, you can use XOR:
$str = "Hello, My Name is Tom";
print strtolower($str) ^ strtoupper($str) ^ $str;
Outputs:
hELLO, mY nAME IS tOM
OK I know you've already got an answer, but the somewhat obscure strtr() function is crying out to be used for this ;)
$str = "Hello, My Name is Tom";
echo strtr($str,
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ');
The quickest way is with a bitmask. No clunky string functions or regex. PHP is a wrapper for C, so we can manipulate bits quite easily if you know your logical function like OR, NOT, AND, XOR, NAND, etc..:
function swapCase($string) {
for ($i = 0; $i < strlen($string); $i++) {
$char = ord($string{$i});
if (($char > 64 && $char < 91) || ($char > 96 && $char < 123)) {
$string{$i} = chr($char ^ 32);
}
}
return $string;
}
This is what changes it:
$string{$i} = chr($char ^ 32);
We take the Nth character in $string and perform an XOR (^) telling the interpreter to take the integer value of $char and swapping the 6th bit (32) from a 1 to 0 or 0 to 1.
All ASCII characters are 32 away from their counterparts (ASCII was an ingenious design because of this. Since 32 is a power of 2 (2^5), it's easy to shift bits. To get the ASCII value of a letter, use the built in PHP function ord():
ord('a') // 65
ord('A') // 97
// 97 - 65 = 32
So you loop through the string using strlen() as the middle part of the for loop, and it will loop exactly the number of times as your string has letters. If the character at position $i is a letter (a-z (65-90) or A-Z (97-122)), it will swap that character for the uppercase or lowercase counterpart using a bitmask.
Here's how the bitmask works:
0100 0001 // 65 (lowercase a)
0010 0000 // 32 (bitmask of 32)
--------- // XOR means: we put a 1 if the bits are different, a 0 if they are same.
0110 0001 // 97 (uppercase A)
We can reverse it:
0110 0001 // 97 (A)
0010 0000 // Bitmask of 32
---------
0100 0001 // 65 (a)
No need for str_replace or preg_replace, we just swap bits to add or subtract 32 from the ASCII value of the character and we swap cases. The 6th bit (6th from the right) determines if the character is uppercase or lowercase. If it's a 0, it's lowercase and 1 if uppercase. Changing the bit from a 0 to a 1 ads 32, getting the uppercase chr() value, and changing from a 1 to a 0 subtracts 32, turning an uppercase letter lowercase.
swapCase('userId'); // USERiD
swapCase('USERiD'); // userId
swapCase('rot13'); // ROT13
We can also have a function that swaps the case on a particular character:
// $i = position in string
function swapCaseAtChar($string, $i) {
$char = ord($string{$i});
if (($char > 64 && $char < 91) || ($char > 96 && $char < 123)) {
$string{$i} = chr($char ^ 32);
return $string;
} else {
return $string;
}
}
echo swapCaseAtChar('iiiiiiii', 0); // Iiiiiiii
echo swapCaseAtChar('userid', 4); // userId
// Numbers are no issue
echo swapCaseAtChar('12345qqq', 7); // 12345qqQ
Very similar in function to the answer by Mark.
preg_replace_callback(
'/[a-z]/i',
function($matches) {
return $matches[0] ^ ' ';
},
$str
)
Explanation by #xtempore:
'a' ^ ' ' returns A. It works because A is 0x41 and a is 0x61 (and likewise for all A-Z), and because a space is 0x20. By xor-ing you are flipping that one bit. In simple terms, you are adding 32 to upper case letters making them lower case and subtracting 32 from lower case letters making them upper case.
You'll need to iterate through the string testing the case of each character, calling strtolower() or strtoupper() as appropriate, adding the modified character to a new string.
I know this question is old - but here's my 2 flavours of a multi-byte implementation.
Multi function version:
(mb_str_split function found here):
function mb_str_split( $string ) {
# Split at all position not after the start: ^
# and not before the end: $
return preg_split('/(?<!^)(?!$)/u', $string );
}
function mb_is_upper($char) {
return mb_strtolower($char, "UTF-8") != $char;
}
function mb_flip_case($string) {
$characters = mb_str_split($string);
foreach($characters as $key => $character) {
if(mb_is_upper($character))
$character = mb_strtolower($character, 'UTF-8');
else
$character = mb_strtoupper($character, 'UTF-8');
$characters[$key] = $character;
}
return implode('',$characters);
}
Single function version:
function mb_flip_case($string) {
$characters = preg_split('/(?<!^)(?!$)/u', $string );
foreach($characters as $key => $character) {
if(mb_strtolower($character, "UTF-8") != $character)
$character = mb_strtolower($character, 'UTF-8');
else
$character = mb_strtoupper($character, 'UTF-8');
$characters[$key] = $character;
}
return implode('',$characters);
}
Following script supports UTF-8 characters like "ą" etc.
PHP 7.1+
$before = 'aaAAąAŚĆżź';
$after = preg_replace_callback('/./u', function (array $char) {
[$char] = $char;
return $char === ($charLower = mb_strtolower($char))
? mb_strtoupper($char)
: $charLower;
}, $before);
PHP 7.4+
$before = 'aaAAąAŚĆżź';
$after = implode(array_map(function (string $char) {
return $char === ($charLower = mb_strtolower($char))
? mb_strtoupper($char)
: $charLower;
}, mb_str_split($before)));
$before: aaAAąAŚĆżź
$after: AAaaĄaśćŻŹ
I suppose a solution might be to use something like this :
$str = "Hello, My Name is Tom";
$newStr = '';
$length = strlen($str);
for ($i=0 ; $i<$length ; $i++) {
if ($str[$i] >= 'A' && $str[$i] <= 'Z') {
$newStr .= strtolower($str[$i]);
} else if ($str[$i] >= 'a' && $str[$i] <= 'z') {
$newStr .= strtoupper($str[$i]);
} else {
$newStr .= $str[$i];
}
}
echo $newStr;
Which gets you :
hELLO, mY nAME IS tOM
i.e. you :
loop over each character of the original string
if it's between A and Z, you put it to lower case
if it's between a and z, you put it to upper case
else, you keep it as-is
The problem being this will probably not work nicely with special character like accents :-(
And here is a quick proposal that might (or might not) work for some other characters :
$str = "Hello, My Name is Tom";
$newStr = '';
$length = strlen($str);
for ($i=0 ; $i<$length ; $i++) {
if (strtoupper($str[$i]) == $str[$i]) {
// Putting to upper case doesn't change the character
// => it's already in upper case => must be put to lower case
$newStr .= strtolower($str[$i]);
} else {
// Putting to upper changes the character
// => it's in lower case => must be transformed to upper case
$newStr .= strtoupper($str[$i]);
}
}
echo $newStr;
An idea, now, would be to use mb_strtolower and mb_strtoupper : it might help with special characters, and multi-byte encodings...
For a multibyte/unicode-safe solution, I'd probably recommend mutating/toggling the case of each letter based on which capture group contains a letter. This way you don't have to make a multibyte-base check after matching a letter with regex.
Code: (Demo)
$string = 'aaAAąAŚĆżź';
echo preg_replace_callback(
'/(\p{Lu})|(\p{Ll})/u',
function($m) {
return $m[1]
? mb_strtolower($m[1])
: mb_strtoupper($m[2]);
},
$string
);
// AAaaĄaśćŻŹ
See this answer about how to match letters that might be multibyte.

Categories