PHP for and loop array - php

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!

Related

How to increment a string with a custom character set

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

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);

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.

"Unfolding" a String

I have a set of strings, each string has a variable number of segments separated by pipes (|), e.g.:
$string = 'abc|b|ac';
Each segment with more than one char should be expanded into all the possible one char combinations, for 3 segments the following "algorithm" works wonderfully:
$result = array();
$string = explode('|', 'abc|b|ac');
foreach (str_split($string[0]) as $i)
{
foreach (str_split($string[1]) as $j)
{
foreach (str_split($string[2]) as $k)
{
$result[] = implode('|', array($i, $j, $k)); // more...
}
}
}
print_r($result);
Output:
$result = array('a|b|a', 'a|b|c', 'b|b|a', 'b|b|c', 'c|b|a', 'c|b|c');
Obviously, for more than 3 segments the code starts to get extremely messy, since I need to add (and check) more and more inner loops. I tried coming up with a dynamic solution but I can't figure out how to generate the correct combination for all the segments (individually and as a whole). I also looked at some combinatorics source code but I'm unable to combine the different combinations of my segments.
I appreciate if anyone can point me in the right direction.
Recursion to the rescue (you might need to tweak a bit to cover edge cases, but it works):
function explodinator($str) {
$segments = explode('|', $str);
$pieces = array_map('str_split', $segments);
return e_helper($pieces);
}
function e_helper($pieces) {
if (count($pieces) == 1)
return $pieces[0];
$first = array_shift($pieces);
$subs = e_helper($pieces);
foreach($first as $char) {
foreach ($subs as $sub) {
$result[] = $char . '|' . $sub;
}
}
return $result;
}
print_r(explodinator('abc|b|ac'));
Outputs:
Array
(
[0] => a|b|a
[1] => a|b|c
[2] => b|b|a
[3] => b|b|c
[4] => c|b|a
[5] => c|b|c
)
As seen on ideone.
This looks like a job for recursive programming! :P
I first looked at this and thought it was going to be a on-liner (and probably is in perl).
There are other non-recursive ways (enumerate all combinations of indexes into segments then loop through, for example) but I think this is more interesting, and probably 'better'.
$str = explode('|', 'abc|b|ac');
$strlen = count( $str );
$results = array();
function splitAndForeach( $bchar , $oldindex, $tempthread) {
global $strlen, $str, $results;
$temp = $tempthread;
$newindex = $oldindex + 1;
if ( $bchar != '') { array_push($temp, $bchar ); }
if ( $newindex <= $strlen ){
print "starting foreach loop on string '".$str[$newindex-1]."' \n";
foreach(str_split( $str[$newindex - 1] ) as $c) {
print "Going into next depth ($newindex) of recursion on char $c \n";
splitAndForeach( $c , $newindex, $temp);
}
} else {
$found = implode('|', $temp);
print "Array length (max recursion depth) reached, result: $found \n";
array_push( $results, $found );
$temp = $tempthread;
$index = 0;
print "***************** Reset index to 0 *****************\n\n";
}
}
splitAndForeach('', 0, array() );
print "your results: \n";
print_r($results);
You could have two arrays: the alternatives and a current counter.
$alternatives = array(array('a', 'b', 'c'), array('b'), array('a', 'c'));
$counter = array(0, 0, 0);
Then, in a loop, you increment the "last digit" of the counter, and if that is equal to the number of alternatives for that position, you reset that "digit" to zero and increment the "digit" left to it. This works just like counting with decimal numbers.
The string for each step is built by concatenating the $alternatives[$i][$counter[$i]] for each digit.
You are finished when the "first digit" becomes as large as the number of alternatives for that digit.
Example: for the above variables, the counter would get the following values in the steps:
0,0,0
0,0,1
1,0,0 (overflow in the last two digit)
1,0,1
2,0,0 (overflow in the last two digits)
2,0,1
3,0,0 (finished, since the first "digit" has only 3 alternatives)

PHP Arrays - Remove duplicates ( Time complexity )

