Filtering function

Hi,

I have a script (attached at the bottom of the page) that can display the output I want but now I have a big problem finding a way to filter the output so it will show the results as below.



<?php
for($i = 0; $i < 10000; $i++){
$formattedi = str_split(str_pad($i,4,'0',STR_PAD_LEFT));
$count = array_count_values($formattedi);
$min = min($count);
$max = max($count);
if($max == 1) $group = 'all different numbers';
if($max == 2 and $min == 2) $group = 'same numbers in pair';
if ($max == 3) $group = '3 same numbers';
if ($max == 2 and $min ==1) $group = '2 same numbers only';
if($max == 4) $group = '4 same numbers';
$out[$group][] = implode('', $formattedi);
}
echo '<pre>', print_r($out), '</pre>';
?>


This is the kind of output I tried to filter :

assuming abcd represents 4 different digit and we need the following output from the script we have :
ABCD , AABC , AABB and AAAB …

We can do the first one ABCD but not the following cases where similar digits are side by side

For instance for
e.g :
AABC (4 digits number with 2 same numbers),
it will show only something like 1123, 3122 and not display output like 2129, 1091. Remove those with 2 counts but not side by side.

e.g :
AAAB (3 digits number)
will show 1112, 2333, 7999 from the script below but not 2322, 1121

e.g :
AABB
will show 1122, 3344 but will not show 2211 (since it is repeating) and will not show 1221 since same digits are not side by side.

Thanks.

Just use some comparison (and a log for 1122 2211)

You don’t have many cases:

if ABCD
if AAAA
if AABB
if AAAB | BAAA
if none of the above then it must be AABC | ABBC | ABCC (as in you don’t even need to test for this)

If I understand this problem correctly, it’s pretty much like a syntax checking issue. For example, A, B, C, and D are letters which is one of 0-9, a word is a composite of four letters, which could be A, B, C, or D. Suppose you have a set of valid words, for instance V = {AABC, AAAB, AABB}, then for each given number, you could check against the set V to see whether the number passes the syntax, which are the patterns. If pass, then the number match the pattern and should be output.
So you may use a generic syntax-checking function to check the given number against excepted word. The function could have four comparisons to check 4 digits.

Steve

For ABCD you will loop through each digit using a variable to store the previous digit, and flag to record whether a digit is the same as the previous one.

If by the end of checking that number the flag has been raised (the flag variable has been set to true) then you know you shouldn’t display that number.

The other rules can be checked in a similar manner.

hmmm… sounds easy to all of you guys but I really have a hard time figuring out the algorithm and the function.

Can some gurus give me an example for this so I can have a better idea how to work on the others, Thanks

Maybe we take AABB as an example. How can I just echo those match this condition from the numbers 0000 to 9999 by using the function I have?

Thanks in advance.

Ahh, double pairs. You would have to choose the more difficult of the lot.

Anyway, knowing the theory behind it, we can now rattle off the following:


for ($i = 0; $i < 10000; $i++) {
    $num = strval($i);
    $numLen = strlen($num);
    $oldDigit = '';
    $dup1 = false;
    $dup2 = false;
    for ($j = 0; $j < $numLen; $j++) {
        $digit = $num[$j];
        if ($digit === $oldDigit) {
            if ($dup1 === TRUE) {
                $dup2 = TRUE;
            } else {
                $dup1 = TRUE;
            }
            $oldDigit = '';
        } else {
            $oldDigit = $digit;
        }
    }
    if ($dup1 === TRUE && $dup2 === TRUE) {
        echo '<p>' . $i . '</p>';
    }
}

