Среднее из двух строк в алфавитном / лексикографическом порядке - PullRequest
8 голосов
/ 24 марта 2010

Предположим, вы берете строки 'a' и 'z' и перечисляете все строки, которые находятся между ними в алфавитном порядке: ['a', 'b', 'c' ... 'x', 'y' , 'г']. Возьмите середину этого списка, и вы найдете 'm'. Так что это похоже на усреднение этих двух строк.

Вы можете расширить его на строки с более чем одним символом, например, средняя точка между 'aa' и 'zz' будет найдена в середине списка ['aa', 'ab', 'ac' .. . 'zx', 'zy', 'zz'].

Может быть где-нибудь есть метод Python, который делает это? Если нет, то даже знание названия алгоритма поможет.

Я начал делать свою собственную процедуру, которая просто проходит через обе строки и находит середину первой отличающейся буквы, которая, казалось, прекрасно работала в том, что средняя точка 'aa' и 'az' была 'am', но затем она терпит неудачу на 'кошке', средней точке 'собачка', которую он считает 'с'. Я пробовал поискать в Google «середину строки двоичного поиска» и т. Д., Но, не зная названия того, что я пытаюсь сделать здесь, мне не повезло.

Я добавил свое собственное решение в качестве ответа

Ответы [ 8 ]

8 голосов
/ 24 марта 2010

Если вы определяете алфавит символов, вы можете просто преобразовать в базу 10, сделать среднее и преобразовать обратно в базу-N, где N - размер алфавита.

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

Редактировать : Основываясь на комментариях и других ответах, вы можете обрабатывать строки различной длины, добавляя первую букву алфавита к более короткому слову, пока они не станут одинаковой длины. Это приведет к «среднему» падению между двумя входами в лексикографическом виде. Изменения кода и новые выходы ниже.

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
6 голосов
/ 24 марта 2010

Если вы имеете в виду в алфавитном порядке, просто используйте алгоритм FogleBird, но поменяйте местами параметры и результат!

>>> print average('cat'[::-1], 'doggie'[::-1])[::-1]
cumdec

или переписать среднее значение, как это

>>> 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
6 голосов
/ 24 марта 2010

Звучит так, как будто вы хотите, чтобы алфавитные символы рассматривались как значение base-26 от 0 до 1. Если у вас есть строки различной длины (пример в базе 10), скажем, 305 и 4202, вы получите средняя точка 3, так как вы смотрите на персонажей по одному. Вместо этого относитесь к ним как к мантиссе с плавающей запятой: 0,305 и 0,4202. Исходя из этого, легко получить среднюю точку .3626 (вы можете округлить, если хотите).

Сделайте то же самое с основанием 26 (a = 0 ... z = 25, ba = 26, bb = 27 и т. Д.), Чтобы выполнить вычисления для букв:

cat становится 'a.cat', а doggie становится 'a.doggie', при выполнении математических операций кошка получает десятичное значение 0.078004096, а doggie - значение 0,136390697 со средним значением 0,107197397, что в базе 26 приблизительно равно "cumcqo"

2 голосов
/ 24 марта 2010

Исходя из предложенного вами использования, последовательное хеширование (http://en.wikipedia.org/wiki/Consistent_hashing) представляется более логичным.

1 голос
/ 30 марта 2010

Спасибо всем, кто ответил, но я закончил тем, что написал свое собственное решение, потому что другие были не совсем тем, что мне было нужно. Я пытаюсь усреднить имена ключей движка приложений, и, изучив их немного подробнее, я обнаружил, что они фактически допускают любые 7-битные символы ASCII в именах. Кроме того, я не мог полагаться на решения, которые сначала преобразовывали имена ключей в числа с плавающей запятой, потому что подозревал, что точности с плавающей запятой просто недостаточно.

Чтобы получить среднее значение, сначала сложите два числа вместе, а затем разделите на два. Это обе такие простые операции, что я решил просто сделать функции для добавления и деления базовых 128 чисел, представленных в виде списков. Это решение еще не использовалось в моей системе, поэтому я все еще могу найти некоторые ошибки в нем. Кроме того, это может быть намного короче, но это просто то, что мне нужно было сделать, вместо того, чтобы пытаться сделать его идеальным.

# 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:
0 голосов
/ 25 марта 2010

Я давно не программировал на python, и это показалось мне достаточно интересным, чтобы попробовать. Терпите мое рекурсивное программирование. Слишком много функциональных языков выглядят как 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
0 голосов
/ 24 марта 2010

Эта версия думает, что 'abc' - это дробь, подобная 0.abc. В этом подходе пробел равен нулю и допустимый ввод / вывод.

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'

К сожалению, наивный алгоритм не может обнаружить периодические 'z', это будет что-то вроде 0.99999 в десятичном виде; следовательно, «zzzzzzzz» фактически является «aa» (пространство перед периодичностью «z» должно быть увеличено на единицу.

Чтобы нормализовать это, вы можете использовать следующую функцию

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'
0 голосов
/ 24 марта 2010
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

Все еще работаете со строками разной длины ... есть идеи?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...