How to increment a string with a custom character set - php

Given a custom alphabet like ['f', 'h', 'z', '#', 's']
I'd like to take a string written in this "alphabet" like
ffff#zz and increment it
So for example if the string was sss After incrementing it will look like hfff the same way that if you increment 999 you get 1000
My current attempt is here: https://3v4l.org/FjAsd
Given:
$characters = ['a', 'b', 'c'];
$string = 'cccc';
my code can do:
baaaa
but if give it
$characters = ['a', 'b', 'c'];
$string = 'aaa';
it will return
b
When I expected aab

The way this works is by processing the string from the end, each time you look at a character you check it's position in the array (I use a flipped array as it's more efficient than using array_search() each time). Then if the character is at the end of the array, then set it to the 0th element of the alphabet and increment the next digit to the left. If there is another letter from the alphabet to increment the current value, then just replace it and stop the loop.
The last bit is that if you have processed every character and the loop was still going, then there is a carry - so add the 0th digit to the start.
$characters = ['a', 'b', 'c'];
$string = 'cccc';
$index = array_flip($characters);
$alphabetCount = count($index)-1;
for ( $i = strlen($string)-1; $i >= 0; $i--) {
$current = $index[$string[$i]]+1;
// Carry
if ( $current > $alphabetCount ) {
$string[$i] = $characters[0];
}
else {
// update and exit
$string[$i] = $characters[$current];
break;
}
}
// As reached end of loop - carry
if ( $i == -1 ) {
$string = $characters[0].$string;
}
echo $string;
gives
aaaaa
with
$characters = ['f', 'h', 'z', '#', 's'];
$string = 'ffff#zz';
you get
ffff#z#

I ended up with something like this:
$string = 'ccc';
$alphabet = ['a', 'b', 'c'];
$numbers = array_keys($alphabet);
$numeric = str_replace($alphabet, $numbers, $string);
$base = count($alphabet) + 1;
$decimal = base_convert($numeric, $base, 10);
$string = base_convert(++$decimal, 10, $base);
strlen($decimal) !== strlen($string)
and $string = str_replace('0', '1', $string);
echo str_replace($numbers, $alphabet, $string);
This one has the advantage of supporting multi byte characters

Related

PHP for and loop array

I just want to get a list of words from an array which has more and less than 3 vowels, but separately how can I do that with only for loop?
$name = array('jake', 'rita', 'ali', 'addert', 'siryteee', 'skeueei', 'wsewwauie', 'aaaaweefio');
$vowels = array('a', 'e', 'i', 'o', 'u');
$massiv = [ ];
$vowel = [ ];
for ($i = 0; $i < count($name); $i++) {
$massiv[ ] = $name[$i];
for ($j = 0; $j < count($vowels); $j++) {
$vowel[ ] = $vowels[$j];
}
}
Use Preg Grep:
$name = array('jake', 'rita', 'ali', 'addert', 'siryteee', 'skeueei', 'wsewwauie', 'aaaaweefio');
$vowels = array('a', 'e', 'i', 'o', 'u');
$pattern = '/('.implode('|',$vowels).'){3}/i';
print_r(preg_grep($pattern, $name));
Output
Array
(
[4] => siryteee
[5] => skeueei
[6] => wsewwauie
[7] => aaaaweefio
)
Sandbox
The regex is pretty strait forward
/(a|e|i|o|u){3}/i
Match anything with at least 3 of any combination of the following a|e|i|o|u. The (..) is a capture group, the | is or, the {3} is match 3 times. You could use {3,} 3 or more, but it doesn't really matter in this case because as soon as you have 3 you can quite looking. The \i flag makes it case insensitive.
As long as each array item is a single word this should work fine, if you have multiple words it will be much more difficult to come up with a match.
For Loop only
Here I will do you the favor
$name = array('jake', 'rita', 'ali', 'addert', 'siryteee', 'skeueei', 'wsewwauie', 'aaaaweefio');
$vowels = array('a', 'e', 'i', 'o', 'u');
$matches = [];
for ($i = 0; $i < count($name); $i++) {
$total = 0;
for ($j = 0; $j < count($vowels); $j++) {
$total += substr_count($name[$i], $vowels[$j]);
if($total > 2){
$matches[] = $name[$i];
break; //exit inner loop
}
}
}
print_r($matches);
Sandbox
The main thing here is
int substr_count ( string $haystack, string $needle [, int $offset = 0 [, int $length ]] )
substr_count() returns the number of times the needle substring occurs in the haystack string. Please note that needle is case sensitive.
http://php.net/manual/en/function.substr-count.php
Same output as the first one. However the nice thing about Preg Grep (besides case insensitivity) is it keeps the array keys, you can replicate this in the for each loop by adding the $i index in when adding matches:
$matches[$i] = $name[$i];
This keeps the matches correlated to the original array, something that may be useful in some cases.
If you want case insensitivity, the easy thing is to just lowercase the words. There are some edge cases where this won't work but for most English words it should be fine.
$name=strtolower($names[$i]);
//change $name to $names as it makes more sense
//when adding to the match array use $names[$i]
//that way we add the unmodified version to our matches
Also I should mention from a performance standpoint it's generally better to count outside of the for loop (condition).
$name_len = count($name);
for ($i = 0; $i < $name_len; $i++)
//another way to do it is like this
for($i=0,$n=count($name);$i<$n;$i++)
Summery
So putting all that togather
$names = array('jake', 'rita', 'ali', 'addert', 'siryteee', 'skeueei', 'wsewwauie', 'aaaaweefio');
$vowels = array('a', 'e', 'i', 'o', 'u');
$matches = [];
for ($i=0,$n=count($names);$i<$n;$i++) {
$total=0;
$name=strtolower($names[$i]);
for ($j=0,$v=count($vowels);$j<$v;$j++) {
//count lowercased version
$total += substr_count($name, $vowels[$j]);
if($total > 2){
$matches[$i] = $names[$i]; //use original in match
break; //exit inner loop
}
}
}
print_r($matches);
You get about 12 lines of code that does the equivalent of one preg_grep call.
LAST THING
The code you had was just transferring the words and vowels to another array. Except you would wind up with words*vowels more vowels. Because for each loop of the outer for loop (tied to words) you would do a full loop of all the vowels.
Anyway Enjoy!

