Как мне создать сокращатель URL?

667

Я хочу создать службу сокращения URL-адресов, в которой вы можете записать длинный URL-адрес в поле ввода, а служба сокращает URL-адрес до " http://www.example.org/abcdef".

Вместо " abcdef" может быть любая другая строка, содержащая шесть символов a-z, A-Z and 0-9. Это составляет 56 ~ 57 миллиардов возможных строк.

Мой подход:

У меня есть таблица базы данных с тремя столбцами:

  1. id, целое число, автоинкремент
  2. long, string, длинный URL, введенный пользователем
  3. короткий, строка, сокращенный URL (или только шесть символов)

Затем я вставил бы длинный URL в таблицу. Затем я бы выбрал значение автоинкремента для " id" и построил его хеш. Этот хеш должен быть вставлен как " short". Но какой хеш я должен создать? Алгоритмы хеширования, такие как MD5, создают слишком длинные строки. Я не использую эти алгоритмы, я думаю. Самостоятельный алгоритм тоже подойдет.

Моя идея:

Для " http://www.google.de/" я получаю идентификатор автоинкремента 239472. Затем я делаю следующие шаги:

short = '';
if divisible by 2, add "a"+the result to short
if divisible by 3, add "b"+the result to short
... until I have divisors for a-z and A-Z.

Это может повторяться до тех пор, пока число больше не будет делиться. Как вы думаете, это хороший подход? У тебя есть идея получше?

Из-за постоянного интереса к этой теме я опубликовал эффективное решение для GitHub с реализациями для JavaScript , PHP , Python и Java . Добавьте ваши решения, если хотите :)

каркать
источник
5
@gudge Смысл этих функций в том, что они имеют обратную функцию. Это означает, что вы можете иметь как encode()и decode()функции. Поэтому необходимо выполнить следующие шаги: (1) сохранить URL-адрес в базе данных (2) получить уникальный идентификатор строки для этого URL-адреса из базы данных (3) преобразовать целочисленный идентификатор в короткую строку encode(), например, 273984в f5a4(4) использовать короткую строку (например f4a4) в URL для общего доступа (5) При получении запроса на короткую строку (например 20a8), декодируйте строку в целочисленный идентификатор с помощью decode()(6). Найдите URL в базе данных для данного идентификатора. Для конвертации используйте: github.com/delight-im/ShortURL
caw
@ Марко, какой смысл хранить хэш в базе данных?
Максим Ви.
3
@MaksimVi. Если у вас есть обратимая функция, ее нет. Если бы у вас была односторонняя хеш-функция, она была бы.
caw
1
было бы неправильно, если бы мы использовали простой алгоритм CRC32 для сокращения URL? Хотя это очень маловероятно в случае коллизии (вывод CRC32 обычно имеет длину 8 символов, что дает нам более 30 миллионов возможностей). Если сгенерированный вывод CRC32 уже использовался ранее и был найден в базе данных, мы могли бы засолить длинный URL случайным числом пока мы не найдем вывод CRC32, который является уникальным в моей базе данных. Насколько это плохо, иначе или уродливо для простого решения?
Ракиб

Ответы:

817

Я бы продолжил ваш подход "конвертировать число в строку". Однако вы поймете, что предложенный вами алгоритм не работает, если ваш идентификатор простое и больше 52 .

Теоретические основы

Вам нужна биективная функция f . Это необходимо, чтобы вы могли найти обратную функцию g ('abc') = 123 для вашей функции f (123) = 'abc' . Это означает:

  • Не должно быть x1, x2 (с x1 ≠ x2) , из-за которых f (x1) = f (x2) ,
  • и для каждого y вы должны быть в состоянии найти x, так что f (x) = y .

