I'm a newbie at Codecademy and I'm currently doing this Hurricane Analysis project. The question I'm stuck on requires me to convert a string (such as "1.76B") and convert it to a real number.
I've tried parsing the string, but can't figure out how to add and convert the B part and add it to the float.
def convert_damages(damages):
for damage in damages:
if damage[-1] == 'B':
split_damages = damage.split('B')
elif damage[-1] == 'M':
split_damages = damage.split('M')
else:
return
So if I have a list containing something like 1.76M, 2.35B and 3.11M it should return something like 1760000, 2350000000, 3110000
Another option is to use a dictionary to hold the conversion factors rather than conditionals
def convert_damages(damages):
" convert damages to a number using following steps "
# Table (dictionary) of conversion lookups
# i.e. conversion_factors['B'] = 10^9, conversion_factors['K'] = 10^3, etc.
conversion_factors = {'B': 1E9,'K': 1E3, 'M': 1E6}
# Get the numeric part of string
num_string = damages[:-1] # number part is everything but last character
# get the factor part of string (last character)
factor = damages[-1] # last character is conversion
new_number = float(num_string) * conversion_factors[factor]
return new_number
Test
for num in ['1.76B', '1.76K', '1.76M']:
print(num, ' => ', convert_damages(num))
Output
1.76B => 1760000000.0
1.76K => 1760.0
1.76M => 1760000.0
Try the following changes:
Make sure that every case returns something - I've added a return at the end.
Note that split returns a list; If you want the first element, take it with [0].
split returns a list of strings, but you can convert your string to a float by calling float(the_string).
Finally, don't forget to multiply by 10 to the relevant power (M is 6 and B is 9 where I come from; change yours appropriately).
The function is as follows:
def convert_damages(damages):
for damage in damages:
if damage[-1] == 'B':
split_damages = float(damage.split('B')[0]) * (10 ** 9)
elif damage[-1] == 'M':
split_damages = float(damage.split('M')[0]) * (10 ** 6)
else:
return
return split_damages
Good luck!
You can use regex to help you with this. The first step is creating a pattern that matches your expected string. You should match one or more digits, followed by 0 or 1 period, and then 1 or more digits. Finally, you want some factor at the end of your string. Since you only mentioned M and B, that's all I've included here. The regex pattern used would be ^(\d+(\.?\d+)?)([MB])$.
Explanation of the pattern:
^ Matches the start of the string
(\d+(\.?\d+)?) Matches one or more digits followed by (optionally) a decimal and one or more digits.
([MB]) Matches M or B literally.
$ Matches end of the string
Because of how our pattern is set up, match.group(0) will be the entire match (this is always true). match.group(1) will be the full number. match.group(2) will be either empty or the decimal point and additional numbers. Note that group 2 is inside group 1 (look at the parenthesis) so we don't need to access group 2 at all. The entire number is in group 1. match.group(3) will be the letter at the end of the string, if it is M or B.
Finally, we need some kind of map between the alphabetic factor (M or B) and the numeric factor (1 million or 1 billion). A dictionary is perfect for this. We multiply the number found in match.group(1) with the numerical factor, and that is our result.
import re
def convert_damage(damage):
damage_pattern = re.compile(r"^(\d+(\.?\d+)?)([MB])$")
match = damage_pattern.match(damage)
if match is None:
print(f"The provided damage '{damage}' is not in the appropriate format.")
return
number = float(match.group(1)) # This is the number
factor = match.group(3) # This is the factor string (M or B)
factor_map = {"M": 1e6, "B": 1e9}
actual_damage = number * factor_map[factor]
return actual_damage
def convert_damages(damages):
output = []
for damage in damages:
output.append(convert_damage(damage))
return output
### Converting single damages
d1 = "1.76M"
print(f"{d1} -> {convert_damage(d1)}")
d2 = "3.52B"
print(f"{d2} -> {convert_damage(d2)}")
### Converting a list of damages
tests = [d1, d2, "6B", "1..5M", "0.2B"]
results = convert_damages(tests)
for damage_str, damage_num in zip(tests, results):
print(f"{damage_str} -> {damage_num}")
Output:
1.76M -> 1760000.0
3.52B -> 3520000000.0
The provided damage '1..5M' is not in the appropriate format.
1.76M -> 1760000.0
3.52B -> 3520000000.0
6B -> 6000000000.0
1..5M -> None
0.2B -> 200000000.0
Related
I'm pretty new to coding and I am trying to write a python script where a user enters an integer and it displays that integer in expanded form raised to the power of 10's.
Example: A user enters 643541 and the script outputs 643541 = (6x10^5 )+(4x10^4)+(3x10^3)+(5x10^2)+(4x10^1)+(1x10^0)
This is my code
A = [7000, 400, 70,1]
cond = True
y = 0
i = 0
sizeArray = len(A)
for i in range(0, sizeArray-1):
while cond == True:
if A[i]%10 == 0:
A[i] = A[i]/10
y += 1
else:
cond = False
print(y)
I tried working with a sample array to test the number of zero's but I don't know how i will be able to output the result as above.
How can I accomplish this?
You can transform your input integer 643541 to an array of digits [6,4,3,5,4,1]. Then maintain a variable for the exponent. It will be decremented for each digit in the array
def function(num):
digits = str(num) # convert number to string
output = []
for i, digit in enumerate(digits):
output.append("(" + digit + "x10^" + str(len(digits)-i-1) + ")")
return " + ".join(output)
Here len(digits)-i-1 plays the role of the variable that maintains exponent value
Every question like this deserves a solution using a list comprehension:
>>> n = 123456
>>> '+'.join([ '({1}x10^{0})'.format(*t) for t in enumerate(str(n)[::-1]) ][::-1])
'(1x10^5)+(2x10^4)+(3x10^3)+(4x10^2)+(5x10^1)+(6x10^0)'
Explanation:
str(n)[::-1] converts the number to a string and then reverses the string, giving the digits of the number, as strings, from least-significant to most-significant.
enumerate returns pairs t = (i, d) where i is the index and d is the digit. Since the sequence is from least-significant to most-significant, the index equals the corresponding exponent of 10.
*t unpacks (i, d) for {0} and {1} in the format string, so the result is like ({d}x10^{i}).
The [::-1] applied to the list comprehension reverses the results back into the right order.
'+'.join joins those results together into a single string, with the + symbol between the parts.
At the moment I frequently have to do something in unittests with hashes and cryptographic signatures. Sometimes they get generated, and I just need to alter one slightly and prove that something no longer works. They are strings of hex-digits 0-9 and a-f of specific length. Here is a sample 64 long:
h = '702b31faad0246cc89a5dc782cdf5235a885d0f529fb30a4e1e70e00938df91a'
I want to change just one character somewhere in there.
You can't be sure that every digit 0 - 9 and a - f will be in there, although would guess it's at least 95% certain that they all are. If you could be sure, I would just run h = h.replace('a', 'b', 1) on it.
If you do it manually, you can just look at it and see the third digit is 2 and run:
new = list(h)
new[2] = '3'
h = ''.join(new)
But if you cannot see it and it needs to happen programmatically, what is a clean and certain way to change just one character in it somewhere?
from random import randrange
h = '702b31faad0246cc89a5dc782cdf5235a885d0f529fb30a4e1e70e00938df91a'
i = randrange(len(h))
new_h = h[:i] + hex(int(h[i], 16) + randrange(1, 16))[-1:] + h[i+1:]
In words:
choose a random index i in h
split the string into the part before the index, the char at the index, and the rest
replace the char at the index with its hex value incremented by a random int between 1 and 15, modulo 16 (i.e., its rightmost hex character)
build the new string from the above pieces
Note that an increment by a value between 1 and 15 (included), followed by a modulo 16, never maps a hex digit onto itself. An increment by 0 or 16 would map it exactly onto itself.
You can just choose a random index
import random
valid_chars = '0...f'
def replace_hash(hash_digest):
idx_to_replace = random.randint(64)
char_to_replace = hash_digest[idx_to_replace]
replacements = valid_chars.replace(char_to_replace, '')
hash_digest[idx_to_replace] = replacements[random.randint(15)
return hash_digest
The most efficient way is to just replace the first char with 1 of 2 replacements. I mean, you can only collide with one char anyway so there's no need to do it randomly. But if you want a random change the function'll work.
I suggest you increment the last character of the hash (cycling to 0 after f). That way you are sure to get a different hash, only differing by one character.
You can easily extend this method to change a character at the position of your choosing, and not just the last one.
h = '702b31faad0246cc89a5dc782cdf5235a885d0f529fb30a4e1e70e00938df91a'
def change_hash(h, index=-1):
digits = list(h)
old_digit= digits[index]
v = int(old_digit, 16)
new_v = (v+1)%16
new_digit = '{:x}'.format(new_v)
digits[index] = new_digit
return ''.join(digits)
print(change_hash(h))
# 702b31faad0246cc89a5dc782cdf5235a885d0f529fb30a4e1e70e00938df91b
# ^
print(change_hash(h, 2))
# 703b31faad0246cc89a5dc782cdf5235a885d0f529fb30a4e1e70e00938df91a
# ^
EDIT:
added option to change a digit at an arbitrary position
formatting the digit using format() as it was proposed in another answer
h = chr(ord(h[0]) + ((-1) if (h[0] in "9z") else 1)) + h[1:]
Given a string, "Hello4.2this.is random 24 text42", I want to return all ints or floats, [4.2, 24, 42]. All the other questions have solutions that return just 24. I want to return a float even if non-digit characters are next to the number. Since I am new to Python, I am trying to avoid regex or other complicated imports. I have no idea how to start. Please help. Here are some research attempts: Python: Extract numbers from a string, this didn't work since it doesn't recognize 4.2 and 42. There are other questions like the one mentioned, none of which sadly recognize 4.2 and 42.
A regex from perldoc perlretut:
import re
re_float = re.compile("""(?x)
^
[+-]?\ * # first, match an optional sign *and space*
( # then match integers or f.p. mantissas:
\d+ # start out with a ...
(
\.\d* # mantissa of the form a.b or a.
)? # ? takes care of integers of the form a
|\.\d+ # mantissa of the form .b
)
([eE][+-]?\d+)? # finally, optionally match an exponent
$""")
m = re_float.match("4.5")
print m.group(0)
# -> 4.5
To get all numbers from a string:
str = "4.5 foo 123 abc .123"
print re.findall(r"[+-]? *(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?", str)
# -> ['4.5', ' 123', ' .123']
Using regular expressions is likely to give you the most concise code for this problem. It is hard to beat the conciseness of
re.findall(r"[+-]? *(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?", str)
from pythad's answer.
However, you say "I am trying to avoid regex", so here's a solution that does not use regular expressions. It is obviously a bit longer than a solution using a regular expression (and probably much slower), but it is not complicated.
The code loops through the input character by character.
As it pulls each character from the string, it appends it to current (a string that holds the number currently being parsed) if appending it still maintains a valid number. When it encounters a character that cannot be appended to current, current is saved to a list of numbers, but only if current itself isn't one of '', '.', '-' or '-.'; these are strings that could potentially begin a number but are not themselves valid numbers.
When current is saved, a trailing 'e', 'e-' or 'e+' is removed. That will happen with a string such as '1.23eA'. While parsing that string, current will eventually become '1.23e', but then 'A' is encountered, which means the string does not contain a valid exponential part, so the 'e' is discarded.
After saving current, it is reset. Usually current is reset to '', but when the character that triggered current to be saved was '.' or '-', current is set to that character, because those characters could be the beginning of a new number.
Here's the function extract_numbers(s). The line before return numbers converts the list of strings to a list of integers and floating point values. If you want just the strings, remove that line.
def extract_numbers(s):
"""
Extract numbers from a string.
Examples
--------
>>> extract_numbers("Hello4.2this.is random 24 text42")
[4.2, 24, 42]
>>> extract_numbers("2.3+45-99")
[2.3, 45, -99]
>>> extract_numbers("Avogadro's number, 6.022e23, is greater than 1 million.")
[6.022e+23, 1]
"""
numbers = []
current = ''
for c in s.lower() + '!':
if (c.isdigit() or
(c == 'e' and ('e' not in current) and (current not in ['', '.', '-', '-.'])) or
(c == '.' and ('e' not in current) and ('.' not in current)) or
(c == '+' and current.endswith('e')) or
(c == '-' and ((current == '') or current.endswith('e')))):
current += c
else:
if current not in ['', '.', '-', '-.']:
if current.endswith('e'):
current = current[:-1]
elif current.endswith('e-') or current.endswith('e+'):
current = current[:-2]
numbers.append(current)
if c == '.' or c == '-':
current = c
else:
current = ''
# Convert from strings to actual python numbers.
numbers = [float(t) if ('.' in t or 'e' in t) else int(t) for t in numbers]
return numbers
If you want to get integers or floats from a string, follow the pythad's
ways...
If you want to get both integers and floats from a single string, do this:
string = "These are floats: 10.5, 2.8, 0.5; and these are integers: 2, 1000, 1975, 308 !! :D"
for line in string:
for actualValue in line.split():
value = []
if "." in actualValue:
value = re.findall('\d+\.\d+', actualValue)
else:
value = re.findall('\d+', actualValue)
numbers += value
A string is palindrome if it reads the same forward and backward. Given a string that contains only lower case English alphabets, you are required to create a new palindrome string from the given string following the rules gives below:
1. You can reduce (but not increase) any character in a string by one; for example you can reduce the character h to g but not from g to h
2. In order to achieve your goal, if you have to then you can reduce a character of a string repeatedly until it becomes the letter a; but once it becomes a, you cannot reduce it any further.
Each reduction operation is counted as one. So you need to count as well how many reductions you make. Write a Python program that reads a string from a user input (using raw_input statement), creates a palindrome string from the given string with the minimum possible number of operations and then prints the palindrome string created and the number of operations needed to create the new palindrome string.
I tried to convert the string to a list first, then modify the list so that should any string be given, if its not a palindrome, it automatically edits it to a palindrome and then prints the result.after modifying the list, convert it back to a string.
c=raw_input("enter a string ")
x=list(c)
y = ""
i = 0
j = len(x)-1
a = 0
while i < j:
if x[i] < x[j]:
a += ord(x[j]) - ord(x[i])
x[j] = x[i]
print x
else:
a += ord(x[i]) - ord(x[j])
x [i] = x[j]
print x
i = i + 1
j = (len(x)-1)-1
print "The number of operations is ",a print "The palindrome created is",( ''.join(x) )
Am i approaching it the right way or is there something I'm not adding up?
Since only reduction is allowed, it is clear that the number of reductions for each pair will be the difference between them. For example, consider the string 'abcd'.
Here the pairs to check are (a,d) and (b,c).
Now difference between 'a' and 'd' is 3, which is obtained by (ord('d')-ord('a')).
I am using absolute value to avoid checking which alphabet has higher ASCII value.
I hope this approach will help.
s=input()
l=len(s)
count=0
m=0
n=l-1
while m<n:
count+=abs(ord(s[m])-ord(s[n]))
m+=1
n-=1
print(count)
This is a common "homework" or competition question. The basic concept here is that you have to find a way to get to minimum values with as few reduction operations as possible. The trick here is to utilize string manipulation to keep that number low. For this particular problem, there are two very simple things to remember: 1) you have to split the string, and 2) you have to apply a bit of symmetry.
First, split the string in half. The following function should do it.
def split_string_to_halves(string):
half, rem = divmod(len(string), 2)
a, b, c = '', '', ''
a, b = string[:half], string[half:]
if rem > 0:
b, c = string[half + 1:], string[rem + 1]
return (a, b, c)
The above should recreate the string if you do a + c + b. Next is you have to convert a and b to lists and map the ord function on each half. Leave the remainder alone, if any.
def convert_to_ord_list(string):
return map(ord, list(string))
Since you just have to do a one-way operation (only reduction, no need for addition), you can assume that for each pair of elements in the two converted lists, the higher value less the lower value is the number of operations needed. Easier shown than said:
def convert_to_palindrome(string):
halfone, halftwo, rem = split_string_to_halves(string)
if halfone == halftwo[::-1]:
return halfone + halftwo + rem, 0
halftwo = halftwo[::-1]
zipped = zip(convert_to_ord_list(halfone), convert_to_ord_list(halftwo))
counter = sum([max(x) - min(x) for x in zipped])
floors = [min(x) for x in zipped]
res = "".join(map(chr, floors))
res += rem + res[::-1]
return res, counter
Finally, some tests:
target = 'ideal'
print convert_to_palindrome(target) # ('iaeai', 6)
target = 'euler'
print convert_to_palindrome(target) # ('eelee', 29)
target = 'ohmygodthisisinsane'
print convert_to_palindrome(target) # ('ehasgidihmhidigsahe', 84)
I'm not sure if this is optimized nor if I covered all bases. But I think this pretty much covers the general concept of the approach needed. Compared to your code, this is clearer and actually works (yours does not). Good luck and let us know how this works for you.
I need to generate a unique hexadecimal string in Python 3 that meets following requirements:
It should contain 6 characters
it should not contain just digits. There must be at least one character.
These generated strings should be random. They should not be in any order.
There should be minimum probability of conflict
I have considered uuid4(). But the problem is that it generates strings with too many characters and any substring of the generated string can contain all digits(i.e. no character) at some point.
Is there any other way to fulfill this conditions? Thanks in advance!
EDIT
Can we use a hash for example SHA-1 to fulfill above requirements?
Here's a simple method that samples evenly from all allowed strings. Sampling uniformly makes conflicts as rare as possible, short of keeping a log of previous keys or using a hash based on a counter (see below).
import random
digits = '0123456789'
letters = 'abcdef'
all_chars = digits + letters
length = 6
while True:
val = ''.join(random.choice(all_chars) for i in range(length))
# The following line might be faster if you only want hex digits.
# It makes a long int with 24 random bits, converts it to hex,
# drops '0x' from the start and 'L' from the end, then pads
# with zeros up to six places if needed
# val = hex(random.getrandbits(4*length))[2:-1].zfill(length)
# test whether it contains at least one letter
if not val.isdigit():
break
# now val is a suitable string
print val
# 5d1d81
Alternatively, here's a somewhat more complex approach that also samples uniformly, but doesn't use any open-ended loops:
import random, bisect
digits = '0123456789'
letters = 'abcdef'
all_chars = digits + letters
length = 6
# find how many valid strings there are with their first letter in position i
pos_weights = [10**i * 6 * 16**(length-1-i) for i in range(length)]
pos_c_weights = [sum(pos_weights[0:i+1]) for i in range(length)]
# choose a random slot among all the allowed strings
r = random.randint(0, pos_c_weights[-1])
# find the position for the first letter in the string
first_letter = bisect.bisect_left(pos_c_weights, r)
# generate a random string matching this pattern
val = ''.join(
[random.choice(digits) for i in range(first_letter)]
+ [random.choice(letters)]
+ [random.choice(all_chars) for i in range(first_letter + 1, length)]
)
# now val is a suitable string
print val
# 4a99f0
And finally, here's an even more complex method that uses the random number r to index directly into the entire range of allowed values, i.e., this converts any number in the range of 0-15,777,216 into a suitable hex string. This could be used to completely avoid conflicts (discussed more below).
import random, bisect
digits = '0123456789'
letters = 'abcdef'
all_chars = digits + letters
length = 6
# find how many valid strings there are with their first letter in position i
pos_weights = [10**i * 6 * 16**(length-1-i) for i in range(length)]
pos_c_weights = [sum(pos_weights[0:i+1]) for i in range(length + 1)]
# choose a random slot among all the allowed strings
r = random.randint(0, pos_c_weights[-1])
# find the position for the first letter in the string
first_letter = bisect.bisect_left(pos_c_weights, r) - 1
# choose the corresponding string from among all that fit this pattern
offset = r - pos_c_weights[first_letter]
val = ''
# convert the offset to a collection of indexes within the allowed strings
# the space of allowed strings has dimensions
# 10 x 10 x ... (for digits) x 6 (for first letter) x 16 x 16 x ... (for later chars)
# so we can index across it by dividing into appropriate-sized slices
for i in range(length):
if i < first_letter:
offset, v = divmod(offset, 10)
val += digits[v]
elif i == first_letter:
offset, v = divmod(offset, 6)
val += letters[v]
else:
offset, v = divmod(offset, 16)
val += all_chars[v]
# now val is a suitable string
print val
# eb3493
Uniform Sampling
I mentioned above that this samples uniformly across all allowed strings. Some other answers here choose 5 characters completely at random and then force a letter into the string at a random position. That approach produces more strings with multiple letters than you would get randomly. e.g., that method always produces a 6-letter string if letters are chosen for the first 5 slots; however, in this case the sixth selection should actually only have a 6/16 chance of being a letter. Those approaches can't be fixed by forcing a letter into the sixth slot only if the first 5 slots are digits. In that case, all 5-digit strings would automatically be converted to 5 digits plus 1 letter, giving too many 5-digit strings. With uniform sampling, there should be a 10/16 chance of completely rejecting the string if the first 5 characters are digits.
Here are some examples that illustrate these sampling issues. Suppose you have a simpler problem: you want a string of two binary digits, with a rule that at least one of them must be a 1. Conflicts will be rarest if you produce 01, 10 or 11 with equal probability. You can do that by choosing random bits for each slot, and then throwing out the 00's (similar to my approach above).
But suppose you instead follow this rule: Make two random binary choices. The first choice will be used as-is in the string. The second choice will determine the location where an additional 1 will be inserted. This is similar to the approach used by the other answers here. Then you will have the following possible outcomes, where the first two columns represent the two binary choices:
0 0 -> 10
0 1 -> 01
1 0 -> 11
1 1 -> 11
This approach has a 0.5 chance of producing 11, or 0.25 for 01 or 10, so it will increase the risk of collisions among 11 results.
You could try to improve this as follows: Make three random binary choices. The first choice will be used as-is in the string. The second choice will be converted to a 1 if the first choice was a 0; otherwise it will be added to the string as-is. The third choice will determine the location where the second choice will be inserted. Then you have the following possible outcomes:
0 0 0 -> 10 (second choice converted to 1)
0 0 1 -> 01 (second choice converted to 1)
0 1 0 -> 10
0 1 1 -> 01
1 0 0 -> 10
1 0 1 -> 01
1 1 0 -> 11
1 1 1 -> 11
This gives 0.375 chance for 01 or 10, and 0.25 chance for 11. So this will slightly increase the risk of conflicts between duplicate 10 or 01 values.
Reducing Conflicts
If you are open to using all letters instead of just 'a' through 'f' (hexadecimal digits), you could alter the definition of letters as noted in the comments. This will give much more diverse strings and much less chance of conflict. If you generated 1,000 strings allowing all upper- and lowercase letters, you'd only have about a 0.0009% chance of generating any duplicates, vs. 3% chance with hex strings only. (This will also virtually eliminate double-passes through the loop.)
If you really want to avoid conflicts between strings, you could store all the values you've generated previously in a set and check against that before breaking from the loop. This would be good if you are going to generate fewer than about 5 million keys. Beyond that, you'd need quite a bit of RAM to hold the old keys, and it might take a few runs through the loop to find an unused key.
If you need to generate more keys than that, you could encrypt a counter, as described at Generating non-repeating random numbers in Python. The counter and its encrypted version would both be ints in the range of 0 to 15,777,216. The counter would just count up from 0, and the encrypted version would look like a random number. Then you would convert the encrypted version to hex using the third code example above. If you do this, you should generate a random encryption key at the start, and change the encryption key each time the counter rolls past your maximum, to avoid producing the same sequence again.
The following approach works as follows, first pick one random letter to ensure rule 2, then select 4 random entries from the list of all available characters. Shuffle the resulting list. Lastly prepend one value taken from the list of all entries except 0 to ensure the string has 6 characters.
import random
all = "0123456789abcdef"
result = [random.choice('abcdef')] + [random.choice(all) for _ in range(4)]
random.shuffle(result)
result.insert(0, random.choice(all[1:]))
print(''.join(result))
Giving you something like:
3b7a4e
This approach avoids having to repeatedly check the result to ensure that it satisfies the rules.
Note: Updated the answer for hexadecimal unique string. Earlier I assumed for alhanumeric string.
You may create your own unique function using uuid and random library
>>> import uuid
>>> import random
# Step 1: Slice uuid with 5 i.e. new_id = str(uuid.uuid4())[:5]
# Step 2: Convert string to list of char i.e. new_id = list(new_id)
>>> uniqueval = list(str(uuid.uuid4())[:5])
# uniqueval = ['f', '4', '4', '4', '5']
# Step 3: Generate random number between 0-4 to insert new char i.e.
# random.randint(0, 4)
# Step 4: Get random char between a-f (for Hexadecimal char) i.e.
# chr(random.randint(ord('a'), ord('f')))
# Step 5: Insert random char to random index
>>> uniqueval.insert(random.randint(0, 4), chr(random.randint(ord('a'), ord('f'))))
# uniqueval = ['f', '4', '4', '4', 'f', '5']
# Step 6: Join the list
>>> uniqueval = ''.join(uniqueval)
# uniqueval = 'f444f5'
This function returns the nth string conforming to your requirements, so you can simply generate unique integers and convert them using this function.
def inttohex(number, digits):
# there must be at least one character:
fullhex = 16**(digits - 1)*6
assert number < fullhex
partialnumber, remainder = divmod(number, digits*6)
charposition, charindex = divmod(remainder, digits)
char = ['a', 'b', 'c', 'd', 'e', 'f'][charposition]
hexconversion = list("{0:0{1}x}".format(partialnumber, digits-1))
hexconversion.insert(charposition, char)
return ''.join(hexconversion)
Now you can get a particular one using for instance
import random
digits = 6
inttohex(random.randint(0, 6*16**(digits-1)), digits)
You can't have maximum randomness along with minimum probability of conflict. I recommend keeping track of which numbers you have handed out or if you are looping through all of them somehow, using a randomly sorted list.