I made an example from my approach (http://www.sitepoint.com/forums/showpost.php?p=4447165&postcount=3). It prints out all numbers match the patterns. Hope it giving you some ideas.


<?php

$patterns = array('AABB', 'ABAB', 'ABBA');
foreach ($patterns as $pattern) {
	echo 'Numbers matching the pattern "'.$pattern.'":<br />';
	for($i = 0; $i < 10000; $i++){
		$word = sprintf('%04s', $i);
		if (pattern_check($pattern, $word)) {
			echo $word.'<br />';
		}
	}
	echo '<br />';
}

/**
 * check whether $word matches $pattern
 * @param $pattern: string
 * @param $word: string
 * @return bool
 */
function pattern_check($pattern, $word) {
	$pattern_len = strlen($pattern);
	if ($pattern_len == 0 || $pattern_len != strlen($word)) {
		return FALSE;
	}

	$pattern_letters = str_split($pattern);
	$valid_letters = array();
	foreach ($pattern_letters as $letter) {
		if (!isset($valid_letters[$letter])) {
			$valid_letters[$letter] = '';
		}
	}

	$word_letters = str_split($word);

	for ($i = 0; $i < count($pattern_letters); $i++) {
		if ($valid_letters[$pattern_letters[$i]] == '') {
			$valid_letters[$pattern_letters[$i]] = $word_letters[$i];
		}
		if ($valid_letters[$pattern_letters[$i]] != $word_letters[$i]) {
			return FALSE;
		}
	}

	return TRUE;
}


CVPer, thanks for the script. It works great but how can I filter those repeated numbers.

E.g : AABB
1199 is displayed and 9911 should not be displayed since it is actually the same except the way it is arranged.

e.g : AAAB
1112 is displayed, 2111 will not be displayed.

e.g : AABC
2113 is displayed, 2311 or 3112 should not be displayed as well.

Please advise and thank you.

I have a script below but it doesn’t really work as what I need. Please advise and thanks.


<?php
// some prob showing 0000 =(
$numbers = range(0000, 9999);
echo "<pre>";
print_r(sideBySideNumbersFilter($numbers));
echo "</pre>";

function sideBySideNumbersFilter($numberSet) {
    $filteredSet = array();

    // Remove numbers that will end up being duplicates if reversed.
    foreach ($numberSet AS $number) {
        if (!in_array($number, $filteredSet) && !in_array(strrev($number), $filteredSet)) {
            $filteredSet[] = $number;
        }
    }

    // Remove numbers that dont have repeating digits that are adjacent to each other.
    foreach ($filteredSet AS $key => $number) {
        if (!preg_match('/([0-9])\\1/', $number)) {
            unset($filteredSet[$key]);
        }
    }

    return $filteredSet;
} 


tried this but error occurred :


$patterns = array('AABB', 'AAAB', 'AABC');
foreach ($patterns as $pattern) {
    echo 'Numbers matching the pattern "'.$pattern.'":<br />';
    for($i = 0; $i < 10000; $i++){
        $word = sprintf('&#37;04s', $i);
        if (pattern_check($pattern, $word)) {
			print_r(sideBySideNumbersFilter($word)).'<br />';
           //echo $word.'<br />';
        }
    }
    echo '<br />';
}

Instead of filtering, it seems that you need to be generating.

Use a loop for the multiples of numbers that need to be there, and then a second inner loop to fill in the remaining numbers.

Hi, you mean I can’t use the function provided by CPver to filter the numbers I don’t need ? I think his function works fine just need to remove some repeating numbers. Please advise. thanks

<?php

$patterns = array(‘AABB’, ‘ABAB’, ‘ABBA’);
foreach ($patterns as $pattern) {
echo ‘Numbers matching the pattern "’.$pattern.‘":<br />’;
for($i = 0; $i < 10000; $i++){
$word = sprintf(‘%04s’, $i);
if (pattern_check($pattern, $word)) {
echo $word.‘<br />’;
}
}
echo ‘<br />’;
}

/**

  • check whether $word matches $pattern

  • @param $pattern: string

  • @param $word: string

  • @return bool
    */
    function pattern_check($pattern, $word) {
    $pattern_len = strlen($pattern);
    if ($pattern_len == 0 || $pattern_len != strlen($word)) {
    return FALSE;
    }

    $pattern_letters = str_split($pattern);
    $valid_letters = array();
    foreach ($pattern_letters as $letter) {
    if (!isset($valid_letters[$letter])) {
    $valid_letters[$letter] = ‘’;
    }
    }

    $word_letters = str_split($word);

    for ($i = 0; $i < count($pattern_letters); $i++) {
    if ($valid_letters[$pattern_letters[$i]] == ‘’) {
    $valid_letters[$pattern_letters[$i]] = $word_letters[$i];
    }
    if ($valid_letters[$pattern_letters[$i]] != $word_letters[$i]) {
    return FALSE;
    }
    }

    return TRUE;
    }

// Remove numbers that will end up being duplicates if reversed.

it’s not only if they are reversed, take 1223 and 3221 or 3122 (they are the same no?)
You can tell if they are equal if you sort them first
3221 or 3122 sorted both equal 1223

sorting would incorrectly group 3131 into 1133, losing the “next to each other” distinction.

Yes Hash, 1223 and 3221 or 3122 is the same and should only be displayed once.

Now I have a function to fix this but how can I integrate CVper script into this script to get what I need ? I tried to CVper function into the section where I echo $signature (if $word is in $signature, print) but failed. Please advise.


$used = array();
foreach(range(0, 9999) as $number){
    $signature = str_split(
        str_pad(
            $number,
            4,
            0,
            STR_PAD_LEFT
        )
    );
    sort($signature);
    $signature = implode(null, $signature);
    if(false === in_array($signature, $used)){
        array_push($used, $signature);
        echo $signature, '<br />';
    }
}




<?php

$arrCases = array(
	array('pattern'=>'AABC','numbers'=>array(1123,3122,2129,1091))
	,array('pattern'=>'AAAB','numbers'=>array(1112,2333,7999,2322,1121))
	,array('pattern'=>'AABB','numbers'=>array(1122,3344,2211,1221))
);

$arrData = array();
echo '<ol>';
foreach($arrCases as $arrCase) {
	echo '<li>',$arrCase['pattern'],'<ol>';
	foreach($arrCase['numbers'] as $intNumber) {
		printf(
			'<li>&#37;s %u</li>'
			,isPatternMatch($intNumber,$arrCase['pattern'],$arrData)?'Matches':'Does Not Match'
			,$intNumber
		);
		$arrData[] = $intNumber;
	}
	echo '</ol></li>';
}
echo '</ol>';

function isPatternMatch($intNumber,$strPattern,&$arrData) {

	if(in_array($intNumber,$arrData) || in_array(strrev((string) $intNumber),$arrData)) {
		return false;
	}

	$strTranslation = '';
	$strTransationReverse = '';
	
	$arrChars = range('A','Z');
	$arrProcessedNumbers = array();

	foreach(str_split((string) $intNumber) as $strNumChar) {

		$intCurrentCharsIndex = array_search($strNumChar,$arrProcessedNumbers);
	
		if($intCurrentCharsIndex === false) {
			$intCurrentCharsIndex = count($arrProcessedNumbers);
			$arrProcessedNumbers[] = $strNumChar;
		}

		$strTranslation.= $arrChars[$intCurrentCharsIndex];
		$strTransationReverse = $arrChars[$intCurrentCharsIndex].$strTransationReverse;
		
	}
	
	$strReversePattern = '';	
	$arrCharsReverse = array_reverse(array_slice($arrChars,0,(count($arrProcessedNumbers))),false);
	
	foreach(str_split($strPattern) as $strAlphaChar) {
		
		$intCurrentCharsIndex = array_search($strAlphaChar,$arrChars);
		
		$strReversePattern.= $arrCharsReverse[$intCurrentCharsIndex];
		
	}
	
	return strcmp($strPattern,$strTranslation) == 0 || strcmp($strReversePattern,$strTransationReverse) == 0;

}
?>

Ouput:


   1. AABC
         1. Matches 1123
         2. Matches 3122
         3. Does Not Match 2129
         4. Does Not Match 1091
   2. AAAB
         1. Matches 1112
         2. Matches 2333
         3. Matches 7999
         4. Does Not Match 2322
         5. Does Not Match 1121
   3. AABB
         1. Matches 1122
         2. Matches 3344
         3. Does Not Match 2211
         4. Does Not Match 1221

thanks. I am close there with your help. How can I put all numbers into the array instead of the few numbers you inserted? I tried to put range(0,9999) to replace your numbers but it returned 1 only. I guess I can’t use range in this case?

Please advise as I need it to display all numbers from 0000 to 9999 that matches the condition. Thank you very much

What is the purpose of creating these numbers. Perhaps with knowledge of some of that background, it will spark further ideas from people.

Using a generative technique, you start with the pattern that you desire, split apart the pattern, and then generate the numbers required.

For example, with AAAA, AAAB, AABB, AABC and ABCD you split them at the first character

AAAA
AAA B
AA BB
AA BC
A BCD

and then generate a number from 0 to 9 that hasn’t been used yet.

0000
000 B
00 BB
00 BC
0 BCD

After generating 0000 to 9999 for the first line, that one is done.

Then you split off the next part

000 B
00 BB
00 B C
0 B CD

And generate a next higher digit that hasn’t been used yet

000 1
00 11
0 1 C
0 1 CD

After generating for the first couple of lines, that lot is done.

Then you split off the next part, etc…

We can use recursive programming for this, where a function continuously calls itself until the job is complete.

So, is this the list you’re looking for?

0000 1111 2222 3333 4444 5555 6666 7777 8888 9999 0001 0002 0003 0004 0005 0006 0007 0008 0009 1112 1113 1114 1115 1116 1117 1118 1119 2223 2224 2225 2226 2227 2228 2229 3334 3335 3336 3337 3338 3339 4445 4446 4447 4448 4449 5556 5557 5558 5559 6667 6668 6669 7778 7779 8889 0011 0022 0033 0044 0055 0066 0077 0088 0099 1122 1133 1144 1155 1166 1177 1188 1199 2233 2244 2255 2266 2277 2288 2299 3344 3355 3366 3377 3388 3399 4455 4466 4477 4488 4499 5566 5577 5588 5599 6677 6688 6699 7788 7799 8899 0012 0013 0014 0015 0016 0017 0018 0019 0023 0024 0025 0026 0027 0028 0029 0034 0035 0036 0037 0038 0039 0045 0046 0047 0048 0049 0056 0057 0058 0059 0067 0068 0069 0078 0079 0089 1123 1124 1125 1126 1127 1128 1129 1134 1135 1136 1137 1138 1139 1145 1146 1147 1148 1149 1156 1157 1158 1159 1167 1168 1169 1178 1179 1189 2234 2235 2236 2237 2238 2239 2245 2246 2247 2248 2249 2256 2257 2258 2259 2267 2268 2269 2278 2279 2289 3345 3346 3347 3348 3349 3356 3357 3358 3359 3367 3368 3369 3378 3379 3389 4456 4457 4458 4459 4467 4468 4469 4478 4479 4489 5567 5568 5569 5578 5579 5589 6678 6679 6689 7789 0123 0124 0125 0126 0127 0128 0129 0134 0135 0136 0137 0138 0139 0145 0146 0147 0148 0149 0156 0157 0158 0159 0167 0168 0169 0178 0179 0189 0234 0235 0236 0237 0238 0239 0245 0246 0247 0248 0249 0256 0257 0258 0259 0267 0268 0269 0278 0279 0289 0345 0346 0347 0348 0349 0356 0357 0358 0359 0367 0368 0369 0378 0379 0389 0456 0457 0458 0459 0467 0468 0469 0478 0479 0489 0567 0568 0569 0578 0579 0589 0678 0679 0689 0789 1234 1235 1236 1237 1238 1239 1245 1246 1247 1248 1249 1256 1257 1258 1259 1267 1268 1269 1278 1279 1289 1345 1346 1347 1348 1349 1356 1357 1358 1359 1367 1368 1369 1378 1379 1389 1456 1457 1458 1459 1467 1468 1469 1478 1479 1489 1567 1568 1569 1578 1579 1589 1678 1679 1689 1789 2345 2346 2347 2348 2349 2356 2357 2358 2359 2367 2368 2369 2378 2379 2389 2456 2457 2458 2459 2467 2468 2469 2478 2479 2489 2567 2568 2569 2578 2579 2589 2678 2679 2689 2789 3456 3457 3458 3459 3467 3468 3469 3478 3479 3489 3567 3568 3569 3578 3579 3589 3678 3679 3689 3789 4567 4568 4569 4578 4579 4589 4678 4679 4689 4789 5678 5679 5689 5789 6789

Yes, pmw57… That is the ouput I am looking for but I want to separate the results as what CPver and Oddz did for me.

Please give me an idea how I can accomplish this by using the script that oddz/cpver advised… Thank you very much

How can I insert the numbers ($word) generated from :


$patterns = array('AABB', 'ABAB', 'ABBA');
foreach ($patterns as $pattern) {
echo 'Numbers matching the pattern "'.$pattern.'"br />';
for($i = 0; $i < 10000; $i++){
$word = sprintf('%04s', $i);
if (pattern_check($pattern, $word)) {
echo $word.'<br />';

and place it into the array (replace 1123,3122 and etc) below:


$arrCases = array(
    array('pattern'=>'AABC','numbers'=>array(1123,3122,2129,1091))
    ,array('pattern'=>'AAAB','numbers'=>array(1112,2333,7999,2322,1121))
    ,array('pattern'=>'AABB','numbers'=>array(1122,3344,2211,1221))
);


Please advise.

I don’t want to sound harsh, but are you even attempting to learn this? With all respect, your not adding anything to the solution…