Как преобразовать идентификатор в сокращенный URL

  1. Подумайте об алфавите, который мы хотим использовать. В вашем случае это так [a-zA-Z0-9]. Он содержит 62 буквы .
  2. Возьмите автоматически сгенерированный уникальный числовой ключ ( idнапример, автоинкремент таблицы MySQL).

    Для этого примера я буду использовать 125 10 (125 с основанием 10).

  3. Теперь вам нужно конвертировать 125 10 в X 62 (база 62).

    125 10 = 2 × 62 1 + 1 × 62 0 =[2,1]

    Это требует использования целочисленного деления и по модулю. Пример псевдокода:

    digits = []
    
    while num > 0
      remainder = modulo(num, 62)
      digits.push(remainder)
      num = divide(num, 62)
    
    digits = digits.reverse
    

    Теперь сопоставьте индексы 2 и 1 с вашим алфавитом. Вот как может выглядеть ваше отображение (например, с массивом):

    0  → a
    1  → b
    ...
    25 → z
    ...
    52 → 0
    61 → 9
    

    При 2 → c и 1 → b вы получите cb 62 в качестве сокращенного URL.

    http://shor.ty/cb
    

Как разрешить сокращенный URL к начальному идентификатору

Обратное еще проще. Вы просто делаете обратный поиск в вашем алфавите.

  1. e9a 62 будет преобразован в «4-ю, 61-ю и 0-ю букву в алфавите».

    e9a 62 = [4,61,0]= 4 × 62 2 + 61 × 62 1 + 0 × 62 0 = 19158 10

  2. Теперь найдите вашу базу данных с помощью WHERE id = 19158и выполните перенаправление.

Пример реализации (предоставляется комментаторами)

Marcel Jackwerth
источник
18
Не забывайте очищать URL-адреса от вредоносного кода JavaScript! Помните, что javascript может быть закодирован в base64 в URL, так что просто поиск 'javascript' не достаточно хорош. J
Bjorn
3
Функция должна быть биективной (инъективной и сюръективной), чтобы иметь обратную.
Гамбо
57
Пища для размышлений, возможно, было бы полезно добавить в URL двухсимвольную контрольную сумму. Это предотвратит прямую итерацию всех URL в вашей системе. Что-то простое, например, f (контрольная сумма (id)% (62 ^ 2)) + f (id) = url_id
коблас
6
Что касается санации URL-адресов, одна из проблем, с которой вы столкнетесь, - спамеры, использующие ваш сервис для маскировки своих URL-адресов, чтобы избежать спам-фильтров. Вам нужно либо ограничить службу известными хорошими актерами, либо применить фильтрацию спама к длинным URL-адресам. В противном случае вы будете оскорблены спамерами.
Эдвард Фальк
75
Base62 может быть плохим выбором, потому что он может генерировать слова f * (например, 3792586=='F_ck'с u вместо _). Я бы исключил некоторые символы, такие как U / U, чтобы минимизировать это.
Пауло Скардин
56

Почему вы хотите использовать хеш?

Вы можете просто использовать простой перевод значения автоинкремента в буквенно-цифровое значение. Вы можете сделать это легко с помощью некоторого базового преобразования. Допустим, у вас есть пространство символов (AZ, az, 0-9 и т. Д.), Состоящее из 40 символов, преобразуйте идентификатор в число с основанием 40 и используйте символы в качестве цифр.

shoosh
источник
13
Помимо того, что AZ, az и 0-9 = 62 символа, а не 40, вы правы на отметке.
Эван Теран
Спасибо! Должен ли я использовать алфавит Base-62 тогда? ru.wikipedia.org/wiki/Base_62 Но как мне конвертировать идентификаторы в число base-62?
caw
Использование базового алгоритма преобразования курса - en.wikipedia.org/wiki/Base_conversion#Change_of_radix
shoosh
2
Что касается «Почему вы хотите использовать хеш?», Базовое преобразование, основанное на автоприращении, будет создавать последовательные URL-адреса, поэтому вам должно быть удобно, чтобы люди могли «просматривать» сокращенные URL-адреса других людей, правильно?
Эндрю Колсон
2
Имея достаточно ресурсов и времени, вы можете «просмотреть» все URL-адреса любой службы сокращения URL-адресов.
Shoosh
51
public class UrlShortener {
    private static final String ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    private static final int    BASE     = ALPHABET.length();

    public static String encode(int num) {
        StringBuilder sb = new StringBuilder();
        while ( num > 0 ) {
            sb.append( ALPHABET.charAt( num % BASE ) );
            num /= BASE;
        }
        return sb.reverse().toString();   
    }

