Find a hash encryption input from an output - python

I have this function hash() that encrypts a given string to an integer.
letters = 'weiojpknasdjhsuert'
def hash(string_input):
h = 3
for i in range(0, len(string_input)):
h = h * 43 + letters.index(string_input[i])
return h
So if I do print(hash('post')), my output is: 11231443
How can I find what my input needs to be to get an output like 1509979332193868 if the input can only be a string from letters? There is a formula inside the loop body but I couldn't figure out how to reverse it.

It seem like since 43 is larger than your alphabet, you can just reverse the math. I don't know how to prove there are no hash collisions, so this may have edge cases. For example:
letters = 'weiojpknasdjhsuert'
def hash(string_input):
h = 3
for i in range(0, len(string_input)):
h = h * 43 + letters.index(string_input[i])
return h
n = hash('wepostjapansand')
print(n)
# 9533132150649729383107184
def rev(n):
s = ''
while n:
l = n % 43 # going in reverse this is the index of the next letter
n -= l # now reverse the math...subtract that index
n //= 43 # and divide by 43 to get the next n
if n:
s = letters[l] + s
return s
print(rev(n))
# wepostjapansand
With a more reasonable alphabet, like lowercase ascii and a space, this still seem to be okay:
from string import ascii_lowercase
letters = ascii_lowercase + ' '
def hash(string_input):
h = 3
for i in range(0, len(string_input)):
h = h * 43 + letters.index(string_input[i])
return h
n = hash('this is some really long text to test how well this works')
print(n)
# 4415562436659212948343839746913950248717359454765313646276852138403823934037244869651587521298
def rev(n):
s = ''
# with more compact logic
while n > 3:
s = letters[n % 43] + s
n = (n - (n % 43)) // 43
return s
print(rev(n))
# this is some really long text to test how well this works
The basic idea is that after all the math, the last number is:
prev * 43 + letter_index
This means you can recover the final letter index by taking the prev modulus 43. Then subtract that and divide by 43 (which is just the reverse of the math) and do it again until your number is zero.

Related

Trying to convert a integer into a ACGT DNA sequence

I am trying to reverse my stringtobin function so that when I run bintostring([3]) it will return "AAAT" where A=0,C=1,G=2,T=3, for example CCCC will return 85 because (1 * 64) + (1 * 16) + (1 * 4) + (1 * 1) = 85. My bintostring function now just returns an empty string.
dna = {'A':0, 'C':1, 'G':2, 'T':3}
dna2 = {0:'A', 1:'C', 2:'G', 3:'T'}
def bintostring(num):
seq = []
nums = [64,16,4,1]
#main while
i = 0
while i<len(num):
#nums while (iterate through nums)
k = 0
while k<len(nums):
#dna2 while (iterate through dna2)
x = 0
while x<len(dna2):
check = 0
if num[i]//nums[k] == dna2[x]:
seq.append(dna2[x])
check+=1
elif check>0:
seq.append('A')
x+=1
k+=1
i+=1
return("".join(seq))
print(bintostring([3]))
def stringtobin(seq):
power_of_4 = 1
num = 0
if len(seq)!=4: return None
i = len(seq)-1
while i>=0:
power_of_4*=4
Digitval = dna[seq[i]]
num+=Digitval*power_of_4//4
i-=1
return num
print(stringtobin("AAAT"))
Your encoding is in base 4 which can't hold the length information of your sequence.
Without the length information the encoded value 3 could mean T or TA or TAAA or TAAAA... (there would be no way to know).
If the sequences are always 4 letters long (or the length is stored/provided separately), you can implement the functions like this
def stringToBin(S):
return sum( 4**i*"ACGT".index(p) for i,p in enumerate(S))
def binToString(N,size=4):
result = ""
for _ in range(size):
N,p = divmod(N,4)
result += "ACGT"[p]
return result
print(stringToBin("AAAT")) # 192
print(binToString(192)) # AAAT
print(stringToBin("TA")) # 3
print(stringToBin("TAAA")) # 3
print(binToString(3)) # TAAA
print(binToString(3,2)) # TA (length has to be supplied separately)
If you want your numeric encoding to also carry the length information, you should make it base 5 and use a non-zero value for each letter. This way, TA and TAAA would give different numbers.
def stringToBin(S):
return sum( 5**i*" ACGT".index(p) for i,p in enumerate(S))
def binToString(N):
result = ""
while N:
N,p = divmod(N,5)
result += " ACGT"[p]
return result
print(stringToBin("TA")) # 9
print(stringToBin("TAAA")) # 159
print(binToString(9)) # TA
print(binToString(159)) # TAAA
Obviously this produces larger number so, a 32 bit unsigned integer will only hold 13 letters as opposed to 16 in base 4. If you're doing this to reduce the size of storage, using text compression (e.g. zip) will probably be more efficient than converting to a fixed base binary representation
Your attempt seems inordinately complex. Just map the bottom two bits to a value, then shift them off.
def bintostring(num):
seq = []
for n in num:
subseq = []
for b in range(4):
subseq.append(dna2[n & 3])
n >>= 2
seq.append("".join(reversed(subseq)))
return seq
In case it's not obvious, & is bitwise AND; value & 3 obtains the bottom two bits of value.
The stringtobin function could be similarly simplified. Demo: https://ideone.com/RlzegN

