I've been given a datafile where the original creator used alphabetical rather than numeric values to show order.
For example, if there's ten items, they'd be named:
12342313A
12342313B
12342313C
12342313D
12342313E
...
I need to import these values into a mySQL table that has order as a required int column, and I need to convert the letter to a number.
Is there a function in PHP to get a numeric value for a letter? Or will I need to do a substr to grab the trailing letter, and create an indexed array of letters and just do a lookup against that array?
I'm hesitant to do the simple way above, since I don't know how many objects could potentially exist, and I could need to write an array from A-AAAA or something.
Try converting it from base 36 to base 10 using base_convert(), I.e. base_convert($str, 36, 10). You might need to strtolower it first, and it'll only work if its not case sensitive.
PHP has a simple way to create that array, so you could write a function to figure all that out for you and do something like:
function str_to_num($letters, $max = 'ZZZZZZ') {
$count = 0;
for ($i = 'A'; $i < $max; $i++) {
$count++;
if ($letters == $i)
return $count;
}
}
Then you could do the substr, find the letters at the end, and then pass it into the function:
str_to_num('A'); // returns 1
str_to_num('AB'); // returns 28
str_to_num('AC'); // returns 29
str_to_num('ABC'); // returns 731
Something like that, anyway.
Good luck.
Assuming this is a one-time problem that you've got to correct and won't encounter moving forward, I suggest you use sort to... erm, sort out the problem. Let's say you have all those alpha-numeric order fields in an array, like so:
$vals = array (
'12342313A',
'12342313D',
'12342313E',
'12342313B',
'12342313C'
);
Those are all mixed up, not in order. But, you can call the function sort (docs) on that array and PHP does a decent job of making sense out of it:
print '<pre>Unsorted: ';
print_r($vals);
print '</pre>';
sort($vals);
print '<pre>Sorted: ';
print_r($vals);
print '</pre>';
/*
Unsorted: Array
(
[0] => 12342313A
[1] => 12342313D
[2] => 12342313E
[3] => 12342313B
[4] => 12342313C
)
Sorted: Array
(
[0] => 12342313A
[1] => 12342313B
[2] => 12342313C
[3] => 12342313D
[4] => 12342313E
)
*/
So far, so good. Now, you've got them ordered, and as a bonus you can use the index of the array as your new field in the database. Alter the table and add a field to hold the new value; we'll call this field numeric_order, and in my sample I've called the field that currently holds the alpha-numeric sort data string_order. Loop your sorted array and update the database (for example):
foreach ($vals as $x=>$v) {
$sql = 'UPDATE myTable SET numeric_order = '.($x+1).' WHERE string_order = "'.$v.'"';
}
I add 1 to x in the loop based on the assumption that you don't want anything to have 0 for the order - if that isn't a concern, then you can just use x. This is also predicated on the assumption that no two rows have the same alpha-numeric sort value.
If they do, then all is not lost! Start with your array looking like this:
$vals = array (
3=>'12342313A',
15=>'12342313D',
66=>'12342313E',
101=>'12342313B',
200=>'12342313C'
);
... the numeric keys would represent the unique/primary key of the corresponding row. Instead of sort, which does not preserve keys, use asort (which does preserve keys - docs), and then your loop looks like this:
$ord = 1
foreach ($vals as $x=>$v) {
$sql = 'UPDATE myTable SET numeric_order = '.$ord.' WHERE id = "'.$x.'"';
$ord++;
}
If my base assumption is wrong, and you'll continue to deal with this method of ordering rows, then in my humble view you ought to re-consider your data design.
use ord() with substr and subtract 64. This will set A to 1, B to 2, etc...
From what you have above, it seems like your values (last digit, at least) can be thought as being hex numbers. You can then transform them into decimal numbers through the hexdec function.
http://php.net/manual/en/function.hexdec.php
Related
The following is the code
<?php
$id ="202883-202882-202884-0";
$str = implode('-',array_unique(explode('-', $id)));
echo $str;
?>
The result is
202883-202882-202884-0
for $id ="202883-202882-202882-0";, result is 202883-202882-0
I would like to replace the duplicate value with zero, so that the result should be like 202883-202882-0-0, not just remove it.
and for $id ="202883-0-0-0";, result should be 202883-0-0-0. zero should not be replaced, repeating zeros are allowed.
How can I archive that?
More info:
I want to replace every duplicate numbers. Because this is for a product comparison website. There will be only maximum 4 numbers. each will be either a 6 digit number or single digit zero. all zero means no product was selected. one 6 digit number and 3 zero means, one product selected and 3 blank.
Each 6 digit number will collect data from database, I dont want to allow users to enter same number multiple times (will happen only if the number is add with the URL manually.).
Update: I understand that my question was not clear, may be my English is poor.
Here is more explanation, this function is for a smartphone comparison website.
The URL format is sitename.com/compare.html?id=202883-202882-202889-202888.
All three numbers are different smartphones(their database product ID).
I dont want to let users to type in the same product ID like id=202883-202882-202882-202888. It will not display two 202882 results in the website, but it will cause some small issues. The URL will be same without change, but the internal PHP code should consider it as id=202883-202882-202888-0.
The duplicates should be replaced as zero and added to the end.
There will be only 4 numbers separated by "-".
The following examples might clear the cloud!
if pid=202883-202882-202889-202888 the result should be 202883-202882-202889-202888
if pid=202883-202883-202883-202888 the result should be 202888-0-0-0
if pid=202883-202882-202883-202888 the result should be 202883-202882-202888-0
if pid=202882-202882-202882-202882 the result should be 202882-0-0-0
I want to allow only either 6 digit numbers or single digit zero through the string.
if pid=rgfsdg-fgsdfr4354-202883-0 the result should be 202883-0-0-0
if pid=fasdfasd-asdfads-adsfds-dasfad the result should be 0-0-0-0
if pid=4354-45882-445202882-202882 the result should be 202882-0-0-0
It is too complicated for me create, I know there are bright minds out there who can do it much more efficiently than I can.
You can do a array_unique (preserves key), then fill the gaps with 0. Sort by key and you are done :)
+ on arrays will unify the arrays but prioritizes the one on the left.
Code
$input = "0-1-1-3-1-1-3-5-0";
$array = explode('-', $input);
$result = array_unique($array) + array_fill(0, count($array), 0);
ksort($result);
var_dump(implode('-',$result));
Code (v2 - suggested by mickmackusa) - shorter and easier to understand
Fill an array of the size of the input array. And replace by leftover values from array_unique. No ksort needed. 0s will be replaced at the preserved keys of array_unique.
$input = "0-1-1-3-1-1-3-5-0";
$array = explode('-', $input);
$result = array_replace(array_fill(0, count($array), 0), array_unique($array));
var_export($result);
Working example.
Output
string(17) "0-1-0-3-0-0-0-5-0"
Working example.
references
ksort - sort by key
array_fill - generate an array filled with 0 of a certain length
This is another way to do it.
$id = "202883-202882-202882-0-234567-2-2-45435";
From the String you explode the string into an array based on the delimiter which in this case is '-'/
$id_array = explode('-', $id);
Then we can loop through the array and for every unique entry we find, we can store it in another array. Thus we are building an array as we search through the array.
$id_array_temp = [];
// Loop through the array
foreach ($id_array as $value) {
if ( in_array($value, $id_array_temp)) {
// If the entry exists, replace it with a 0
$id_array_temp[] = 0;
} else {
// If the entry does not exist, save the value so we can inspect it on the next loop.
$id_array_temp[] = $value;
}
}
At the end of this operation we will have an array of unique values with any duplicates replaced with a 0.
To recreate the string, we can use implode...
$str = implode('-', $id_array_temp);
echo $str;
Refactoring this, using a ternary to replace the If,else...
$id_array = explode('-', $id);
$id_array_temp = [];
foreach ($id_array as $value) {
$id_array_temp[] = in_array($value, $id_array_temp) ? 0 : $value;
}
$str = implode('-', $id_array_temp);
echo $str;
Output is
202883-202882-0-0-234567-2-0-45435
This appears to be a classic XY Problem.
The essential actions only need to be:
Separate the substrings in the hyphen delimited string.
Validate that the characters in each substring are in the correct format AND are unique to the set.
Only take meaningful action on qualifying value.
You see, there is no benefit to replacing/sanitizing anything when you only really need to validate the input data. Adding zeros to your input just creates more work later.
In short, you should use a direct approach similar to this flow:
if (!empty($_GET['id'])) {
$ids = array_unique(explode('-', $_GET['id']));
foreach ($ids as $id) {
if (ctype_digit($id) && strlen($id) === 6) {
// or: if (preg_match('~^\d{6}$~', $id)) {
takeYourNecessaryAction($id);
}
}
}
I was playing around with PHP as I've just begun my training in it. I came across the array_rand function that returns random indexes and you can control how many random indexes you want. But what if the number of random indexes is kept equal to the actual length of the array? I tried it and the result was surprising.
<?php
$arr = array(1,2,3,4,5,6);
$temp = array_rand($arr,6);
foreach($temp as $r){
echo $arr[$r]." ";
}
?>
So, I'm randomizing all the indices and printing the same array once again, but in the order that array_rand returns. Please note that I'm not looking for an alternative for this piece of code as I was solely practicing. What I want to know is why the random function returns Array ( [0] => 0 [1] => 1 [2] => 2 [3] => 3 [4] => 4 [5] => 5 ) [if you print the array_rand result]? Why is it not random in this case?
array_rand is to pick some random indexes from a given array. For example it may gives you 1,3,4 indexes or 3,5,6 indexes. But not 5,2,4 (At least the purpose of the function is not that).
If you want to randomize indexes for an array you have to use shuffle
http://php.net/manual/en/function.shuffle.php
Just add one more step after arry_rand, shuffle(); so your code will be:
<?php
$arr = array(1,2,3,4,5,6);
$temp = array_rand($arr,6);
shuffle($temp);
foreach($temp as $r){
echo $arr[$r]." ";
}
?>
because as pre. answer explained the mean.
I'm writing a game in PHP. There are 52 elements in an array 0 to 51. The goal is to create a random list each time that is unique from the previous list. So that there is no sequence pattern from the first list in the next one. For example, if the first list has a pattern contained as 7654, we don't want a pattern of 7654 to appear immediately in the next list.
The rule is that the first element of the list and the last element can't be the start and end elements from the previous list. For example if the first list starts and ends with:
0
...
51
Don't want the next list to start with 51 and end with 0.
I'm been thinking about how to approach this. I'm aware of the shuffle function in PHP, but each time it generates a random list it has no knowledge of the previous list. Again, the lists need to be unique directly from it's previous list, not from any other list it generates for the player.
The overall goal is that each list has no relationship or common pattern to it's previous list.
I don't know if there is a name for these kind of random lists.
How could this be done in PHP? Thanks!
Might this be done by using a random method which uses a different "seed" number each time it randomizes the list?
The pattern sequence is 4. So if 7654 appeared in the previous list, then the new list can't have 7654 in it.
I think I've got a solution that should work to meet these constraints.
function nextList($last) {
$index = rand(1,50); // don't select first or last elements
$out = array();
do {
list($value) = array_splice($last, $index, 1);
$out[] = $value;
$maxLoop = count($last);
do {
$newIndex = array_rand($last);
} while ($newIndex == $index && --$maxLoop);
$index = $newIndex;
} while (count($last) > 1);
$out[] = $last[0];
return $out;
}
Used as follows:
$first = range(0, 51);
shuffle($first);
$second = nextList($first);
$third = nextList($second);
// etc.
This works on the basis that, for each element it adds to the new array, it is not proceeded by the same element that proceeds it in the last array.
For example, if the last array had, somewhere, the values 43,12,13..., and we add the element 43 to the new array, then we ensure that the next element is any element EXCEPT 12. With this logic, it should be impossible to get the same repeated sequences.
It also ensures that the first element is neither the first nor last element of the last array, however, making this constraint work also for the last element of the array would be a lot more difficult.
EDIT
I was actually able to use the check function given in Hendriq's answer to check whether this solution works, and it seems to always return a valid new list which does not contain a sequence from the last array =]
Well there are is a small problem with the assumptions you have
Rule 1
The overall goal is that each list has no relationship or common pattern to it's previous list.
Rule 2
The rule is that the first element of the list and the last element can't be the start and end elements from the previous list.
Those two contradict each other. First there may be no relationship, so random. So as you said shuffle (did not look at is I take your word). But the second one needs to know about the previous one. What contradicts with your previous rule.
But what you could do is have the first and last element of the previous draw (the previous 52). Then you shuffle the current draw. If the first and/or last element are the same, draw again until no match is found.
Also I think the word for this is semi-random.
Look at the do{ }while(); for the trick.
After the comment of not having the same 4 elements after each other the next part came.
What you need is way to identify the elements, so lets assume every "item" has an id (for the purpose of the example I only use 5 elements)
Lets use the following
array(
0 => array('id' => 1,),
1 => array('id' => 2,),
2 => array('id' => 3,),
3 => array('id' => 4,),
4 => array('id' => 5,),
)
The next draw is exaclty the same (just for the purpose of the exaple). What we do is we make an array of the ids that are drawn:
array(
0 => 1,
1 => 2,
2 => 3,
3 => 4,
4 => 5,
)
Then use this array in the following function (not checked but should give you a right start)
function check(array $prev, array $draw, $the_same = 4) {
$to_check = count($prev) - $the_same;
for($i = 0; $i < $to_check; $i++) {
if ($array_slice($prev, $i, $the_same) === array_slice($draw, $i, $the_same)) {
return false;
}
}
return true;
}
What is does, you give it the old array and the one you drew, and you give the number with it with how many occurence may be after eachother that maybe the same. Then it just start looping and array slicing to check if they are equal.
Before I write my own function to do it, is there any built-in function, or simple one-liner to convert:
Array
(
[0] => pg_response_type=D
[1] => pg_response_code=U51
[2] => pg_response_description=MERCHANT STATUS
[3] => pg_trace_number=477DD76B-B608-4318-882A-67C051A636A6
)
Into:
Array
(
[pg_response_type] => D
[pg_response_code] =>U51
[pg_response_description] =>MERCHANT STATUS
[pg_trace_number] =>477DD76B-B608-4318-882A-67C051A636A6
)
Just trying to avoid reinventing the wheel. I can always loop through it and use explode.
I can always loop through it and use explode.
that's what you should do.
Edit - didn't read the question right at all, whoops..
A foreach through the array is the quickest way to do this, e.g.
foreach($arr as $key=>$val)
{
$new_vals = explode("=", $val);
$new_arr[$new_vals[0]] = $new_vals[1];
}
This should be around five lines of code. Been a while since I've done PHP but here's some pseudocode
foreach element in the array
explode result on the equals sign, set limit = 2
assign that key/value pair into a new array.
Of course, this breaks on keys that have more than one equals sign, so it's up to you whether you want to allow keys to have equals signs in them.
You could do it like this:
$foo = array(
'pg_response_type=D',
'pg_response_code=U51',
'pg_response_description=MERCHANT STATUS',
'pg_trace_number=477DD76B-B608-4318-882A-67C051A636A6',
);
parse_str(implode('&', $foo), $foo);
var_dump($foo);
Just be sure to encapsulate this code in a function whose name conveys the intent.
PHP.
$a['0']=1;
$a[0]=2;
Which is proper form?
In the first example you use a string to index the array which will be a hashtable "under the hood" which is slower. To access the value a "number" is computed from the string to locate the value you stored. This calculation takes time.
The second example is an array based on numbers which is faster. Arrays that use numbers will index the array according to that number. 0 is index 0; 1 is index 1. That is a very efficient way of accessing an array. No complex calculations are needed. The index is just an offset from the start of the array to access the value.
If you only use numbers, then you should use numbers, not strings. It's not a question of form, it's a question of how PHP will optimize your code. Numbers are faster.
However the speed differences are negligible when dealing with small sizes (arrays storing less than <10,000 elements; Thanks Paolo ;)
In the first you would have an array item:
Key: 0
Index: 0
In the second example, you have only an index set.
Index: 0
$arr = array();
$arr['Hello'] = 'World';
$arr['YoYo'] = 'Whazzap';
$arr[2] = 'No key'; // Index 2
The "funny" thing is, you will get exactly the same result.
PHP (for whatever reason) tests whether a string used as array index contains only digits. If it does the string is converted to int or double.
<?php
$x=array(); $x['0'] = 'foo';
var_dump($x);
$x=array(); $x[0] = 'foo';
var_dump($x);
For both arrays you get [0] => foo, not ["0"] => foo.
Or another test:<?php
$x = array();
$x[0] = 'a';
$x['1'] = 'b';
$x['01'] = 'c';
$x['foo'] = 'd';
foreach( $x as $k=>$v ) {
echo $k, ' ', gettype($k), "\n";
}0 integer
1 integer
01 string
foo string
If you still don't believe it take a look at #define HANDLE_NUMERIC(key, length, func) in zend_hash.h and when and where it is used.
You think that's weird? Pick a number and get in line...
If you plan to increment your keys use the second option. The first one is an associative array which contains the string "0" as the key.
They are both "proper" but have the different side effects as noted by others.
One other thing I'd point out, if you are just pushing items on to an array, you might prefer this syntax:
$a = array();
$a[] = 1;
$a[] = 2;
// now $a[0] is 1 and $a[1] is 2.
they are both good, they will both work.
the difference is that on the first, you set the value 1 to a key called '0'
on the second example, you set the value 2 on the first element in the array.
do not mix them up accidentally ;)