    public static int decode(String str) {
        int num = 0;
        for ( int i = 0; i < str.length(); i++ )
            num = num * BASE + ALPHABET.indexOf(str.charAt(i));
        return num;
    }   
}
Stradivariuz
источник
Мне очень нравится идея, единственная проблема, с которой я сталкиваюсь, заключается в том, что я продолжаю получать переменную num в функции декодирования за пределами границ (даже долго), у вас есть идея, как заставить это работать? или это только теоретическое?
user1322801
@ user1322801: Предположительно, вы пытаетесь декодировать что-то намного большее, чем то, что фактически может обрабатывать функция кодирования. Вы могли бы получить еще больше, если бы вы конвертировали все "целые" в BigInteger, но если у вас нет> 9223372036854775807 индексов, то, вероятно, должно хватить long.
biggusjimmus
2
Могу ли я знать, какова важность реверса? т.е. sb.reverse (). toString ();
dotNet Decoder
Это что 62 ^ 62 = 1,7 трлн?
Ной Тони
33

Не ответ на ваш вопрос, но я бы не стал использовать сокращенные URL-адреса с учетом регистра. Их трудно запомнить, как правило, нечитаемые (многие шрифты отображают 1 и l, 0 и O и другие символы очень похожи, так что почти невозможно различить их) и подвержены прямым ошибкам. Попробуйте использовать только нижний или верхний регистр.

Кроме того, попробуйте создать формат, в котором вы будете смешивать цифры и символы в заранее определенной форме. Существуют исследования, которые показывают, что люди, как правило, запоминают одну форму лучше, чем другие (например, номера телефонов, где номера сгруппированы в определенной форме). Попробуйте что-то вроде num-char-char-num-char-char. Я знаю, что это понизит комбинации, особенно если у вас нет прописных и строчных букв, но это будет более полезным и, следовательно, полезным.

ясень
источник
2
Спасибо, очень хорошая идея. Я еще не думал об этом. Понятно, что это зависит от вида использования, имеет ли это смысл или нет.
caw
19
Это не будет проблемой, если люди будут строго копировать и вставлять короткие URL.
Эдвард Фальк
2
Цель коротких URL-адресов - не быть запоминающимся или легко говорить. Это только нажмите или скопировать / вставить.
Хьюго Ногейра
да, я думал, что короткий URL предназначен только для того, чтобы люди могли его перечислить или отправить по электронной почте, и поэтому он короткий и не будет занимать 200 символов, как некоторые URL, поэтому случай не проблема
неполярность
29

Мой подход: взять идентификатор базы данных, затем Base36 кодировать его . Я НЕ буду использовать как заглавные, так и строчные буквы, потому что это делает передачу этих URL-адресов по телефону кошмаром, но вы, конечно, можете легко расширить эту функцию до базового 62 en / декодера.

Майкл Стум
источник
Спасибо, ты прав. Независимо от того, есть ли у вас 2 176 782 336 возможностей или 56 800 235 584, это одно и то же: и того и другого будет достаточно. Поэтому я буду использовать кодировку базы 36.
caw
Это может быть очевидно, но вот некоторый код PHP, на который есть ссылка в википедии, для кодирования base64 в php tonymarston.net/php-mysql/converter.html
Райан Уайт
8

Вот мой класс PHP 5.

<?php
class Bijective
{
    public $dictionary = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";

    public function __construct()
    {
        $this->dictionary = str_split($this->dictionary);
    }

    public function encode($i)
    {
        if ($i == 0)
        return $this->dictionary[0];

        $result = '';
        $base = count($this->dictionary);

        while ($i > 0)
        {
            $result[] = $this->dictionary[($i % $base)];
            $i = floor($i / $base);
        }

        $result = array_reverse($result);

        return join("", $result);
    }

    public function decode($input)
    {
        $i = 0;
        $base = count($this->dictionary);

        $input = str_split($input);

        foreach($input as $char)
        {
            $pos = array_search($char, $this->dictionary);

            $i = $i * $base + $pos;
        }

        return $i;
    }
}
Xeoncross
источник
6

Решение Node.js и MongoDB

Так как мы знаем формат, который MongoDB использует для создания нового ObjectId с 12 байтами.

  • 4-байтовое значение, представляющее секунды с начала эпохи Unix,
  • 3-байтовый идентификатор машины,
  • 2-байтовый идентификатор процесса
  • 3-байтовый счетчик (на вашем компьютере), начиная со случайного значения.