How to split abbreviated days of the week?

I'm trying to split the string MTWTHFS (days of the week) and store it as an array.
I've tried str_split() but it will also split T and H for Thursday.
if(strpos($days, 'TH') !== false) {
$withth = str_replace("TH", "" ,$days);
}else{
$day = str_split($days);
print_r($day);
}
I also tried first removing the TH in the string before splitting it.
str_split() is fine when there is no TH in the string.
This is my desired result:
array([0]=>'M', [1]=>'T', [2]=>'W', [3]=>'TH', [4]=>'F', [5]=>'S')
Are there any better ways to do this?
I'm sure there will be a few ways to do this. Here is one way with preg_split()
Code: (PHP Demo)
$days = 'MTWTHFS';
$split = preg_split('/.H?\K/', $days, 0, PREG_SPLIT_NO_EMPTY);
var_export($split);
Output:
array (
0 => 'M',
1 => 'T',
2 => 'W',
3 => 'TH',
4 => 'F',
5 => 'S',
)
Pattern Demo
For anyone who wishes to use a non-regex method for any reason, this will do the same:
$days = 'MTWTHFS';
for ($i = 0, $k = -1, $len = strlen($days); $i < $len; ++$i) {
if ($days[$i] == 'H') {
$split[$k] .= 'H';
} else {
$split[++$k] = $days[$i];
}
}
It seems PHP 5.5 doesn't like \K in the pattern. This will provide the desired result:
$split = preg_split('/(.H?)/', $days, 0, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);

Make an array divisible by n?

I have an array:
$arr = ['a', 'b', 'c', 'd', 'e', 'f', 'g'];
Is there a way to make it contain elements only divisible by 3 (or n)? So in the above example g would be removed as it's left over from a division of 3?
EDIT
I want amount of elements divisible by 3.
You can use a combination of array_slice(), count() and modulus (%) to get the array evenly divisible:
$arr_length = count($arr);
$new_arr = array_slice($arr, 0, $arr_length - ($arr_length % 3));
Sounds like you want the length to be divisible by 3 and you want to remove elements until that happens. array_slice can help you out here.
$to_remove = count($arr) % 3;
if($to_remove > 0){
$arr = array_slice($arr, 0, -$to_remove);
}
#panthro simply try with for loop like below and use array_pop()
<?php
$arr = ['a', 'b', 'c', 'd', 'e', 'f', 'g'];
if(count($arr) % 3 != 0){
$remainder = count($arr) % 3;
for($i=0;$i < $remainder;$i++){
array_pop($arr);
}
}
echo "<pre>";
print_r($arr); //final array divisible by 3

Search associative array and print corresponding value in php