Okay this is not a question of "how to get all uniques" or "How to remove duplicates from my array in php". This is a question about the time complexity.
I figured that the array_unique is somewhat O(n^2 - n) and here's my implementation:
function array_unique2($array)
{
$to_return = array();
$current_index = 0;
for ( $i = 0 ; $i < count($array); $i++ )
{
$current_is_unique = true;
for ( $a = $i+1; $a < count($array); $a++ )
{
if ( $array[$i] == $array[$a] )
{
$current_is_unique = false;
break;
}
}
if ( $current_is_unique )
{
$to_return[$current_index] = $array[$i];
}
}
return $to_return;
}
However when benchmarking this against the array_unique i got the following result:
Testing (array_unique2)... Operation took 0.52146291732788 s.
Testing (array_unique)... Operation took 0.28323101997375 s.
Which makes the array_unique twice as fast, my question is, why ( Both had the same random data ) ?
And a friend of mine wrote the following:
function array_unique2($a)
{
$n = array();
foreach ($a as $k=>$v)
if (!in_array($v,$n))
$n[$k]=$v;
return $n;
}
which is twice as fast as the built in one in php.
I'd like to know, why?
What is the time-complexity of array_unique and in_array?
Edit
I removed the count($array) from both loops and just used a variable in the top of the function, that gained 2 seconds on 100 000 elements!
While I can't speak for the native array_unique function, I can tell you that your friends algorithm is faster because:
He uses a single foreach loop as opposed to your double for() loop.
Foreach loops tend to perform faster than for loops in PHP.
He used a single if(! ) comparison while you used two if() structures
The only additional function call your friend made was in_array whereas you called count() twice.
You made three variable declarations that your friend didn't have to ($a, $current_is_unique, $current_index)
While none of these factors alone is huge, I can see where the cumulative effect would make your algorithm take longer than your friends.
The time complexity of in_array() is O(n). To see this, we'll take a look at the PHP source code.
The in_array() function is implemented in ext/standard/array.c. All it does is call php_search_array(), which contains the following loop:
while (zend_hash_get_current_data_ex(target_hash, (void **)&entry, &pos) == SUCCESS) {
// checking the value...
zend_hash_move_forward_ex(target_hash, &pos);
}
That's where the linear characteristic comes from.
This is the overall characteristic of the algorithm, becaus zend_hash_move_forward_ex() has constant behaviour: Looking at Zend/zend_hash.c, we see that it's basically just
*current = (*current)->pListNext;
As for the time complexity of array_unique():
first, a copy of the array will be created, which is an operation with linear characteristic
then, a C array of struct bucketindex will be created and pointers into our array's copy will be put into these buckets - linear characteristic again
then, the bucketindex-array will be sorted usign quicksort - n log n on average
and lastly, the sorted array will be walked and and duplicate entries will be removed from our array's copy - this should be linear again, assuming that deletion from our array is a constant time operation
Hope this helps ;)
Try this algorithm. It takes advantage of the fact that the key lookup is faster than in_array():
function array_unique_mine($A) {
$keys = Array();
$values = Array();
foreach ($A as $k => $v) {
if (!array_key_exists($v, $values)) {
$keys[] = $k;
$values[$v] = $v;
}
}
return array_combine($keys, $values);
}
Gabriel's answer has some great points about why your friend's method beats yours. Intrigued by the conversation following Christoph's answer, I decided to run some tests of my own.
Also, I tried this with differing lengths of random strings and although the results were different, the order was the same. I used 6 chars in this example for brevity.
Notice that array_unique5 actually has the same keys as native, 2 and 3, but just outputs in a different order.
Results...
Testing 10000 array items of data over 1000 iterations:
array_unique6: 1.7561039924622 array ( 9998 => 'b', 9992 => 'a', 9994 => 'f', 9997 => 'e', 9993 => 'c', 9999 => 'd', )
array_unique4: 1.8798060417175 array ( 0 => 'b', 1 => 'a', 2 => 'f', 3 => 'e', 4 => 'c', 5 => 'd', )
array_unique5: 7.5023629665375 array ( 10 => 'd', 0 => 'b', 3 => 'e', 2 => 'f', 9 => 'c', 1 => 'a', )
array_unique3: 11.356487989426 array ( 0 => 'b', 1 => 'a', 2 => 'f', 3 => 'e', 9 => 'c', 10 => 'd', )
array_unique: 22.535032987595 array ( 0 => 'b', 1 => 'a', 2 => 'f', 3 => 'e', 9 => 'c', 10 => 'd', )
array_unique2: 62.107122898102 array ( 0 => 'b', 1 => 'a', 2 => 'f', 3 => 'e', 9 => 'c', 10 => 'd', )
array_unique7: 71.557286024094 array ( 0 => 'b', 1 => 'a', 2 => 'f', 3 => 'e', 9 => 'c', 10 => 'd', )
And The Code...
set_time_limit(0);
define('HASH_TIMES', 1000);
header('Content-Type: text/plain');
$aInput = array();
for ($i = 0; $i < 10000; $i++) {
array_push($aInput, chr(rand(97, 102)));
}
function array_unique2($a) {
$n = array();
foreach ($a as $k=>$v)
if (!in_array($v,$n))
$n[$k]=$v;
return $n;
}
function array_unique3($aOriginal) {
$aUnique = array();
foreach ($aOriginal as $sKey => $sValue) {
if (!isset($aUnique[$sValue])) {
$aUnique[$sValue] = $sKey;
}
}
return array_flip($aUnique);
}
function array_unique4($aOriginal) {
return array_keys(array_flip($aOriginal));
}
function array_unique5($aOriginal) {
return array_flip(array_flip(array_reverse($aOriginal, true)));
}
function array_unique6($aOriginal) {
return array_flip(array_flip($aOriginal));
}
function array_unique7($A) {
$keys = Array();
$values = Array();
foreach ($A as $k => $v) {
if (!array_key_exists($v, $values)) {
$keys[] = $k;
$values[$v] = $v;
}
}
return array_combine($keys, $values);
}
function showResults($sMethod, $fTime, $aInput) {
echo $sMethod . ":\t" . $fTime . "\t" . implode("\t", array_map('trim', explode("\n", var_export(call_user_func($sMethod, $aInput), 1)))) . "\n";
}
echo 'Testing ' . (count($aInput)) . ' array items of data over ' . HASH_TIMES . " iterations:\n";
$fTime = microtime(1);
for ($i = 0; $i < HASH_TIMES; $i++) array_unique($aInput);
$aResults['array_unique'] = microtime(1) - $fTime;
$fTime = microtime(1);
for ($i = 0; $i < HASH_TIMES; $i++) array_unique2($aInput);
$aResults['array_unique2'] = microtime(1) - $fTime;
$fTime = microtime(1);
for ($i = 0; $i < HASH_TIMES; $i++) array_unique3($aInput);
$aResults['array_unique3'] = microtime(1) - $fTime;
$fTime = microtime(1);
for ($i = 0; $i < HASH_TIMES; $i++) array_unique4($aInput);
$aResults['array_unique4'] = microtime(1) - $fTime;
$fTime = microtime(1);
for ($i = 0; $i < HASH_TIMES; $i++) array_unique5($aInput);
$aResults['array_unique5'] = microtime(1) - $fTime;
$fTime = microtime(1);
for ($i = 0; $i < HASH_TIMES; $i++) array_unique6($aInput);
$aResults['array_unique6'] = microtime(1) - $fTime;
$fTime = microtime(1);
for ($i = 0; $i < HASH_TIMES; $i++) array_unique7($aInput);
$aResults['array_unique7'] = microtime(1) - $fTime;
asort($aResults, SORT_NUMERIC);
foreach ($aResults as $sMethod => $fTime) {
showResults($sMethod, $fTime, $aInput);
}
Results using Christoph's data set from the comments:
$aInput = array(); for($i = 0; $i < 1000; ++$i) $aInput[$i] = $i; for($i = 500; $i < 700; ++$i) $aInput[10000 + $i] = $i;
Testing 1200 array items of data over 1000 iterations:
array_unique6: 0.83235597610474
array_unique4: 0.84050011634827
array_unique5: 1.1954448223114
array_unique3: 2.2937450408936
array_unique7: 8.4412341117859
array_unique: 15.225166797638
array_unique2: 48.685120105743
PHP's arrays are implemented as hash tables, i.e. their performance characteristics are different from what you'd expect from 'real' arrays. An array's key-value-pairs are additionally stored in a linked list to allow fast iteration.
This explains why your implementation is so slow compared to your friend's: For every numeric index, your algorithm has to do a hash table lookup, whereas a foreach()-loop will just iterate over a linked list.
The following implementation uses a reverse hash table and might be the fastest of the crowd (double-flipping courtesy of joe_mucchiello):
function array_unique2($array) {
return array_flip(array_flip($array));
}
This will only work if the values of $array are valid keys, ie integers or strings.
I also reimplemented your algorithm using foreach()-loops. Now, it will actually be faster than your friend's for small data sets, but still slower than the solution via array_flip():
function array_unique3($array) {
$unique_array = array();
foreach($array as $current_key => $current_value) {
foreach($unique_array as $old_value) {
if($current_value === $old_value)
continue 2;
}
$unique_array[$current_key] = $current_value;
}
return $unique_array;
}
For large data sets, the built-in version array_unique() will outperform all other's except the double-flipping one. Also, the version using in_array() by your friend will be faster than array_unique3().
To summarize: Native code for the win!
Yet another version, which should preserve keys and their ordering:
function array_flop($array) {
$flopped_array = array();
foreach($array as $key => $value) {
if(!isset($flopped_array[$value]))
$flopped_array[$value] = $key;
}
return $flopped_array;
}
function array_unique4($array) {
return array_flip(array_flop($array));
}
This is actually enobrev's array_unique3() - I didn't check his implementations as thoroughly as I should have...
PHP is slower to execute than raw machine code (which is most likely executed by array_unique).
Your second example function (the one your friend wrote) is interesting. I do not see how it would be faster than the native implementation, unless the native one is removing elements instead of building a new array.
I'll admit I don't understand the native code very well, but it seems to copy the entire array, sort it, then loop through it removing duplicates. In that case your second piece of code is actually a more efficient algorithm, since adding to the end of an array is cheaper than deleting from the middle of it.
Keep in mind the PHP developers probably had a good reason for doing it the way they do. Does anyone want to ask them?
The native PHP function array_unique is implemented in C. Thus it is faster than PHP, that has to be translated first. What’s more, PHP uses an different algorithm than you do. As I see it, PHP first uses Quick sort to sort the elements and then deletes the duplicates in one run.
Why his friend’s implementation is faster has his own? Because it uses more built-in functionality that trying to recreate them.

Categories