Пример (я выбираю случайную последовательность) a1b2c3d4e5f6g7h8i9j1k2l3

  • a1b2c3d4 представляет секунды с начала эпохи Unix,
  • 4e5f6g7 представляет идентификатор машины,
  • h8i9 представляет идентификатор процесса
  • j1k2l3 представляет счетчик, начиная со случайного значения.

Поскольку счетчик будет уникальным, если мы храним данные на одной машине, мы можем получить их, не сомневаясь, что они будут повторяться.

Таким образом, короткий URL будет счетчиком, а вот фрагмент кода, предполагающий, что ваш сервер работает правильно.

const mongoose = require('mongoose');
const Schema = mongoose.Schema;

// Create a schema
const shortUrl = new Schema({
    long_url: { type: String, required: true },
    short_url: { type: String, required: true, unique: true },
  });
const ShortUrl = mongoose.model('ShortUrl', shortUrl);

// The user can request to get a short URL by providing a long URL using a form

app.post('/shorten', function(req ,res){
    // Create a new shortUrl */
    // The submit form has an input with longURL as its name attribute.
    const longUrl = req.body["longURL"];
    const newUrl = ShortUrl({
        long_url : longUrl,
        short_url : "",
    });
    const shortUrl = newUrl._id.toString().slice(-6);
    newUrl.short_url = shortUrl;
    console.log(newUrl);
    newUrl.save(function(err){
        console.log("the new URL is added");
    })
});
Firas Omrane
источник
1
Как СУБД будет лучше, чем хранилище без ключа / значения ключа?
kjs3
@ kjs3 да, вы правы, поскольку нет никаких связей с другими таблицами, нет необходимости в СУБД, и хранилище значений ключей будет быстрее.
Firas Omrane
4

Версия C #:

public class UrlShortener 
{
    private static String ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    private static int    BASE     = 62;

    public static String encode(int num)
    {
        StringBuilder sb = new StringBuilder();

        while ( num > 0 )
        {
            sb.Append( ALPHABET[( num % BASE )] );
            num /= BASE;
        }

        StringBuilder builder = new StringBuilder();
        for (int i = sb.Length - 1; i >= 0; i--)
        {
            builder.Append(sb[i]);
        }
        return builder.ToString(); 
    }

    public static int decode(String str)
    {
        int num = 0;

        for ( int i = 0, len = str.Length; i < len; i++ )
        {
            num = num * BASE + ALPHABET.IndexOf( str[(i)] ); 
        }

        return num;
    }   
}
user1477388
источник
4

Вы можете хэшировать весь URL, но если вы просто хотите сократить идентификатор, сделайте, как предложил Марсель. Я написал эту реализацию Python:

https://gist.github.com/778542

bhelx
источник
4

Я продолжаю увеличивать целочисленную последовательность для каждого домена в базе данных и использую Hashids для кодирования целого числа в URL-пути.

static hashids = Hashids(salt = "my app rocks", minSize = 6)

Я запустил скрипт, чтобы увидеть, сколько времени потребуется, чтобы исчерпать длину символа. Для шести символов он может делать 164,916,224ссылки, а затем доходит до семи символов. Битли использует семь символов. Под пятью персонажами выглядит странно для меня.

Хашиды могут декодировать URL-путь обратно к целому числу, но более простым решением является использование всей короткой ссылки sho.rt/ka8ds3в качестве первичного ключа.

Вот полная концепция:

function addDomain(domain) {
    table("domains").insert("domain", domain, "seq", 0)
}

function addURL(domain, longURL) {
    seq = table("domains").where("domain = ?", domain).increment("seq")
    shortURL = domain + "/" + hashids.encode(seq)
    table("links").insert("short", shortURL, "long", longURL)
    return shortURL
}

// GET /:hashcode
function handleRequest(req, res) {
    shortURL = req.host + "/" + req.param("hashcode")
    longURL = table("links").where("short = ?", shortURL).get("long")
    res.redirect(301, longURL)
}
AJcodez
источник
3

Если вы не хотите заново изобретать колесо ... http://lilurl.sourceforge.net/

