I need to convert Excel coordinates (for example "AD45") into X=30 and Y=45 positions in integers.
I have this snippet of PHP code:
/**
* #param String $coordinates
*
* #return array
*/
public function getCoordinatesPositions($coordinates) {
$letters = preg_replace('/[^a-zA-Z]/', '', $coordinates);
$numbers = preg_replace('/[^0-9]/', '', $coordinates);
$letters = strtoupper($letters);
$columnCoordinate = 0;
$alphabetIterate = 0;
$alphabetRange = range('A', 'Z');
$alphabetCount = count($alphabetRange);
$splittedLetters = str_split($letters);
$lettersCount = count($splittedLetters);
$i = 1;
if ($lettersCount === 1) {
$columnCoordinate = array_search($splittedLetters[0], $alphabetRange) + 1;
} else {
foreach ($splittedLetters as $letter) {
if ($i !== $lettersCount) {
$position = (array_search($letter, $alphabetRange) + 1) * $alphabetCount;
} else {
$position = (array_search($letter, $alphabetRange) + 1);
}
$columnCoordinate += $position;
$i++;
}
}
return array('column' => $columnCoordinate, 'row' => $numbers);
}
My problem is, that this function is not returning correct column value if you pass coordinates with 3 or more letters ("ABC45"). And my colleague said, that this algorithm is also poor performance.
Do you have any ideas for simpler and better performance algorithm? Thank you.
In principle the algorithm is fine. You can simplify it and make it more general this way:
function getCoordinatesPositions($coordinates) {
$letters = preg_replace('/[^a-zA-Z]/', '', $coordinates);
$numbers = preg_replace('/[^0-9]/', '', $coordinates);
$letters = strtoupper($letters);
$alphabetRange = range('A', 'Z');
$alphabetCount = count($alphabetRange);
$splittedLetters = str_split($letters);
$lettersCount = count($splittedLetters);
$columnCoordinate = 0;
$i = 1;
foreach ($splittedLetters as $letter) {
$columnCoordinate += (array_search($letter, $alphabetRange) + 1) * pow($alphabetCount, $lettersCount - $i);
$i++;
}
return array('column' => $columnCoordinate, 'row' => intval($numbers));
}
var_dump(getCoordinatesPositions("ABC456"));
For PHPExcel see PHPExcel how to get column index from cell.
The #Axel Richter's answer is a good solution and works fine, but it may be improved to:
Secure against wrong coordinates.
Reduce code.
And probably increase performance.
Here is the proposed version:
function getCoordinatesPositions($coordinates) {
if (preg_match('/^([a-z]+)(\d+)$/i', $coordinates, $matches)) {
$level = strlen($matches[1]);
$matches[1] = array_reduce(
str_split(strtoupper($matches[1])),
function($result, $letter) use (&$level) {
return $result + (ord($letter) - 64) * pow(26, --$level);
}
);
return array_splice($matches, 1);
}
// (returns NULL when wrong $coordinates)
}
Using the initial preg_match() ensures to avoid working with wrong coordinates, and directly extracts the column part into $matches['1'].
Now the main improvement is to use ord($letter) to compute the letter's individual value: it avoids creating a temporary array of range('A', 'Z'), and simplifies the evaluation.
Then array_reduce() allows more compact processing of the column part, which is modified in situ, so the final return is also simplified as a simple part of the intermediary $matches.
Related
ok so i have array for example $arr= "/43sdsd555ksldk66sd"544fdfd";
I take numbers using preg_match_all '/\d+/', and array_map('intval', $zni[0]);
Now the problem is i need to reverse those whole int to see if they are symmetric,like 555 and 66 , and if they are GET A TOTAL OF THEM.(total of only symmetric numbers)
i tried to use function " strrev "and got symmetric numbers, but i don't know how to put them in a one place IF THEY ARE symmetric and calculate them.
<?php
$numbers = "";
if (isset($_GET['submit']))
{
$numbers = ($_GET['niz']);
preg_match_all('/\d+/', $numbers, $zni);
$numtwo= array_map('intval', $zni[0]);
}
foreach ($numtwo as $num)
{
$reverse = strrev($num);
var_dump($reverse);
if ($num == $reverse)
{
$reverse = "true";
} else {
$reverse = "false";
}
var_dump($reverse);
}
Since you already got most of the way there and all you were missing basically was to use + or +=, here is an easy example on how to do it:
$input = "/43sdsd555ksldk66sd544fdfd";
$total = 0;
preg_match_all('/\d+/', $input, $m);
foreach ($m[0] as $d)
if ($d == strrev($d))
$total += $d;
var_dump($total); // => int(621)
Using intval() is not necessary as PHP will implicitly cast between types as needed.
Alternatively you can replace the loop with PHP's array_* functions:
$input = "/43sdsd555ksldk66sd544fdfd";
preg_match_all('/\d+/', $input, $m);
$total = array_sum(array_filter($m[0], function ($v) { return $v == strrev($v); }));
var_dump($total); // => int(621)
Here we use an anonymous function with array_filter() to generate a new array that only contains palindrome numbers from the original matches which is then given to array_sum().
So all that's needed to transform your original code into a working example, is introducing a variable and summing up:
<?php
$numbers = "";
if (isset($_GET['submit']))
{
$numbers = ($_GET['niz']);
preg_match_all('/\d+/', $numbers, $zni);
$numtwo= array_map('intval', $zni[0]);
}
$total = 0; // new variable
foreach ($numtwo as $num)
{
$reverse = strrev($num);
var_dump($reverse);
if ($num == $reverse)
{
$reverse = "true";
$total += $num; // sum up
} else {
$reverse = "false";
}
var_dump($reverse);
}
var_dump($total); // => int(621)
I'm trying to calculate all combinations of an set of values in an array for a number of inputs. Similar to this Question:
PHP algorithm to generate all combinations of a specific size from a single set
For example:
function sampling($chars, $size, $combinations = array()) {
if (empty($combinations)) {
$combinations = $chars;
}
if ($size == 1) {
return $combinations;
}
$new_combinations = array();
foreach ($combinations as $combination) {
foreach ($chars as $char) {
$new_combinations[] = $combination . $char;
}
}
return sampling($chars, $size - 1, $new_combinations);
}
$chars = array('a', 'b', 'c');
$output = sampling($chars, 2);
echo implode($output,', ');
Output:
aa, ab, ac, ba, bb, bc, ca, cb, cc
But the trouble is when I ramp this up to a larger list, for example:
$chars = array('a', 'b', 'c', 'd');
$output = sampling($chars, 12);
The number of permutations dramatically increases and PHP runs out of memory. Apparently the solution to this is to use generators and yield the results throughout the looping. The only examples of generators though are for slightly different problem sets:
See: https://stackoverflow.com/a/27160465/345086
Any ideas on how to use generators to solve this problem?
Give this a shot:
<?php
$chars = array('a','b','c');
$count = 13;
$generator = genCombinations($chars,$count);
foreach ($generator as $value) {
// Do something with the value here
echo $value;
}
function genCombinations($values,$count=0) {
// Figure out how many combinations are possible:
$permCount=pow(count($values),$count);
// Iterate and yield:
for($i = 0; $i < $permCount; $i++)
yield getCombination($values, $count, $i);
}
// State-based way of generating combinations:
function getCombination($values, $count, $index) {
$result=array();
for($i = 0; $i < $count; $i++) {
// Figure out where in the array to start from, given the external state and the internal loop state
$pos = $index % count($values);
// Append and continue
$result[] = $values[$pos];
$index = ($index-$pos)/count($values);;
}
return $result;
}
It is a state-based fixed-length combination generator that should hopefully fit the bill. It will only accept arrays and will return combinations of the array items, regardless of what is actually stored in the array.
I would like to make a function that is able to generate a list of letters and optional numbers using a-z,0-9.
$output = array();
foreach(range('a','z') as $i) {
foreach(range('a','z') as $j) {
foreach(range('a','z') as $k) {
$output[] =$i.$j.$k;
}
}
}
Thanks
example:
myfunction($include, $length)
usage something like this:
myfunction('a..z,0..9', 3);
output:
000
001
...
aaa
aab
...
zzz
The output would have every possible combination of the letters, and numbers.
Setting the stage
First, a function that expands strings like "0..9" to "0123456789" using range:
function expand_pattern($pattern) {
$bias = 0;
$flags = PREG_SET_ORDER | PREG_OFFSET_CAPTURE;
preg_match_all('/(.)\.\.(.)/', $pattern, $matches, $flags);
foreach ($matches as $match) {
$range = implode('', range($match[1][0], $match[2][0]));
$pattern = substr_replace(
$pattern,
$range,
$bias + $match[1][1],
$match[2][1] - $match[1][1] + 1);
$bias += strlen($range) - 4; // 4 == length of "X..Y"
}
return $pattern;
}
It handles any number of expandable patterns and takes care to preserve their position inside your source string, so for example
expand_pattern('abc0..4def5..9')
will return "abc01234def56789".
Calculating the result all at once
Now that we can do this expansion easily, here's a function that calculates cartesian products given a string of allowed characters and a length:
function cartesian($pattern, $length) {
$choices = strlen($pattern);
$indexes = array_fill(0, $length, 0);
$results = array();
$resets = 0;
while ($resets != $length) {
$result = '';
for ($i = 0; $i < $length; ++$i) {
$result .= $pattern[$indexes[$i]];
}
$results[] = $result;
$resets = 0;
for ($i = $length - 1; $i >= 0 && ++$indexes[$i] == $choices; --$i) {
$indexes[$i] = 0;
++$resets;
}
}
return $results;
}
So for example, to get the output described in the question you would do
$options = cartesian(expand_pattern('a..z0..9'), 3);
See it in action (I limited the expansion length to 2 so that the output doesn't explode).
Generating the result on the fly
Since the result set can be extremely large (it grows exponentially with $length), producing it all at once can turn out to be prohibitive. In that case it is possible to rewrite the code so that it returns each value in turn (iterator-style), which has become super easy with PHP 5.5 because of generators:
function cartesian($pattern, $length) {
$choices = strlen($pattern);
$indexes = array_fill(0, $length, 0);
$resets = 0;
while ($resets != $length) {
$result = '';
for ($i = 0; $i < $length; ++$i) {
$result .= $pattern[$indexes[$i]];
}
yield $result;
$resets = 0;
for ($i = $length - 1; $i >= 0 && ++$indexes[$i] == $choices; --$i) {
$indexes[$i] = 0;
++$resets;
}
}
}
See it in action.
See this answer for a code that produces all possible combinations:
https://stackoverflow.com/a/8567199/1800369
You just need to add the $length parameter to limit the combinations size.
You can use a recursive function
assuming you mean it can be any number of levels deep, you can use a recursive function to generate an array of the permutations e.g.:
/**
* take the range of characters, and generate an array of all permutations
*
* #param array $range range of characters to itterate over
* #param array $array input array - operated on by reference
* #param int $depth how many chars to put in the resultant array should be
* #param int $currentDepth internal variable to track how nested the current call is
* #param string $prefix internal variable to know what to prefix the current string with
* #return array permutations
*/
function foo($range, &$array, $depth = 1, $currentDepth = 0, $prefix = "") {
$start = !$currentDepth;
$currentDepth++;
if ($currentDepth > $depth) {
return;
}
foreach($range as $char) {
if ($currentDepth === $depth) {
$array[] = $prefix . $char;
continue;
}
foo($range, $array, $depth, $currentDepth, $prefix . $char);
}
if ($start) {
return $array;
}
With the above function, initialize the return variable and call it:
$return = array();
echo implode(foo(range('a', 'z'), $return, 3), "\n");
And you're output will be all three char combinations from aaa, to zzz:
aaa
aab
...
zzy
zzz
The numeric parameter determins how recursive the function is:
$return = array();
echo implode(foo(range('a', 'z'), $return, 1), "\n");
a
b
c
...
Here's a live example.
$number= range(0, 9);
$letters = range('a', 'z');
$array= array_merge($number, $letters);
//print_r($array);
for($a=0;$a<count($array);$a++){
for($b=0;$b<count($array);$b++){
for($c=0;$c<count($array);$c++){
echo $array[$a].$array[$b].$array[$c]."<br>";
}
}
}
tested and working :)
Given:
$this->objPHPExcelReader = PHPExcel_IOFactory::createReaderForFile($this->config['file']);
$this->objPHPExcelReader->setLoadSheetsOnly(array($this->config['worksheet']));
$this->objPHPExcelReader->setReadDataOnly(true);
$this->objPHPExcel = $this->objPHPExcelReader->load($this->config['file']);
I can iterate through the rows like this but it is very slow, i.e. in a 3MB Excel file with a worksheet that has "EL" columns, it takes about 1 second per row:
foreach ($this->objPHPExcel->setActiveSheetIndex(0)->getRowIterator() as $row)
{
$dataset = array();
$cellIterator = $row->getCellIterator();
$cellIterator->setIterateOnlyExistingCells(false);
foreach ($cellIterator as $cell)
{
if (!is_null($cell))
{
$dataset[] = $cell->getCalculatedValue();
}
}
$this->datasets[] = $dataset;
}
When I iterate like this, it it significantly faster (approx. 2000 rows in 30 seconds), but I will have to convert the letters e.g. "EL" to a number:
$highestColumm = $this->objPHPExcel->setActiveSheetIndex(0)->getHighestColumn(); // e.g. "EL"
$highestRow = $this->objPHPExcel->setActiveSheetIndex(0)->getHighestRow();
$number_of_columns = 150; // TODO: figure out how to get the number of cols as int
for ($row = 1; $row < $highestRow + 1; $row++) {
$dataset = array();
for ($column = 0; $column < $number_of_columns; $column++) {
$dataset[] = $this->objPHPExcel->setActiveSheetIndex(0)->getCellByColumnAndRow($column, $row)->getValue();
}
$this->datasets[] = $dataset;
}
Is there a way to get the highest column as an integer (e.g. "28") instead of in Excel-styled letters (e.g. "AB")?
$colNumber = PHPExcel_Cell::columnIndexFromString($colString);
returns 1 from a $colString of 'A', 26 from 'Z', 27 from 'AA', etc.
and the (almost) reverse
$colString = PHPExcel_Cell::stringFromColumnIndex($colNumber);
returns 'A' from a $colNumber of 0, 'Z' from 25, 'AA' from 26, etc.
EDIT
A couple of useful tricks:
There is a toArray() method for the worksheet class:
$this->datasets = $this->objPHPExcel->setActiveSheetIndex(0)->toArray();
which accepts the following parameters:
* #param mixed $nullValue Value returned in the array entry if a cell doesn't exist
* #param boolean $calculateFormulas Should formulas be calculated?
* #param boolean $formatData Should formatting be applied to cell values?
* #param boolean $returnCellRef False - Return a simple array of rows and columns indexed by number counting from zero
* True - Return rows and columns indexed by their actual row and column IDs
although it does use the iterators, so would be slightly slower
OR
Take advantage of PHP's ability to increment character strings Perl Style
$highestColumm = $this->objPHPExcel->setActiveSheetIndex(0)->getHighestColumn(); // e.g. "EL"
$highestRow = $this->objPHPExcel->setActiveSheetIndex(0)->getHighestRow();
$highestColumm++;
for ($row = 1; $row < $highestRow + 1; $row++) {
$dataset = array();
for ($column = 'A'; $column != $highestColumm; $column++) {
$dataset[] = $this->objPHPExcel->setActiveSheetIndex(0)->getCell($column . $row)->getValue();
}
$this->datasets[] = $dataset;
}
and if you're processing a large number of rows, you might actually notice the performance improvement of ++$row over $row++
This is a somewhat simplified version of dqhendricks answer. I have added to copies, one function assuming you enter the full excel cell reference (ie. "AB12") and the other assuming you enter just the column reference (ie. "AB"). They both return a zero based index.
Input Full Cell Reference
function getIndex ($cell) {
// Strip cell reference down to just letters
$let = preg_replace('/[^A-Z]/', '', $cell);
// Iterate through each letter, starting at the back to increment the value
for ($num = 0, $i = 0; $let != ''; $let = substr($let, 0, -1), $i++)
$num += (ord(substr($let, -1)) - 65) * pow(26, $i);
return $num;
}
Input Column Reference Only
function getIndex ($let) {
// Iterate through each letter, starting at the back to increment the value
for ($num = 0, $i = 0; $let != ''; $let = substr($let, 0, -1), $i++)
$num += (ord(substr($let, -1)) - 65) * pow(26, $i);
return $num;
}
The function goes from the back of the string to the front to increase the value of the column. It uses the ord() function to get the numeric value of a character and then has the letter value subtracted to give the local column value. Finally it is multiplied by the current power of 26.
I suggest to convert excel to array, clean it from empty elements and then count the number of columns:
protected function getColumnsCheck($file, $col_number) {
if (strstr($file, ".xls") != false && strstr($file, ".xlsx") != false) {
$fileType = PHPExcel_IOFactory::identify($file);
$objReader = PHPExcel_IOFactory::createReader($fileType);
$objPHPExcel = $objReader->load($file);
$columns_empty = $objPHPExcel->getActiveSheet(0)->toArray()[0];
$columns = array_filter($columns_empty);
return ($col_number==count($columns));
}
return false;
}
/**
* Example 0 = A, 1 = B
*/
function getNameFromNumber(int $num): string {
if ($num < 0) throw new TypeError('$num must be at least 0');
$numeric = $num % 26;
$letter = chr(65 + $numeric);
$num2 = intval($num / 26);
if ($num2 > 0) {
return (__FUNCTION__)($num2 - 1) . $letter;
} else {
return $letter;
}
}
getNameFromNumber(0) // returns "A"
Not sure if your class has a built in method, but you could always use the ord() function on each letter of the column index string. You will of course have to subtract out the base value of 'A', and multiply by 26^x for each position from the far right of the string. Something like:
$input_string = 'BC';
$base_value = 64;
$decimal_value = 26;
$column_index = 0;
for ($i = 0; $i < strlen($input_string); $i++) {
$char_value = ord($input_string[$i]);
$char_value -= $base_value;
$char_value *= pow($decimal_value, (strlen($input_string) - ($i + 1)));
$column_index += $char_value;
}
echo $column_index;
Basically this would make 'BC' equal (2 * 26^1) + (3 * 26^0) = 55.
$input_string being the column index string, $base_value being the ord() value of 'A' minus 1, and $decimal_value being the value of A0. Should work up to any number column. Have tested. Hope this helps.
/**
*
*/
function number_to_alphabet($number) {
$number = intval($number);
if ($number <= 0) {
return '';
}
$alphabet = '';
while($number != 0) {
$p = ($number - 1) % 26;
$number = intval(($number - $p) / 26);
$alphabet = chr(65 + $p) . $alphabet;
}
return $alphabet;
}
/**
* Required PHP 5.6.
* #see: http://php.net/manual/en/language.operators.arithmetic.php
*/
function alphabet_to_number($string) {
$string = strtoupper($string);
$length = strlen($string);
$number = 0;
$level = 1;
while ($length >= $level ) {
$char = $string[$length - $level];
$c = ord($char) - 64;
$number += $c * (26 ** ($level-1));
$level++;
}
return $number;
}
Test:
for ($x=1; $x<=1000; $x++) {
echo 'number_to_alphabet('.$x.') = ',$y = number_to_alphabet($x),'; ';
echo 'alphabet_to_number('.$y.') = '.alphabet_to_number($y).'; ';
echo PHP_EOL;
}
Since this question is 10 years old, and the packages referenced here are not the newest ones any more:
Here is how you do it using phpspreadsheet:
$colNumber = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::columnIndexFromString($colString); // e.g. 5
Source:
https://phpspreadsheet.readthedocs.io/en/latest/topics/accessing-cells/
how can I generate random numbers and letters mixed together.
Here is my php code.
$i=1;
while($i<=10000){
echo $i++;
}
Here is the function I use
function rand_str($n = 32, $str = "abcdefghijklmnopqrstuvwxyz0123456789")
{
$len = strlen($str);
$pin = "";
for($i = 0; $i < $n; $i++)
{
$rand = rand(0, $len - 1);
$letter = substr($str, $rand, 1);
$pin .= $letter;
}
return $pin;
}
PHP offers the function uniqid(). This function guarantees a unique string.
As such, the values from uniqid() are fairly predictable, and should not be used in encryption (PHPs rand(), by the way, is considered fairly unpredictable).
Running uniqid(), prefixed with rand() trough md5() give more unpredictable values:
$quite_random_token = md5(uniqid(rand(1,6)));
The other benefit of this, is that md5() assures hashes (strings) that are 32 characters/numbers long.
It's normally good to have some type of string / text class that allows you to do this in a reusable fashion, rather than just writing one off functions / writing the code inline.
<?php
class Text
{
/**
* Generate a random string
* #param string $type A type of pool, or a string of characters to use as the pool
* #param integer $length Length of string to return
* #return string
*/
public static function random($type = 'alnum', $length = 8)
{
$pools = array(
'alnum' => '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
'alpha' => 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
'hexdec' => '0123456789abcdef',
'numeric' => '0123456789',
'nozero' => '123456789',
'distinct' => '2345679ACDEFHJKLMNPRSTUVWXYZ'
);
// Use type as a pool if it isn't preconfigured
$pool = isset($pools[$type]) ? $pools[$type] : $type;
$pool = str_split($pool, 1);
$max = count($pool) - 1;
$str = '';
for ($i = 0; $i < $length; $i++)
{
$str .= $pool[mt_rand(0, $max)];
}
return $str;
}
}
here is an example usage:
http://codepad.org/xiu7rYQe
You need something this:
$chars = 'ABCDEFGHIJKLMNOPQRSTOUVWXYZ0123456789';
$i = 0;
do{
$i++;
$ret .= $ret.$chars[mt_rand(0,35)];
}while($i<$length+1);
YOu can print a random alpha numeric character like this:
print chr(rand(97, 122));
Check the ascii chars you want to return. 97 = a and 122 = z. (I think that's right)
Edit: That's almost right. You'll have to include 0-9 but that'e enough to get you started.
Here's mine.
<?php
function randomMixed($length) {
$output = '';
$rand = array_merge(range('a','z'), range('A','Z'), range('0','9'));
for($i = 0; $i < $length; $i++) {
$output .= $rand[array_rand($rand)];
}
return $output;
}
?>
As told by greg0ire, you can use uniqueid() function in following way to generate alphanumeric random number:
printf("uniqid(): %s\r\n", uniqid());