How to create an iterator with Python itertools that returns progressively larger repeats? [duplicate]

I would like to make a alphabetical list for an application similar to an excel worksheet.
A user would input number of cells and I would like to generate list.
For example a user needs 54 cells. Then I would generate
'a','b','c',...,'z','aa','ab','ac',...,'az', 'ba','bb'
I can generate the list from [ref]
from string import ascii_lowercase
L = list(ascii_lowercase)
How do i stitch it together?
A similar question for PHP has been asked here. Does some one have the python equivalent?
Use itertools.product.
from string import ascii_lowercase
import itertools
def iter_all_strings():
for size in itertools.count(1):
for s in itertools.product(ascii_lowercase, repeat=size):
yield "".join(s)
for s in iter_all_strings():
print(s)
if s == 'bb':
break
Result:
a
b
c
d
e
...
y
z
aa
ab
ac
...
ay
az
ba
bb
This has the added benefit of going well beyond two-letter combinations. If you need a million strings, it will happily give you three and four and five letter strings.
Bonus style tip: if you don't like having an explicit break inside the bottom loop, you can use islice to make the loop terminate on its own:
for s in itertools.islice(iter_all_strings(), 54):
print s
You can use a list comprehension.
from string import ascii_lowercase
L = list(ascii_lowercase) + [letter1+letter2 for letter1 in ascii_lowercase for letter2 in ascii_lowercase]
Following #Kevin 's answer :
from string import ascii_lowercase
import itertools
# define the generator itself
def iter_all_strings():
size = 1
while True:
for s in itertools.product(ascii_lowercase, repeat=size):
yield "".join(s)
size +=1
The code below enables one to generate strings, that can be used to generate unique labels for example.
# define the generator handler
gen = iter_all_strings()
def label_gen():
for s in gen:
return s
# call it whenever needed
print label_gen()
print label_gen()
print label_gen()
I've ended up doing my own.
I think it can create any number of letters.
def AA(n, s):
r = n % 26
r = r if r > 0 else 26
n = (n - r) / 26
s = chr(64 + r) + s
if n > 26:
s = AA(n, s)
elif n > 0:
s = chr(64 + n) + s
return s
n = quantity | r = remaining (26 letters A-Z) | s = string
To print the list :
def uprint(nc):
for x in range(1, nc + 1):
print AA(x,'').lower()
Used VBA before convert to python :
Function AA(n, s)
r = n Mod 26
r = IIf(r > 0, r, 26)
n = (n - r) / 26
s = Chr(64 + r) & s
If n > 26 Then
s = AA(n, s)
ElseIf n > 0 Then
s = Chr(64 + n) & s
End If
AA = s
End Function
Using neo's insight on a while loop.
For a given iterable with chars in ascending order. 'abcd...'.
n is the Nth position of the representation starting with 1 as the first position.
def char_label(n, chars):
indexes = []
while n:
residual = n % len(chars)
if residual == 0:
residual = len(chars)
indexes.append(residual)
n = (n - residual)
n = n // len(chars)
indexes.reverse()
label = ''
for i in indexes:
label += chars[i-1]
return label
Later you can print a list of the range n of the 'labels' you need using a for loop:
my_chrs = 'abc'
n = 15
for i in range(1, n+1):
print(char_label(i, my_chrs))
or build a list comprehension etc...
Print the set of xl cell range of lowercase and uppercase charterers
Upper_case:
from string import ascii_uppercase
import itertools
def iter_range_strings(start_colu):
for size in itertools.count(1):
for string in itertools.product(ascii_uppercase, repeat=size):
yield "".join(string)
input_colume_range = ['A', 'B']
input_row_range= [1,2]
for row in iter_range_strings(input_colume_range[0]):
for colum in range(int(input_row_range[0]), int(input_row_range[1]+1)):
print(str(row)+ str(colum))
if row == input_colume_range[1]:
break
Result:
A1
A2
B1
B2
In two lines (plus an import):
from string import ascii_uppercase as ABC
count = 100
ABC+=' '
[(ABC[x[0]] + ABC[x[1]]).strip() for i in range(count) if (x:= divmod(i-26, 26))]
Wrap it in a function/lambda if you need to reuse.
code:
alphabet = ["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z"]
for i in range(len(alphabet)):
for a in range(len(alphabet)):
print(alphabet[i] + alphabet[a])
result:
aa
ab
ac
ad
ae
af
ag
ah
ai
aj
ak
al
am
...