Алистер Булман
источник
1
«Извините, похоже, что спамеры добрались до этого. Попробуйте вместо этого tinyurl».
принимает
на демонстрационный сайт. Исходный код по-прежнему можно загрузить из Sourceforge.
Алистер Булман
3
// simple approach

$original_id = 56789;

$shortened_id = base_convert($original_id, 10, 36);

$un_shortened_id = base_convert($shortened_id, 36, 10);
phirschybar
источник
2
alphabet = map(chr, range(97,123)+range(65,91)) + map(str,range(0,10))

def lookup(k, a=alphabet):
    if type(k) == int:
        return a[k]
    elif type(k) == str:
        return a.index(k)


def encode(i, a=alphabet):
    '''Takes an integer and returns it in the given base with mappings for upper/lower case letters and numbers 0-9.'''
    try:
        i = int(i)
    except Exception:
        raise TypeError("Input must be an integer.")

    def incode(i=i, p=1, a=a):
        # Here to protect p.                                                                                                                                                                                                                
        if i <= 61:
            return lookup(i)

        else:
            pval = pow(62,p)
            nval = i/pval
            remainder = i % pval
            if nval <= 61:
                return lookup(nval) + incode(i % pval)
            else:
                return incode(i, p+1)

    return incode()



def decode(s, a=alphabet):
    '''Takes a base 62 string in our alphabet and returns it in base10.'''
    try:
        s = str(s)
    except Exception:
        raise TypeError("Input must be a string.")

    return sum([lookup(i) * pow(62,p) for p,i in enumerate(list(reversed(s)))])a

Вот моя версия для тех, кому это нужно.

MrChrisRodriguez
источник
2

Взгляните на https://hashids.org/ это с открытым исходным кодом и на многих языках.

На их странице изложены некоторые подводные камни других подходов.

Джон
источник
1

Почему бы просто не перевести свой идентификатор в строку? Вам просто нужна функция, которая отображает цифру, скажем, от 0 до 61, в одну букву (верхний / нижний регистр) или цифру. Затем примените это для создания, скажем, четырехбуквенных кодов, и вы получите 14,7 миллионов URL-адресов.

cr333
источник
+1 за упрощенное мышление. Это действительно настолько просто. Я только что опубликовал ответ, который делает именно это. У меня есть некоторый производственный код, который запрашивает базу данных, чтобы убедиться, что нет повторяющихся строк и все уникально.
Эндрю Риз
1

Вот достойная функция кодирования URL для PHP ...

// From http://snipplr.com/view/22246/base62-encode--decode/
private function base_encode($val, $base=62, $chars='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ') {
    $str = '';
    do {
        $i = fmod($val, $base);
        $str = $chars[$i] . $str;
        $val = ($val - $i) / $base;
    } while($val > 0);
    return $str;
}
Саймон Ист
источник
1

Не знаю, сочтет ли кто-нибудь это полезным - это скорее метод 'hack n slash', но он прост и хорошо работает, если вам нужны только определенные символы.

$dictionary = "abcdfghjklmnpqrstvwxyz23456789";
$dictionary = str_split($dictionary);

// Encode
$str_id = '';
$base = count($dictionary);

while($id > 0) {
    $rem = $id % $base;
    $id = ($id - $rem) / $base;
    $str_id .= $dictionary[$rem];
}


// Decode
$id_ar = str_split($str_id);
$id = 0;

for($i = count($id_ar); $i > 0; $i--) {
    $id += array_search($id_ar[$i-1], $dictionary) * pow($base, $i - 1);
} 
Райан Чармли
источник
1

Вы пропустили O, 0 и я специально?

Я только что создал класс PHP на основе решения Райана.

