我想创建一个URL缩短服务,你可以写一个长URL到输入字段和服务缩短URL为“http://www.example.org/abcdef”。

可以用包含a-z, a-z和0-9的6个字符的字符串代替"abcdef"。这样就有560 ~ 570亿个可能的字符串。

我的方法:

我有一个有三列的数据库表:

Id,整数,自动递增 long, string,用户输入的长URL 短,字符串,缩短的URL(或只有六个字符)

然后将长URL插入到表中。然后,我将为“id”选择自动递增的值,并构建它的散列。这个散列应该作为“short”插入。但是我应该构建什么样的哈希呢?像MD5这样的哈希算法会创建太长的字符串。我认为我不用这些算法。一个自建的算法也可以。

我的想法:

对于“http://www.google.de/”,我得到了自动增量id 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的实现。如果你喜欢,添加你的解决方案:)


当前回答

我将继续您的“将数字转换为字符串”方法。但是,如果您的ID是质数且大于52,您将意识到您提出的算法将失败。

理论背景

你需要一个双射函数f。这是必要的,这样你就可以为你的f(123) = 'abc'函数找到一个逆函数g('abc') = 123。这意味着:

一定不存在x1 x2 (x1≠x2)使得f(x1) = f(x2) 对于每一个y,你必须能找到一个x,使f(x) = y。

如何将ID转换为缩短的URL

Think of an alphabet we want to use. In your case, that's [a-zA-Z0-9]. It contains 62 letters. Take an auto-generated, unique numerical key (the auto-incremented id of a MySQL table for example). For this example, I will use 12510 (125 with a base of 10). Now you have to convert 12510 to X62 (base 62). 12510 = 2×621 + 1×620 = [2,1] This requires the use of integer division and modulo. A pseudo-code example: digits = [] while num > 0 remainder = modulo(num, 62) digits.push(remainder) num = divide(num, 62) digits = digits.reverse Now map the indices 2 and 1 to your alphabet. This is how your mapping (with an array for example) could look like: 0 → a 1 → b ... 25 → z ... 52 → 0 61 → 9 With 2 → c and 1 → b, you will receive cb62 as the shortened URL. http://shor.ty/cb

如何将缩短的URL解析为初始ID

反过来就更容易了。你只需要在字母表中反向查找。

E9a62将被解析为“字母表中的第4、61和0个字母”。 E9a62 = [4,61,0] = 4×622 + 61×621 + 0×620 = 1915810 现在找到WHERE id = 19158的数据库记录并执行重定向。

示例实现(由评论者提供)

c++ Python Ruby Haskell c# CoffeeScript Perl

其他回答

我的方法是:获取数据库ID,然后对其进行Base36编码。我不会同时使用大写字母和小写字母,因为这会使通过电话传输这些url成为一场噩梦,但您当然可以轻松地将该功能扩展为62 en/解码器。

看看https://hashids.org/,它是开源的,有多种语言版本。

他们的页面概述了其他方法的一些陷阱。

// simple approach

$original_id = 56789;

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

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

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)
  }

}

您可以散列整个URL,但如果您只想缩短id,请按照marcel的建议进行。我写了这个Python实现:

https://gist.github.com/778542