Codewars. Some tests are passed, but i need to get tests which outputs the following mistake: 3263 should equal -1

Can you explain it what problems are here? To my mind, this code is like a heap of crap but with the right solving. I beg your pardon for my english.
the task of this kata:
Some numbers have funny properties. For example:
89 --> 8¹ + 9² = 89 * 1
695 --> 6² + 9³ + 5⁴= 1390 = 695 * 2
46288 --> 4³ + 6⁴+ 2⁵ + 8⁶ + 8⁷ = 2360688 = 46288 * 51
Given a positive integer n written as abcd... (a, b, c, d... being digits) and a positive integer p we want to find a positive integer k, if it exists, such as the sum of the digits of n taken to the successive powers of p is equal to k * n. In other words:
Is there an integer k such as : (a ^ p + b ^ (p+1) + c ^(p+2) + d ^ (p+3) + ...) = n * k
If it is the case we will return k, if not return -1.
Note: n, p will always be given as strictly positive integers.
dig_pow(89, 1) should return 1 since 8¹ + 9² = 89 = 89 * 1
dig_pow(92, 1) should return -1 since there is no k such as 9¹ + 2² equals 92 * k
dig_pow(695, 2) should return 2 since 6² + 9³ + 5⁴= 1390 = 695 * 2
dig_pow(46288, 3) should return 51 since 4³ + 6⁴+ 2⁵ + 8⁶ + 8⁷ = 2360688 = 46288 * 51
def dig_pow(n, p):
if n > 0 and p > 0:
b = []
a = str(n)
result = []
for i in a:
b.append(int(i))
for x in b:
if p != 1:
result.append(x ** p)
p += 1
else:
result.append(x ** (p + 1))
if int((sum(result)) / n) < 1:
return -1
elif int((sum(result)) / n) < 2:
return 1
else:
return int((sum(result)) / n)
test results:
Test Passed
Test Passed
Test Passed
Test Passed
3263 should equal -1
I don't know what exact version of Python you're using. This following code are in Python 3. And if I get you correctly, the code can be as simple as
def dig_pow(n, p):
assert n > 0 and p > 0
digits = (int(i) for i in str(n)) # replaces your a,b part with generator
result = 0 # you don't use result as a list, so an int suffice
for x in digits: # why do you need if in the loop? (am I missing something?)
result += x ** p
p += 1
if result % n: # you just test for divisibility
return -1
else:
return result // n
The major problem is that, in your objective, you have only two option of returning, but you wrote if elif else, which is definitely unnecessary and leads to problems and bugs. The % is modulus operator.
Also, having an if and not returning anything in the other branch is often not a good idea (see the assert part). Of course, if you don't like it, just fall back to if.
I believe this could work as well and I find it a little easier to read, however it can definitely be improved:
def dig_pow(n, p):
value = 0
for digit in str(n):
value += int(digit)**p
p += 1
for k in range(1,value):
if value/k == n:
return k
return -1
this is some example simple example than using:
digits = (int(i) for i in str(n))
I'm opting to use this version since I am still a beginner which can be done with this alt way:
result = 0
for digits in str(n):
#iterate through each digit from n
# single of digits turn to int & power to p
for number in digits:
result += int(number) ** p
p += 1
as for the full solution, it goes like this:
def dig_pow(n, p):
# example n = 123 , change it to string = 1, 2, 3
# each string[] **p, and p iterate by 1
# if n % p not equal to p return - 1
result = 0
for digits in str(n):
#iterate through each digit from n
# single digit turn to int & power to p
for number in digits:
result += int(number) ** p
p += 1
if result % n:
return -1
else:
return result // n