<?php

    $shorty = new App_Shorty();

    echo 'ID: ' . 1000;
    echo '<br/> Short link: ' . $shorty->encode(1000);
    echo '<br/> Decoded Short Link: ' . $shorty->decode($shorty->encode(1000));


    /**
     * A nice shorting class based on Ryan Charmley's suggestion see the link on Stack Overflow below.
     * @author Svetoslav Marinov (Slavi) | http://WebWeb.ca
     * @see http://stackoverflow.com/questions/742013/how-to-code-a-url-shortener/10386945#10386945
     */
    class App_Shorty {
        /**
         * Explicitly omitted: i, o, 1, 0 because they are confusing. Also use only lowercase ... as
         * dictating this over the phone might be tough.
         * @var string
         */
        private $dictionary = "abcdfghjklmnpqrstvwxyz23456789";
        private $dictionary_array = array();

        public function __construct() {
            $this->dictionary_array = str_split($this->dictionary);
        }

        /**
         * Gets ID and converts it into a string.
         * @param int $id
         */
        public function encode($id) {
            $str_id = '';
            $base = count($this->dictionary_array);

            while ($id > 0) {
                $rem = $id % $base;
                $id = ($id - $rem) / $base;
                $str_id .= $this->dictionary_array[$rem];
            }

            return $str_id;
        }

        /**
         * Converts /abc into an integer ID
         * @param string
         * @return int $id
         */
        public function decode($str_id) {
            $id = 0;
            $id_ar = str_split($str_id);
            $base = count($this->dictionary_array);

            for ($i = count($id_ar); $i > 0; $i--) {
                $id += array_search($id_ar[$i - 1], $this->dictionary_array) * pow($base, $i - 1);
            }
            return $id;
        }
    }
?>
Святослав Маринов
источник
Да. Вы видели комментарий чуть ниже объявления класса?
Святослав Маринов
0

Это то, что я использую:

# Generate a [0-9a-zA-Z] string
ALPHABET = map(str,range(0, 10)) + map(chr, range(97, 123) + range(65, 91))

def encode_id(id_number, alphabet=ALPHABET):
    """Convert an integer to a string."""
    if id_number == 0:
        return alphabet[0]

    alphabet_len = len(alphabet) # Cache

    result = ''
    while id_number > 0:
        id_number, mod = divmod(id_number, alphabet_len)
        result = alphabet[mod] + result

    return result

def decode_id(id_string, alphabet=ALPHABET):
    """Convert a string to an integer."""
    alphabet_len = len(alphabet) # Cache
    return sum([alphabet.index(char) * pow(alphabet_len, power) for power, char in enumerate(reversed(id_string))])

Это очень быстро и может занимать длинные целые числа.

Давид Муццарелли
источник
0

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

Джоэл Бергер
источник
Этот подход сработал для вас в долгосрочной перспективе?
Крис
Если честно, я понятия не имею, на какой проект я ссылался там :-P
Джоэл Бергер
0

У меня есть вариант проблемы в том, что я храню веб-страницы от разных авторов, и мне нужно предотвратить обнаружение страниц путем догадок. Поэтому мои короткие URL-адреса добавляют пару дополнительных цифр к строке Base-62 для номера страницы. Эти дополнительные цифры генерируются из информации в самой записи страницы, и они гарантируют, что только 1 из 3844 URL-адресов являются действительными (при условии 2-значный Base-62). Вы можете увидеть общее описание на http://mgscan.com/MBWL .

Грэхем
источник
0

Очень хороший ответ, я создал реализацию bjf на Golang:

package bjf

import (
    "math"
    "strings"
    "strconv"
)

const alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"

func Encode(num string) string {
    n, _ := strconv.ParseUint(num, 10, 64)
    t := make([]byte, 0)

    /* Special case */
    if n == 0 {
        return string(alphabet[0])
    }

    /* Map */
    for n > 0 {
        r := n % uint64(len(alphabet))
        t = append(t, alphabet[r])
        n = n / uint64(len(alphabet))
    }

    /* Reverse */
    for i, j := 0, len(t) - 1; i < j; i, j = i + 1, j - 1 {
        t[i], t[j] = t[j], t[i]
    }

    return string(t)
}

func Decode(token string) int {
    r := int(0)
    p := float64(len(token)) - 1

    for i := 0; i < len(token); i++ {
        r += strings.Index(alphabet, string(token[i])) * int(math.Pow(float64(len(alphabet)), p))
        p--
    }

    return r
}

Размещено на github: https://github.com/xor-gate/go-bjf

Джерри Джейкобс
источник
0
/**
 * <p>
 *     Integer to character and vice-versa
 * </p>
 *  
 */
public class TinyUrl {

    private final String characterMap = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    private final int charBase = characterMap.length();