I have a dictionary like
UUU F
UUC F
CUU L
CUC L
UUA L
CUA L
UUG L
CUG L
AUU I
AUC I
AUA I
GUU V
GUC V
GUG V
GUA V
So Given a string I want to replace every 3 chars its respective char
I was thinking on using associative arrays:
$d = array();
$d['UUU']='F';
$d['UUC']='F';
$d['UUA']='L';
$d['CUU']='L';
$d['GUC']='V';
$d['GUG']='V';
$d['GUA']='V';
$d['UUG']='L';
$d['CUG']='L';
$s = "UUAGUAUUG";
$temp="";
for($i=0; $i<strlen($s)+1; $i++){
$temp .= $s[$i];
if($i%3==0){
echo $temp;
echo array_search($temp, $d);
$temp = "";
}
}
It should output LVL but have no success
Use str_split:
$s = 'UUAGUAUUG';
$split = str_split($s,3);
$translated = array();
foreach ($split as $bases) {
/**
* # supresses warnings if the array index doesn't exist
*/
$translated []= #$d[$bases];
}
echo join('',$translated);
I think this might work:
$temp = implode(array_map(function($a) { return $d[$a];}, str_split($s, 3)));
The basic solution is:
<?php
$dict = array(
'UUU' => 'F',
'UUC' => 'F',
'UUA' => 'L',
'CUU' => 'L',
'GUC' => 'V',
'GUG' => 'V',
'GUA' => 'V',
'UUG' => 'L',
'CUG' => 'L'
);
$before = "UUAGUAUUG";
$after = strtr($before, $dict);
Although you may be able to write a faster version that takes into account that you are replacing every three letters.
And I'm not 100% sure this will even work, given what kind of combinations can overlap over the 3-char barrier. Which leaves you with this rotten solution:
$after = str_replace('-', '',
strtr(preg_replace('/[A-Z]{3}/', '\0-', $before), $dict));
Seriously, don't try this at home. ;)
You're testing at the wrong time.
Change $i%3 == 0 to $i%3 == 2.
The reason here is that you have added to your temporary string first. That means you immediately check a string of length 1 ("U") and then clear it. Then you go around for another 3 times and you get "UAG", followed by "UAU". Neither of these are in your array.
What I don't understand is that you output the value of $temp each time this happens, so you should have picked up on this.
Change your for loop to this:
for($i=0; $i<strlen($s)+1; $i++){
$temp .= $s[$i];
if(($i+1)%3==0){
echo $d[$temp];
$temp = "";
}
}
Your i value starts from 0. And array_values does not give the expected answer.

php - string replacement

I am trying to do chord transposition in PHP the array of Chord values are as followed...
$chords1 = array('C','C#','D','D#','E','F','F#','G','G#','A','A#','B','C','Db','D','Eb','E','F','Gb','G','Ab','A','Bb','B','C');
An example would be D6/F#. I want to match the array value and then transpose it by a given number position in the array. Here is what I have so far...
function splitChord($chord){ // The chord comes into the function
preg_match_all("/C#|D#|F#|G#|A#|Db|Eb|Gb|Ab|Bb|C|D|E|F|G|A|B/", $chord, $notes); // match the item
$notes = $notes[0];
$newArray = array();
foreach($notes as $note){ // for each found item as a note
$note = switchNotes($note); // switch the not out
array_push($newArray, $note); // and push it into the new array
}
$chord = str_replace($notes, $newArray, $chord); // then string replace the chord with the new notes available
return($chord);
}
function switchNotes($note){
$chords1 = array('C','C#','D','D#','E','F','F#','G','G#','A','A#','B','C','Db','D','Eb','E','F','Gb','G','Ab','A','Bb','B','C');
$search = array_search($note, $chords1);////////////////Search the array position D=2 & F#=6
$note = $chords1[$search + 4];///////////////////////then make the new position add 4 = F# and A#
return($note);
}
This works, except the problem is that if I use a split chord like (D6/F#) The chord is transposed to A#6/A#. It is replacing the first note (D) with an (F#) then, Both (F#'s) with an (A#).
The question is... How can I keep this redundancy from happening. The desired output would be F#6/A#. Thank you for your help. If the solution is posted, I WILL mark it as answered.
You can use preg_replace_callback function
function transposeNoteCallback($match) {
$chords = array('C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B', 'C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'B', 'C');
$pos = array_search($match[0], $chords) + 4;
if ($pos >= count($chords)) {
$pos = $pos - count($chords);
}
return $chords[$pos];
}
function transposeNote($noteStr) {
return preg_replace_callback("/C#|D#|F#|G#|A#|Db|Eb|Gb|Ab|Bb|C|D|E|F|G|A|B/", 'transposeNoteCallback', $noteStr);
}
Test
echo transposeNote("Eb6 Bb B Ab D6/F#");
returns
G6 C# Eb C F#6/A#
Cheap advice: move into natural numbers domain [[0-11]] and associate them with corresponding notes at display time only, it will save you many time.
The only problem will be homophones sounds [e.g. C-sharp / D-flat], but hope you can deduce it from tonality.

Categories