Convert an excel or spreadsheet column letter to its number in Pythonic fashion

Is there a more pythonic way of converting excel-style columns to numbers (starting with 1)?
Working code up to two letters:
def column_to_number(c):
"""Return number corresponding to excel-style column."""
number=-25
for l in c:
if not l in string.ascii_letters:
return False
number+=ord(l.upper())-64+25
return number
Code runs:
>>> column_to_number('2')
False
>>> column_to_number('A')
1
>>> column_to_number('AB')
28
Three letters not working.
>>> column_to_number('ABA')
54
>>> column_to_number('AAB')
54
Reference: question answered in C#
There is a way to make it more pythonic (works with three or more letters and uses less magic numbers):
def col2num(col):
num = 0
for c in col:
if c in string.ascii_letters:
num = num * 26 + (ord(c.upper()) - ord('A')) + 1
return num
And as a one-liner using reduce (does not check input and is less readable so I don't recommend it):
col2num = lambda col: reduce(lambda x, y: x*26 + y, [ord(c.upper()) - ord('A') + 1 for c in col])
One-liners tested in Python 2.7.1 and 3.5.2
excel_col_num = lambda a: 0 if a == '' else 1 + ord(a[-1]) - ord('A') + 26 * excel_col_num(a[:-1])
excel_col_name = lambda n: '' if n <= 0 else excel_col_name((n - 1) // 26) + chr((n - 1) % 26 + ord('A'))
Multi-liners likewise
def excel_column_name(n):
"""Number to Excel-style column name, e.g., 1 = A, 26 = Z, 27 = AA, 703 = AAA."""
name = ''
while n > 0:
n, r = divmod (n - 1, 26)
name = chr(r + ord('A')) + name
return name
def excel_column_number(name):
"""Excel-style column name to number, e.g., A = 1, Z = 26, AA = 27, AAA = 703."""
n = 0
for c in name:
n = n * 26 + 1 + ord(c) - ord('A')
return n
def test (name, number):
for n in [0, 1, 2, 3, 24, 25, 26, 27, 702, 703, 704, 2708874, 1110829947]:
a = name(n)
n2 = number(a)
a2 = name(n2)
print ("%10d %-9s %s" % (n, a, "ok" if a == a2 and n == n2 else "error %d %s" % (n2, a2)))
test (excel_column_name, excel_column_number)
test (excel_col_name, excel_col_num)
All tests print
0 ok
1 A ok
2 B ok
3 C ok
24 X ok
25 Y ok
26 Z ok
27 AA ok
702 ZZ ok
703 AAA ok
704 AAB ok
2708874 EXCEL ok
1110829947 COLUMNS ok
You could just add the following to the console after installing the openpyxl module:
>>> from openpyxl.utils import get_column_letter, column_index_from_string
>>> get_column_letter(1)
'A'
>>> column_index_from_string('A')
1
Just change the letters and number to suit your needs.
Here is one way to do it. It is a variation on code in the XlsxWriter module:
def col_to_num(col_str):
""" Convert base26 column string to number. """
expn = 0
col_num = 0
for char in reversed(col_str):
col_num += (ord(char) - ord('A') + 1) * (26 ** expn)
expn += 1
return col_num
>>> col_to_num('A')
1
>>> col_to_num('AB')
28
>>> col_to_num('ABA')
729
>>> col_to_num('AAB')
704
Using openpyxl
import openpyxl
(column_string, row) = openpyxl.cell.coordinate_from_string(address)
column = openpyxl.cell.column_index_from_string(column_string)
This should do, in VBA, what you're looking for:
Function columnNumber(colLetter As String) As Integer
Dim colNumber As Integer
Dim i As Integer
colLetter = UCase(colLetter)
colNumber = 0
For i = 1 To Len(colLetter)
colNumber = colNumber + (Asc(Mid(colLetter, Len(colLetter) - i + 1, 1)) - 64) * 26 ^ (i - 1)
Next
columnNumber = colNumber
End Function
You can use it as you would an Excel formula--enter column, in letters, as a string (eg, "AA") and should work regardless of column length.
Your code breaks when dealing with three letters because of the way you're doing the counting--you need to use base 26.
After reading this, I decided to find a way to do it directly in Excel cells. It even accounts for columns after Z.
Just paste this formula into a cell of any row of any column and it will give you the corresponding number.
=IF(LEN(SUBSTITUTE(ADDRESS(ROW(),COLUMN(),4),ROW(),""))=2,
CODE(LEFT(SUBSTITUTE(ADDRESS(ROW(),COLUMN(),4),ROW(),""),1))-64*26)+
CODE(RIGHT(SUBSTITUTE(ADDRESS(ROW(),COLUMN(),4),ROW(),""),1)-64),
CODE(SUBSTITUTE(ADDRESS(ROW(),COLUMN(),4),ROW(),""))-64)
The theme here was to grab the letter of the column, get the Code() of it and subtract 64, based on the fact that the ASCII character code for letter A is 64.
I made this one-liner:
colNameToNum = lambda cn: sum([((ord(cn[-1-pos]) - 64) * 26 ** pos) for pos in range(len(cn))])
It works by iterating through the letters in reverse order and multiplying by 1, 26, 26 * 26 etc, then summing the list. This method would be compatible with longer strings of letters, too.
I call it with:
print(colNameToNum("AA")) # 27
or
print(colNameToNum("XFD")) # the highest column allowed, I believe. Result = 16384
You could use this oneliner using comprehension and string that is fairly easy to use:
sum([string.ascii_lowercase.index(c) + 26 ** i for i,c in enumerate(col_letters)])
Here's what I use (wrote before I found this page):
def col_to_index(col):
return sum((ord(c) - 64) * 26**i for i, c in enumerate(reversed(col))) - 1
And some runs:
>>> col_to_index('A')
1
>>> col_to_index('AB')
28
>>> col_to_index('ABCD')
19010
Use:
LETTERS = list(string.ascii_uppercase)
def column_number(column_id):
return sum([(LETTERS.index(j)+1)*(26**i) for i,j in enumerate(column_id[::-1])])
There are several parts to this one-liner, so here's the explanation:
column_id[::-1]: reverses the string, e.g. converts 'AZ' to 'ZA', there's a good reason to do so, which we will see in a bit.
enumerate(): produces a iterable, e.g. (0, 'Z'), (1, 'A')
With some observation:
A -> 1 = (26**0)*1 # ** is the exponential operator
B -> 2 = (26**0)*2
Z -> 26 = (26**0)*26
AA -> 27 = (26**0)*1 + (26**1)*1
AB -> 28 = (26**0)*2 + (26**1)*1
AZ -> 52 = (26**0)*26 + (26**1)*1 # recall that we have (0, 'Z'), (1, 'A')
Reversing the column_id and enumerate() allows us to use the index as the exponent for 26. The rest is now trivial.
LETTERS.index(j): gives us the index of the letter in LETTERS
sum(): takes a list of numbers and returns the total.
Here is a recursive solution:
def column_string_to_num(s):
n = ord(s[-1]) - 64
if s[:-1]:
return 26 * (column_string_to_num(s[:-1])) + n
else:
return n
column_string_to_num("AB")
#output: 28
The inverse can also be defined recursively, in a similar way:
def column_num_to_string(n):
n, rem = divmod(n - 1, 26)
next_char = chr(65 + rem)
if n:
return column_string(n) + next_char
else:
return next_char
column_num_to_string(28)
#output: 'AB'
Concise and elegant Ruby version:
def col_num(col_name)
col_name.split(//).inject(0) { |n, c| n * 26 + c.upcase.ord - "A".ord + 1 }
end
I'm not sure I understand properly, do you want to "translate" the referenced C# code to python? If so, you were on the right track; just modify it so:
def column_to_number(c):
"""Return number corresponding to excel-style column."""
sum = 0
for l in c:
if not l in string.ascii_letters:
return False
sum*=26
sum+=ord(l.upper())-64
return sum
just do :
print ws.Range("E2").Column
call example :
from win32com import client
xl = client.Dispatch("Excel.Application")
wb = xl.Workbooks.Open("c:/somePath/file.xls")
xl.Visible = 1
ws = wb.Sheets("sheet 1")
print ws.Range("E2").Column
result :
>>5
For index that starts from zero (e.g. A = 0, B = 1, and so on):
def col_to_index(col):
A = ord('A')
return sum(i * 26 + (ord(c) - A) for i, c in enumerate(col[::-1].upper()))
You could also do it by a series of multiplies and adds as follows. Here "A" will equal to 1. Running time is O(n) where n is the length of the column, col.
import functools
def spreadsheet_column_encoding(col):
return functools.reduce(
lambda result, char: result * 26 + ord(char) - ord("A") + 1, col, 0
)
E.g ZZ = 702:
0 * 26 + 90 - 65 + 1 = 26
26 * 26 + 90 - 65 + 1 = 702
P.S: ord('Z') = 90
To convert number to column letter, kindly see my answer here. You get to do the opposite using division and modulus calculations.

Average of two strings in alphabetical/lexicographical order

Suppose you take the strings 'a' and 'z' and list all the strings that come between them in alphabetical order: ['a','b','c' ... 'x','y','z']. Take the midpoint of this list and you find 'm'. So this is kind of like taking an average of those two strings.
You could extend it to strings with more than one character, for example the midpoint between 'aa' and 'zz' would be found in the middle of the list ['aa', 'ab', 'ac' ... 'zx', 'zy', 'zz'].
Might there be a Python method somewhere that does this? If not, even knowing the name of the algorithm would help.
I began making my own routine that simply goes through both strings and finds midpoint of the first differing letter, which seemed to work great in that 'aa' and 'az' midpoint was 'am', but then it fails on 'cat', 'doggie' midpoint which it thinks is 'c'. I tried Googling for "binary search string midpoint" etc. but without knowing the name of what I am trying to do here I had little luck.
I added my own solution as an answer
If you define an alphabet of characters, you can just convert to base 10, do an average, and convert back to base-N where N is the size of the alphabet.
alphabet = 'abcdefghijklmnopqrstuvwxyz'
def enbase(x):
n = len(alphabet)
if x < n:
return alphabet[x]
return enbase(x/n) + alphabet[x%n]
def debase(x):
n = len(alphabet)
result = 0
for i, c in enumerate(reversed(x)):
result += alphabet.index(c) * (n**i)
return result
def average(a, b):
a = debase(a)
b = debase(b)
return enbase((a + b) / 2)
print average('a', 'z') #m
print average('aa', 'zz') #mz
print average('cat', 'doggie') #budeel
print average('google', 'microsoft') #gebmbqkil
print average('microsoft', 'google') #gebmbqkil
Edit: Based on comments and other answers, you might want to handle strings of different lengths by appending the first letter of the alphabet to the shorter word until they're the same length. This will result in the "average" falling between the two inputs in a lexicographical sort. Code changes and new outputs below.
def pad(x, n):
p = alphabet[0] * (n - len(x))
return '%s%s' % (x, p)
def average(a, b):
n = max(len(a), len(b))
a = debase(pad(a, n))
b = debase(pad(b, n))
return enbase((a + b) / 2)
print average('a', 'z') #m
print average('aa', 'zz') #mz
print average('aa', 'az') #m (equivalent to ma)
print average('cat', 'doggie') #cumqec
print average('google', 'microsoft') #jlilzyhcw
print average('microsoft', 'google') #jlilzyhcw
If you mean the alphabetically, simply use FogleBird's algorithm but reverse the parameters and the result!
>>> print average('cat'[::-1], 'doggie'[::-1])[::-1]
cumdec
or rewriting average like so
>>> def average(a, b):
... a = debase(a[::-1])
... b = debase(b[::-1])
... return enbase((a + b) / 2)[::-1]
...
>>> print average('cat', 'doggie')
cumdec
>>> print average('google', 'microsoft')
jlvymlupj
>>> print average('microsoft', 'google')
jlvymlupj
It sounds like what you want, is to treat alphabetical characters as a base-26 value between 0 and 1. When you have strings of different length (an example in base 10), say 305 and 4202, your coming out with a midpoint of 3, since you're looking at the characters one at a time. Instead, treat them as a floating point mantissa: 0.305 and 0.4202. From that, it's easy to come up with a midpoint of .3626 (you can round if you'd like).
Do the same with base 26 (a=0...z=25, ba=26, bb=27, etc.) to do the calculations for letters:
cat becomes 'a.cat' and doggie becomes 'a.doggie', doing the math gives cat a decimal value of 0.078004096, doggie a value of 0.136390697, with an average of 0.107197397 which in base 26 is roughly "cumcqo"
Based on your proposed usage, consistent hashing ( http://en.wikipedia.org/wiki/Consistent_hashing ) seems to make more sense.
Thanks for everyone who answered, but I ended up writing my own solution because the others weren't exactly what I needed. I am trying to average app engine key names, and after studying them a bit more I discovered they actually allow any 7-bit ASCII characters in the names. Additionally I couldn't really rely on the solutions that converted the key names first to floating point, because I suspected floating point accuracy just isn't enough.
To take an average, first you add two numbers together and then divide by two. These are both such simple operations that I decided to just make functions to add and divide base 128 numbers represented as lists. This solution hasn't been used in my system yet so I might still find some bugs in it. Also it could probably be a lot shorter, but this is just something I needed to get done instead of trying to make it perfect.
# Given two lists representing a number with one digit left to decimal point and the
# rest after it, for example 1.555 = [1,5,5,5] and 0.235 = [0,2,3,5], returns a similar
# list representing those two numbers added together.
#
def ladd(a, b, base=128):
i = max(len(a), len(b))
lsum = [0] * i
while i > 1:
i -= 1
av = bv = 0
if i < len(a): av = a[i]
if i < len(b): bv = b[i]
lsum[i] += av + bv
if lsum[i] >= base:
lsum[i] -= base
lsum[i-1] += 1
return lsum
# Given a list of digits after the decimal point, returns a new list of digits
# representing that number divided by two.
#
def ldiv2(vals, base=128):
vs = vals[:]
vs.append(0)
i = len(vs)
while i > 0:
i -= 1
if (vs[i] % 2) == 1:
vs[i] -= 1
vs[i+1] += base / 2
vs[i] = vs[i] / 2
if vs[-1] == 0: vs = vs[0:-1]
return vs
# Given two app engine key names, returns the key name that comes between them.
#
def average(a_kn, b_kn):
m = lambda x:ord(x)
a = [0] + map(m, a_kn)
b = [0] + map(m, b_kn)
avg = ldiv2(ladd(a, b))
return "".join(map(lambda x:chr(x), avg[1:]))
print average('a', 'z') # m#
print average('aa', 'zz') # n-#
print average('aa', 'az') # am#
print average('cat', 'doggie') # d(mstr#
print average('google', 'microsoft') # jlim.,7s:
print average('microsoft', 'google') # jlim.,7s:
import math
def avg(str1,str2):
y = ''
s = 'abcdefghijklmnopqrstuvwxyz'
for i in range(len(str1)):
x = s.index(str2[i])+s.index(str1[i])
x = math.floor(x/2)
y += s[x]
return y
print(avg('z','a')) # m
print(avg('aa','az')) # am
print(avg('cat','dog')) # chm
Still working on strings with different lengths... any ideas?
This version thinks 'abc' is a fraction like 0.abc. In this approach space is zero and a valid input/output.
MAX_ITER = 10
letters = " abcdefghijklmnopqrstuvwxyz"
def to_double(name):
d = 0
for i, ch in enumerate(name):
idx = letters.index(ch)
d += idx * len(letters) ** (-i - 1)
return d
def from_double(d):
name = ""
for i in range(MAX_ITER):
d *= len(letters)
name += letters[int(d)]
d -= int(d)
return name
def avg(w1, w2):
w1 = to_double(w1)
w2 = to_double(w2)
return from_double((w1 + w2) * 0.5)
print avg('a', 'a') # 'a'
print avg('a', 'aa') # 'a mmmmmmmm'
print avg('aa', 'aa') # 'a zzzzzzzz'
print avg('car', 'duck') # 'cxxemmmmmm'
Unfortunately, the naïve algorithm is not able to detect the periodic 'z's, this would be something like 0.99999 in decimal; therefore 'a zzzzzzzz' is actually 'aa' (the space before the 'z' periodicity must be increased by one.
In order to normalise this, you can use the following function
def remove_z_period(name):
if len(name) != MAX_ITER:
return name
if name[-1] != 'z':
return name
n = ""
overflow = True
for ch in reversed(name):
if overflow:
if ch == 'z':
ch = ' '
else:
ch=letters[(letters.index(ch)+1)]
overflow = False
n = ch + n
return n
print remove_z_period('a zzzzzzzz') # 'aa'
I haven't programmed in python in a while and this seemed interesting enough to try.
Bear with my recursive programming. Too many functional languages look like python.
def stravg_half(a, ln):
# If you have a problem it will probably be in here.
# The floor of the character's value is 0, but you may want something different
f = 0
#f = ord('a')
L = ln - 1
if 0 == L:
return ''
A = ord(a[0])
return chr(A/2) + stravg_half( a[1:], L)
def stravg_helper(a, b, ln, x):
L = ln - 1
A = ord(a[0])
B = ord(b[0])
D = (A + B)/2
if 0 == L:
if 0 == x:
return chr(D)
# NOTE: The caller of helper makes sure that len(a)>=len(b)
return chr(D) + stravg_half(a[1:], x)
return chr(D) + stravg_helper(a[1:], b[1:], L, x)
def stravg(a, b):
la = len(a)
lb = len(b)
if 0 == la:
if 0 == lb:
return a # which is empty
return stravg_half(b, lb)
if 0 == lb:
return stravg_half(a, la)
x = la - lb
if x > 0:
return stravg_helper(a, b, lb, x)
return stravg_helper(b, a, la, -x) # Note the order of the args

Categories