    public String covertToCharacter(int num){
        StringBuilder sb = new StringBuilder();

        while (num > 0){
            sb.append(characterMap.charAt(num % charBase));
            num /= charBase;
        }

        return sb.reverse().toString();
    }

    public int covertToInteger(String str){
        int num = 0;
        for(int i = 0 ; i< str.length(); i++)
            num += characterMap.indexOf(str.charAt(i)) * Math.pow(charBase , (str.length() - (i + 1)));

        return num;
    }
}

class TinyUrlTest{

    public static void main(String[] args) {
        TinyUrl tinyUrl = new TinyUrl();
        int num = 122312215;
        String url = tinyUrl.covertToCharacter(num);
        System.out.println("Tiny url:  " + url);
        System.out.println("Id: " + tinyUrl.covertToInteger(url));
    }
}
Хришикеш Мишра
источник
0

Реализация в Scala:

class Encoder(alphabet: String) extends (Long => String) {

  val Base = alphabet.size

  override def apply(number: Long) = {
    def encode(current: Long): List[Int] = {
      if (current == 0) Nil
      else (current % Base).toInt :: encode(current / Base)
    }
    encode(number).reverse
      .map(current => alphabet.charAt(current)).mkString
  }
}

class Decoder(alphabet: String) extends (String => Long) {

  val Base = alphabet.size

  override def apply(string: String) = {
    def decode(current: Long, encodedPart: String): Long = {
      if (encodedPart.size == 0) current
      else decode(current * Base + alphabet.indexOf(encodedPart.head),encodedPart.tail)
    }
    decode(0,string)
  }
}

Тестовый пример с тестом Scala:

import org.scalatest.{FlatSpec, Matchers}

class DecoderAndEncoderTest extends FlatSpec with Matchers {

  val Alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"

  "A number with base 10" should "be correctly encoded into base 62 string" in {
    val encoder = new Encoder(Alphabet)
    encoder(127) should be ("cd")
    encoder(543513414) should be ("KWGPy")
  }

  "A base 62 string" should "be correctly decoded into a number with base 10" in {
    val decoder = new Decoder(Alphabet)
    decoder("cd") should be (127)
    decoder("KWGPy") should be (543513414)
  }

}
дрейфовать
источник
0

Функция основана на классе Xeoncross

function shortly($input){
$dictionary = ['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','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','0','1','2','3','4','5','6','7','8','9'];
if($input===0)
    return $dictionary[0];
$base = count($dictionary);
if(is_numeric($input)){
    $result = [];
    while($input > 0){
        $result[] = $dictionary[($input % $base)];
        $input = floor($input / $base);
    }
    return join("", array_reverse($result));
}
$i = 0;
$input = str_split($input);
foreach($input as $char){
    $pos = array_search($char, $dictionary);
    $i = $i * $base + $pos;
}
return $i;
}
Луис Сосед
источник
0

Вот реализация Node.js, которая, вероятно, будет bit.ly. создать очень случайную строку из семи символов.

Он использует криптографию Node.js для генерации очень случайного набора из 25 символов вместо случайного выбора семи символов.

var crypto = require("crypto");
exports.shortURL = new function () {
    this.getShortURL = function () {
        var sURL = '',
            _rand = crypto.randomBytes(25).toString('hex'),
            _base = _rand.length;
        for (var i = 0; i < 7; i++)
            sURL += _rand.charAt(Math.floor(Math.random() * _rand.length));
        return sURL;
    };
}
Хафиз Арслан
источник
Что вы подразумеваете под "bit.ly" ?
Питер Мортенсен
0

Моя версия Python 3

