Как закодировать url ссылками?
Я хочу создать службу сокращения URL, где вы можете написать длинный URL-адрес в поле ввода, и служба сокращает URL-адрес до"http://www.example.org/abcdef".
Edit: из-за постоянного интереса к этой теме, я опубликовано эффективное решение для GitHub, С реализации JavaScript,PHP, Python и Java. Добавьте свои решения, если хотите :)
вместо "abcdef " там может быть любая другая строка с шестью символами, содержащими a-z, A-Z and 0-9. Это составляет 56~57 миллиардов возможных строк.
мой подход:
у меня есть таблица базы данных с тремя столбцами:
- id, integer, auto-increment
- long, string, длинный URL, введенный пользователем
- short, string, укороченный 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.
что может повторяться до тех пор, пока число больше не делится. Как вы думаете, это хороший подход? У тебя есть идея получше?
27 ответов:
Я бы продолжил ваш подход "преобразовать число в строку". Однако вы поймете, что предложенный алгоритм не работает, если Ваш идентификатор простые и больше, чем 52.
теоретические основы
вам понадобится Биективная Функцияf. Это необходимо для того, чтобы можно было найти обратную функцию g ('abc') = 123 для f (123) = 'abc'
Почему вы хотите использовать хэш?
Вы можете просто использовать простой перевод вашего значения автоинкремента в буквенно-цифровое значение. Вы можете сделать это легко, используя некоторые базовые преобразования. Скажем, у вас символьное пространство (A-Z,a-z,0-9 и т. д.) имеет 40 символов, преобразует идентификатор в базовое число-40 и использует символы-цифры.
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; } }
Не ответ на ваш вопрос, но я бы не использовал сокращенные URL-адреса с учетом регистра. Они трудно запоминаются, обычно нечитабельны (многие шрифты отображают 1 и l, 0 и O и другие символы очень похожи, что они почти невозможно отличить) и прямо подвержены ошибкам. Старайтесь использовать только нижний или верхний регистр.
кроме того, попробуйте иметь формат, в котором вы смешиваете числа и символы в предопределенной форме. Есть исследования, которые показывают, что люди, как правило, помнят один форма лучше других (подумайте о телефонных номерах, где номера сгруппированы в определенную форму). Попробуйте что-то вроде ням-чар-чар-ням-Шарми. Я знаю, что это снизит комбинации, особенно если у вас нет верхнего и нижнего регистра, но это было бы более полезным и, следовательно, полезным.
мой подход: возьмите идентификатор базы данных, затем Base36 кодирует его. Я бы не использовал как верхние, так и строчные буквы, потому что это делает передачу этих URL-адресов по телефону кошмаром, но вы, конечно, можете легко расширить функцию, чтобы быть базовым 62 en/decoder.
вот мой 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; } }
вы можете хэшировать весь URL, но если вы просто хотите сократить идентификатор, сделайте так, как предложил Марсель. Я написал эту реализацию python:
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; } }
Если вы не хотите заново изобретать колесо ... http://lilurl.sourceforge.net/
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вот моя версия для тех, кому это нужно.
// simple approach $original_id = 56789; $shortened_id = base_convert($original_id, 10, 36); $un_shortened_id = base_convert($shortened_id, 36, 10);
узел 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"); }) });
вот достойная функция кодирования 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; }
Не знаю, найдет ли кто - нибудь это полезным-это скорее метод "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); }
Я продолжаю увеличивать целочисленную последовательность на домен в базе данных и использовать Hashids для кодирования целого числа в URL-адрес.
static hashids = Hashids(salt = "my app rocks", minSize = 6)Я запустил скрипт, чтобы посмотреть, сколько времени потребуется, пока он не исчерпает длину символа. За 6 символов он может сделать
164,916,224ссылки, а затем идет до 7 символов. Bitly используются 7 символов. Под 5 символов выглядит странно для меня.Hashids можно декодировать путь 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) }
Почему бы просто не перевести свой идентификатор в строку? Вам просто нужна функция, которая отображает цифру между, скажем, 0 и 61 в одну букву (верхний/нижний регистр) или цифру. Затем примените это для создания, скажем, 4-буквенных кодов, и у вас есть 14,7 миллиона URL-адресов.
вот что я использую:
# 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))])Это очень быстро и может занять много целых чисел.
для аналогичного проекта, чтобы получить новый ключ, я делаю функцию обертки вокруг генератор случайных строк это вызывает генератор, пока я не получу строку, которая еще не была использована в моей хэш-таблице. Этот метод замедлится, как только ваше пространство имен начнет заполняться, но, как вы уже сказали, даже с 6 символами у вас есть много пространства имен для работы.
вы специально опустили O, 0, i ?
только что создал класс 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 stackoverflow 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 { /** * Explicitely 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; } } ?>
У меня есть вариант проблемы, в котором я храню веб-страницы от многих разных авторов и должен предотвратить обнаружение страниц по догадкам. Поэтому мои короткие URL-адреса добавляют пару дополнительных цифр в строку Base-62 для номера страницы. Эти дополнительные цифры генерируются из информации в самой записи страницы, и они гарантируют, что только 1 из 3844 URL-адресов действительны (предполагая 2-значную базу-62). Вы можете увидеть описание контура в http://mgscan.com/MBWL.
очень хороший ответ, я создал реализацию Golang bjf:
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
/** * <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)); } }
моя версия python3
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")
вот узел.реализация JS, что скорее всего бит.лы. генерировать случайные 7 строку символов. используя узел.JS crypto для генерации очень случайных 25 символов, чем случайный выбор 7 символов.
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; }; }
для качественного решения NodeJS / Javascript см. id-shortener модуль, который тщательно протестирован и используется в производстве в течение месяца.
Он обеспечивает эффективное сокращение id / url, поддерживаемое подключаемым хранилищем по умолчанию Рэдис, и вы даже можете настроить свой короткий набор символов id и является ли сокращение идемпотентных. Это важное различие, которое не все сокращатели URL принимают во внимание счет.
по отношению к другим ответы здесь, этот модуль реализует отличные принял Марсель Jackwerth выше ответ.
ядро решения обеспечивается следующим 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
реализация в 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) } }
функция, основанная в классе 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; }
Comments