base_list = list("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
base = len(base_list)

def encode(num: int):
    result = []
    if num == 0:
        result.append(base_list[0])

    while num > 0:
        result.append(base_list[num % base])
        num //= base

    print("".join(reversed(result)))

def decode(code: str):
    num = 0
    code_list = list(code)
    for index, code in enumerate(reversed(code_list)):
        num += base_list.index(code) * base ** index
    print(num)

if __name__ == '__main__':
    encode(341413134141)
    decode("60FoItT")
wyx
источник
0

Качественное решение Node.js / JavaScript см. В модуле id-shorttener , который тщательно протестирован и уже несколько месяцев используется в производстве.

Он обеспечивает эффективное сокращение идентификатора / URL-адреса при поддержке подключаемого хранилища по умолчанию Redis , и вы даже можете настроить свой короткий набор символов идентификатора и определить, является ли сокращение идемпотентным . Это важное различие, которое учитывают не все средства сокращения URL.

В отношении других ответов здесь, этот модуль реализует превосходный принятый ответ Марселя Джекверта выше.

Основу решения предоставляет следующий фрагмент Redis Lua :

local sequence = redis.call('incr', KEYS[1])

local chars = '0123456789ABCDEFGHJKLMNPQRSTUVWXYZ_abcdefghijkmnopqrstuvwxyz'
local remaining = sequence
local slug = ''

while (remaining > 0) do
  local d = (remaining % 60)
  local character = string.sub(chars, d + 1, d + 1)

  slug = character .. slug
  remaining = (remaining - d) / 60
end

redis.call('hset', KEYS[2], slug, ARGV[1])

return slug
fisch2
источник
0

Почему бы просто не сгенерировать случайную строку и не добавить ее к базовому URL? Это очень упрощенная версия этого в C # .

static string chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
static string baseUrl = "https://google.com/";

private static string RandomString(int length)
{
    char[] s = new char[length];
    Random rnd = new Random();
    for (int x = 0; x < length; x++)
    {
        s[x] = chars[rnd.Next(chars.Length)];
    }
    Thread.Sleep(10);

    return new String(s);
}

Затем просто добавьте случайную строку в baseURL:

string tinyURL = baseUrl + RandomString(5);

Помните, что это очень упрощенная версия, и, возможно, метод RandomString может создавать повторяющиеся строки. В процессе производства вы бы хотели учесть наличие дублирующихся строк, чтобы у вас всегда был уникальный URL-адрес. У меня есть некоторый код, который учитывает дублирующиеся строки, запрашивая таблицу базы данных, которой я мог бы поделиться, если кому-то интересно.

Эндрю Риз
источник
0

Это мои первоначальные мысли, и можно больше думать, или провести какое-то моделирование, чтобы увидеть, работает ли оно хорошо или требуется какое-либо улучшение:

Мой ответ - запомнить длинный URL-адрес в базе данных и использовать идентификатор 0для 9999999999999999(или сколь угодно большого числа).

Но ID 0 9999999999999999может быть проблемой, потому что

  1. это может быть короче, если мы используем шестнадцатеричное, или даже base62 или base64. (base64 так же, как YouTube, используя A- Z a- z 0- 9 _и -)
  2. если оно возрастает от 0до 9999999999999999равномерно, то хакеры могут посетить их в таком порядке , и знать , что URL - адреса люди посылают друг другу, так что это может быть вопрос о конфиденциальности

Мы можем сделать это:

  1. один сервер должен быть выделен 0для 999одного сервера, сервера A, поэтому теперь сервер A имеет 1000 таких идентификаторов. Таким образом, если существует 20 или 200 серверов, постоянно нуждающихся в новых идентификаторах, не нужно постоянно запрашивать каждый новый идентификатор, а вместо этого запрашивать один раз 1000 идентификаторов.
  2. например, для идентификатора 1 поменяйте местами биты. Так и 000...00000001получается 10000...000, что при преобразовании в base64 он будет неравномерно увеличивать ID каждый раз.
  3. используйте XOR, чтобы перевернуть биты для окончательных идентификаторов. Например, XOR с 0xD5AA96...2373(как секретный ключ), и некоторые биты будут перевернуты. (всякий раз, когда секретный ключ имеет 1 бит, он переворачивает бит идентификатора). Это сделает идентификаторы еще сложнее угадать и выглядеть более случайным

В соответствии с этой схемой один сервер, который выделяет идентификаторы, может формировать идентификаторы, как и 20 или 200 серверов, запрашивающих назначение идентификаторов. Распределяющий сервер должен использовать блокировку / семафор, чтобы два запрашивающих сервера не могли получить один и тот же пакет (или если он принимает одно соединение за раз, это уже решает проблему). Поэтому мы не хотим, чтобы строка (очередь) была слишком длинной для ожидания выделения. Вот почему выделение 1000 или 10000 за раз может решить проблему.

nonopolarity
источник