Wie erstelle ich einen URL-Shortener?


667

Ich möchte einen URL-Shortener-Service erstellen, bei dem Sie eine lange URL in ein Eingabefeld schreiben können und der Service die URL auf " http://www.example.org/abcdef" verkürzt .

Anstelle von " abcdef" kann es auch eine andere Zeichenfolge mit sechs Zeichen geben a-z, A-Z and 0-9. Das macht 56 ​​bis 57 Milliarden mögliche Saiten möglich.

Mein Ansatz:

Ich habe eine Datenbanktabelle mit drei Spalten:

  1. ID, Ganzzahl, automatische Inkrementierung
  2. long, string, die lange URL, die der Benutzer eingegeben hat
  3. kurz, Zeichenfolge, die verkürzte URL (oder nur die sechs Zeichen)

Ich würde dann die lange URL in die Tabelle einfügen. Dann würde ich den Auto-Inkrement-Wert für " id" auswählen und einen Hash davon erstellen. Dieser Hash sollte dann als " short" eingefügt werden . Aber welche Art von Hash soll ich bauen? Hash-Algorithmen wie MD5 erzeugen zu lange Zeichenfolgen. Ich denke, ich benutze diese Algorithmen nicht. Ein selbst erstellter Algorithmus funktioniert ebenfalls.

Meine Idee:

Für " http://www.google.de/" erhalte ich die Auto-Inkrement-ID 239472. Dann mache ich folgende Schritte:

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.

Das könnte wiederholt werden, bis die Zahl nicht mehr teilbar ist. Halten Sie dies für einen guten Ansatz? Hast du eine bessere Idee?

Aufgrund des anhaltenden Interesses an diesem Thema habe ich eine effiziente Lösung für GitHub mit Implementierungen für JavaScript , PHP , Python und Java veröffentlicht . Fügen Sie Ihre Lösungen hinzu, wenn Sie möchten :)


5
@gudge Der Punkt dieser Funktionen ist, dass sie eine Umkehrfunktion haben. Dies bedeutet, dass Sie beide encode()und decode()Funktionen haben können. Die Schritte sind daher: (1) Speichern der URL in der Datenbank (2) Abrufen der eindeutigen Zeilen-ID für diese URL aus der Datenbank (3) Konvertieren der Ganzzahl-ID in eine kurze Zeichenfolge mit encode()z. B. 273984in f5a4(4) Verwenden Sie die kurze Zeichenfolge (z. B. f4a4) in Ihrer gemeinsam nutzbare URLs (5) Wenn Sie eine Anforderung für eine kurze Zeichenfolge (z. B. 20a8) erhalten, dekodieren Sie die Zeichenfolge in eine ganzzahlige ID mit decode()(6) Suchen Sie die URL in der Datenbank nach der angegebenen ID. Verwenden Sie für die Konvertierung: github.com/delight-im/ShortURL
caw

@Marco, was bringt es, den Hash in der Datenbank zu speichern?
Maksim Vi.

3
@MaksimVi. Wenn Sie eine invertierbare Funktion haben, gibt es keine. Wenn Sie eine Einweg-Hash-Funktion hätten, gäbe es eine.
Caw

1
Wäre es falsch, wenn wir einen einfachen CRC32-Algorithmus verwenden würden, um eine URL zu verkürzen? Obwohl eine Kollision sehr unwahrscheinlich ist (eine CRC32-Ausgabe ist normalerweise 8 Zeichen lang und bietet mehr als 30 Millionen Möglichkeiten). Wenn eine generierte CRC32-Ausgabe bereits zuvor verwendet und in der Datenbank gefunden wurde, können wir die lange URL mit einer Zufallszahl salzen bis wir eine CRC32-Ausgabe finden, die in meiner Datenbank eindeutig ist. Wie schlimm oder anders oder hässlich wäre das für eine einfache Lösung?
Rakib

Antworten:


815

Ich würde Ihren Ansatz "Nummer in Zeichenfolge konvertieren" fortsetzen. Sie werden jedoch feststellen, dass Ihr vorgeschlagener Algorithmus fehlschlägt, wenn Ihre ID eine Primzahl ist und größer als 52 ist .

Theoretischer Hintergrund

Sie benötigen eine bijektive Funktion f . Dies ist notwendig, damit Sie eine Umkehrfunktion g ('abc') = 123 für Ihre Funktion f (123) = 'abc' finden können. Das heisst:

  • Es darf kein x1, x2 (mit x1 ≠ x2) geben , das f (x1) = f (x2) ergibt .
  • und für jedes y müssen Sie in der Lage sein, ein x zu finden, so dass f (x) = y ist .

So konvertieren Sie die ID in eine verkürzte URL

  1. Stellen Sie sich ein Alphabet vor, das wir verwenden möchten. In deinem Fall ist das so [a-zA-Z0-9]. Es enthält 62 Buchstaben .
  2. Nehmen Sie einen automatisch generierten, eindeutigen numerischen Schlüssel ( idz. B. den automatisch inkrementierten einer MySQL-Tabelle).

    In diesem Beispiel verwende ich 125 10 (125 mit einer Basis von 10).

  3. Jetzt müssen Sie 125 10 in X 62 (Basis 62) konvertieren .

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

    Dies erfordert die Verwendung von Integer Division und Modulo. Ein Pseudocode-Beispiel:

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

    Ordnen Sie nun die Indizes 2 und 1 Ihrem Alphabet zu. So könnte Ihre Zuordnung (zum Beispiel mit einem Array) aussehen:

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

    Mit 2 → c und 1 → b erhalten Sie cb 62 als verkürzte URL.

    http://shor.ty/cb
    

So lösen Sie eine verkürzte URL in die ursprüngliche ID auf

Das Gegenteil ist noch einfacher. Sie machen einfach eine umgekehrte Suche in Ihrem Alphabet.

  1. e9a 62 wird in "4., 61. und 0. Buchstabe im Alphabet" aufgelöst.

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

  2. Suchen Sie nun Ihren Datenbankeintrag mit WHERE id = 19158und führen Sie die Umleitung durch.

Beispielimplementierungen (von Kommentatoren bereitgestellt)


18
Vergessen Sie nicht, die URLs auf schädlichen Javascript-Code zu bereinigen! Denken Sie daran, dass Javascript in einer URL base64-codiert werden kann, sodass die Suche nach 'Javascript' nicht gut genug ist.j
Björn

3
Eine Funktion muss bijektiv (injektiv und surjektiv) sein, um eine Inverse zu haben.
Gumbo

57
Denkanstöße, es könnte nützlich sein, der URL eine Prüfsumme mit zwei Zeichen hinzuzufügen. Dies würde eine direkte Iteration aller URLs in Ihrem System verhindern. Etwas Einfaches wie f (Prüfsumme (id)% (62 ^ 2)) + f (id) = url_id
koblas

6
Bei der Bereinigung der URLs besteht eines der Probleme darin, dass Spammer Ihren Dienst verwenden, um ihre URLs zu maskieren und Spamfilter zu vermeiden. Sie müssen entweder den Service auf bekannte gute Schauspieler beschränken oder die Spam-Filterung auf die langen URLs anwenden. Andernfalls werden Sie von Spammern missbraucht.
Edward Falk

74
Base62 kann eine schlechte Wahl sein, da es das Potenzial hat, f * -Wörter zu generieren (z. B. 3792586=='F_ck'mit u anstelle von _). Ich würde einige Zeichen wie u / U ausschließen, um dies zu minimieren.
Paulo Scardine

56

Warum sollten Sie einen Hash verwenden wollen?

Sie können einfach eine einfache Übersetzung Ihres Auto-Inkrement-Werts in einen alphanumerischen Wert verwenden. Sie können dies einfach tun, indem Sie eine Basiskonvertierung verwenden. Angenommen, Ihr Zeichenraum (AZ, az, 0-9 usw.) besteht aus 40 Zeichen, konvertieren Sie die ID in eine Basis-40-Zahl und verwenden Sie die Zeichen als Ziffern.


13
Abgesehen von der Tatsache, dass AZ, az und 0-9 = 62 Zeichen, nicht 40, sind Sie genau richtig.
Evan Teran

Vielen Dank! Soll ich dann das Base-62-Alphabet verwenden? en.wikipedia.org/wiki/Base_62 Aber wie kann ich die IDs in eine Basis-62-Nummer konvertieren?
Caw

Natürlich mit einem Basis-Konvertierungsalgorithmus - en.wikipedia.org/wiki/Base_conversion#Change_of_radix
shoosh

2
In Bezug auf "Warum sollten Sie einen Hash verwenden?" Werden bei einer auf der automatischen Inkrementierung basierenden Basiskonvertierung sequenzielle URLs erstellt. Sie müssen sich also damit vertraut machen, dass Benutzer die verkürzten URLs anderer Personen "durchsuchen" können. Recht?
Andrew Coleson

2
Mit genügend Ressourcen und Zeit können Sie alle URLs eines beliebigen URL-Verkürzungsdienstes "durchsuchen".
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;
    }   
}

Ich mag die Idee wirklich, das einzige Problem, das ich damit habe, ist, dass ich die num-Variable in der Dekodierungsfunktion (auch für lange Zeit) immer wieder außerhalb der Grenzen habe. Haben Sie eine Idee, wie sie funktioniert? oder ist es nur theoretisch?
user1322801

@ user1322801: Vermutlich versuchen Sie, etwas zu dekodieren, das weitaus größer war als das, was die Codierungsfunktion tatsächlich verarbeiten kann. Sie könnten etwas mehr Kilometer herausholen, wenn Sie alle "Ints" in BigInteger konvertieren würden, aber wenn Sie nicht über> 9223372036854775807 Indizes verfügen, sollte Long wahrscheinlich ausreichen.
Biggusjimmus

2
Darf ich wissen, wie wichtig das Umkehren ist? dh sb.reverse (). toString ();
dotNet Decoder

Ist das 62 ^ 62 = 1,7 Billionen?
Noah Tony

33

Keine Antwort auf Ihre Frage, aber ich würde keine verkürzten URLs verwenden, bei denen zwischen Groß- und Kleinschreibung unterschieden wird. Sie sind schwer zu merken, normalerweise unlesbar (viele Schriftarten machen 1 und 1, 0 und O und andere Zeichen sehr ähnlich, so dass es nahezu unmöglich ist, den Unterschied zu erkennen) und geradezu fehleranfällig. Versuchen Sie, nur Klein- oder Großbuchstaben zu verwenden.

Versuchen Sie auch, ein Format zu haben, in dem Sie die Zahlen und Zeichen in einer vordefinierten Form mischen. Es gibt Studien, die zeigen, dass Menschen sich eine Form besser merken als andere (denken Sie an Telefonnummern, bei denen die Nummern in einer bestimmten Form gruppiert sind). Versuchen Sie etwas wie num-char-char-num-char-char. Ich weiß, dass dies die Kombinationen verringert, insbesondere wenn Sie keine Groß- und Kleinschreibung haben, aber es wäre benutzerfreundlicher und daher nützlich.


2
Danke, sehr gute Idee. Darüber habe ich noch nicht nachgedacht. Es ist klar, dass es von der Art der Verwendung abhängt, ob dies sinnvoll ist oder nicht.
Caw

19
Es ist kein Problem, wenn die Leute die kurzen URLs streng kopieren und einfügen.
Edward Falk

2
Der Zweck von kurzen URLs ist nicht, einprägsam oder leicht zu sprechen zu sein. Ist nur klicken oder kopieren / einfügen.
Hugo Nogueira

Ja, ich dachte, die kurze URL ist nur für Leute gedacht, die sie
auflisten

29

Mein Ansatz: Nehmen Sie die Datenbank-ID und codieren Sie sie dann von Base36 . Ich würde NICHT sowohl Groß- als auch Kleinbuchstaben verwenden, da dies das Übertragen dieser URLs über das Telefon zu einem Albtraum macht, aber Sie könnten die Funktion natürlich leicht zu einem Basis-62-En / Decoder erweitern.


Danke, du hast recht. Ob Sie 2.176.782.336 Möglichkeiten oder 56.800.235.584 haben, es ist das gleiche: Beides wird ausreichen. Also werde ich Base 36-Codierung verwenden.
Caw

Es mag offensichtlich sein, aber hier ist ein PHP-Code, auf den in Wikipedia verwiesen wird, um die Base64-Codierung in php tonymarston.net/php-mysql/converter.html durchzuführen
Ryan White

8

Hier ist meine PHP 5 Klasse.

<?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;
    }
}

6

Eine Node.js- und MongoDB-Lösung

Bearbeiten: Es ist besser, eine relationale Datenbank zum Speichern solcher Daten (short_url und true url) zu verwenden und nicht MongoDB.

Da wir das Format kennen, mit dem MongoDB eine neue ObjectId mit 12 Bytes erstellt.

  • ein 4-Byte-Wert, der die Sekunden seit der Unix-Epoche darstellt,
  • eine 3-Byte-Maschinenkennung,
  • eine 2-Byte-Prozess-ID
  • Ein 3-Byte-Zähler (in Ihrem Computer), der mit einem zufälligen Wert beginnt.

Beispiel (ich wähle eine zufällige Sequenz) a1b2c3d4e5f6g7h8i9j1k2l3

  • a1b2c3d4 repräsentiert die Sekunden seit der Unix-Epoche.
  • 4e5f6g7 repräsentiert die Maschinenkennung,
  • h8i9 repräsentiert die Prozess-ID
  • j1k2l3 repräsentiert den Zähler, beginnend mit einem zufälligen Wert.

Da der Zähler eindeutig ist, wenn wir die Daten auf demselben Computer speichern, können wir sie ohne Zweifel abrufen, dass sie doppelt vorhanden sind.

Die kurze URL ist also der Zähler. Hier ist ein Codeausschnitt, der davon ausgeht, dass Ihr Server ordnungsgemäß ausgeführt wird.

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

4

C # -Version:

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

4

Sie könnten die gesamte URL hashen, aber wenn Sie nur die ID kürzen möchten, tun Sie, was marcel vorgeschlagen hat. Ich habe diese Python-Implementierung geschrieben:

https://gist.github.com/778542


4

Ich erhöhe ständig eine Ganzzahlsequenz pro Domäne in der Datenbank und verwende Hashids , um die Ganzzahl in einen URL-Pfad zu codieren.

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

Ich habe ein Skript ausgeführt, um zu sehen, wie lange es dauert, bis die Zeichenlänge erschöpft ist. Für sechs Zeichen kann es 164,916,224Links erstellen und dann bis zu sieben Zeichen. Bitly verwendet sieben Zeichen. Unter fünf Zeichen sieht für mich komisch aus.

Hashids können den URL-Pfad zurück in eine Ganzzahl dekodieren. Eine einfachere Lösung besteht jedoch darin, den gesamten Kurzlinksho.rt/ka8ds3 als Primärschlüssel zu verwenden.

Hier ist das vollständige Konzept:

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


3
// simple approach

$original_id = 56789;

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

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

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

Hier ist meine Version für jeden, der sie braucht.


1

Warum übersetzen Sie Ihre ID nicht einfach in eine Zeichenfolge? Sie benötigen lediglich eine Funktion, die eine Ziffer zwischen beispielsweise 0 und 61 einem einzelnen Buchstaben (Groß- / Kleinbuchstaben) oder einer Ziffer zuordnet. Wenden Sie dies dann an, um beispielsweise 4-Buchstaben-Codes zu erstellen, und Sie haben 14,7 Millionen URLs abgedeckt.


+1 für das vereinfachte Denken. So einfach ist das wirklich. Ich habe gerade eine Antwort gepostet, die genau das tut. Ich habe einen Produktionscode, der die Datenbank abfragt, um sicherzustellen, dass keine doppelten Zeichenfolgen vorhanden sind und alles eindeutig ist.
Andrew Reese

1

Hier ist eine anständige URL-Codierungsfunktion für 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

Ich weiß nicht, ob jemand dies nützlich finden wird - es ist eher eine "Hack n Slash" -Methode, aber einfach und funktioniert gut, wenn Sie nur bestimmte Zeichen möchten.

$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

Haben Sie absichtlich O, 0 und i weggelassen?

Ich habe gerade eine PHP-Klasse basierend auf Ryans Lösung erstellt.

<?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;
        }
    }
?>

Ja. Haben Sie den Kommentar direkt unter der Klassendeklaration gesehen?
Svetoslav Marinov


0

Das benutze ich:

# 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))])

Es ist sehr schnell und kann lange ganze Zahlen dauern.


0

Um für ein ähnliches Projekt einen neuen Schlüssel zu erhalten, erstelle ich eine Wrapper-Funktion um einen Zufallszeichenfolgengenerator , der den Generator aufruft, bis ich eine Zeichenfolge erhalte, die noch nicht in meiner Hashtabelle verwendet wurde. Diese Methode wird langsamer, sobald Ihr Namensraum voll wird, aber wie Sie bereits gesagt haben, haben Sie selbst mit nur 6 Zeichen genügend Namespace, mit dem Sie arbeiten können.


Hat sich dieser Ansatz für Sie langfristig bewährt?
Chris

Um ehrlich zu sein, habe ich keine Ahnung, auf welches Projekt ich mich dort bezog :-P
Joel Berger

0

Ich habe eine Variante des Problems, indem ich Webseiten von vielen verschiedenen Autoren speichere und verhindern muss, dass Seiten durch Vermutungen entdeckt werden. Meine kurzen URLs fügen der Base-62-Zeichenfolge für die Seitenzahl ein paar zusätzliche Ziffern hinzu. Diese zusätzlichen Ziffern werden aus Informationen im Seitendatensatz selbst generiert und stellen sicher, dass nur 1 von 3844 URLs gültig sind (unter der Annahme einer zweistelligen Basis-62). Eine Gliederungsbeschreibung finden Sie unter http://mgscan.com/MBWL .


0

Sehr gute Antwort, ich habe eine Golang-Implementierung des bjf erstellt:

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
}

Gehostet bei 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

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

Testbeispiel mit Scala-Test:

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

Funktion basierend auf der Xeoncross-Klasse

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

Hier ist eine Node.js-Implementierung, die wahrscheinlich bit.ly ist. Generieren Sie eine sehr zufällige Zeichenfolge mit sieben Zeichen.

Es verwendet Node.js Krypto, um einen sehr zufälligen Zeichensatz von 25 zu generieren, anstatt zufällig sieben Zeichen auszuwählen.

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

Was meinst du mit "bit.ly"? ?
Peter Mortensen

0

Meine Python 3-Version

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

0

Eine hochwertige Node.js / JavaScript-Lösung finden Sie im ID-Shortener Modul, das gründlich getestet wurde und seit Monaten in der Produktion verwendet wird.

Es bietet einen effizienten ID / URL-Shortener, der durch einen steckbaren Speicher unterstützt wird, der standardmäßig auf Redis eingestellt ist , und Sie können sogar Ihren kurzen ID-Zeichensatz anpassen und festlegen, ob die Kürzung idempotent ist oder nicht . Dies ist eine wichtige Unterscheidung, die nicht alle URL-Shortender berücksichtigen.

In Bezug auf andere Antworten hier implementiert dieses Modul die oben akzeptierte ausgezeichnete Antwort von Marcel Jackwerth.

Den Kern der Lösung bildet das folgende Redis Lua- Snippet :

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

0

Warum nicht einfach eine zufällige Zeichenfolge generieren und an die Basis-URL anhängen? Dies ist eine sehr vereinfachte Version von 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);
}

Fügen Sie dann einfach die zufällige Zeichenfolge an die baseURL an:

string tinyURL = baseUrl + RandomString(5);

Denken Sie daran, dass dies eine sehr vereinfachte Version ist und dass die RandomString-Methode möglicherweise doppelte Zeichenfolgen erstellt. In der Produktion sollten Sie doppelte Zeichenfolgen berücksichtigen, um sicherzustellen, dass Sie immer eine eindeutige URL haben. Ich habe einen Code, der doppelte Zeichenfolgen berücksichtigt, indem er eine Datenbanktabelle abfragt, die ich bei Interesse freigeben kann.


0

Dies sind meine ersten Gedanken, und es kann mehr nachgedacht werden, oder es kann eine Simulation durchgeführt werden, um festzustellen, ob es gut funktioniert oder Verbesserungen erforderlich sind:

Meine Antwort ist, sich die lange URL in der Datenbank zu merken und die ID 0zu verwenden 9999999999999999(oder wie groß die Anzahl auch sein mag).

Aber die ID 0 bis 9999999999999999kann ein Problem sein, weil

  1. Es kann kürzer sein, wenn wir hexadezimal oder sogar base62 oder base64 verwenden. (base64 genau wie YouTube mit A- Z a- z 0- 9 _und -)
  2. wenn es aus erhöht 0zu 9999999999999999gleichmäßig, dann kann Hacker sie in dieser Reihenfolge besucht und weiß , was URLs Menschen sich senden, so dass es ein Datenschutzproblem sein kann

Wir können das schaffen:

  1. haben einen Server zuteilen , 0um 999auf einen Server, Server A, so dass nun Server Ein 1000 solchen IDs aufweist. Wenn also 20 oder 200 Server ständig neue IDs wünschen, muss sie nicht ständig nach jeder neuen ID fragen, sondern einmal nach 1000 IDs
  2. für die ID 1 vertauschen Sie beispielsweise die Bits. So 000...00000001wird 10000...000, so dass bei der Konvertierung in base64 die IDs jedes Mal ungleichmäßig erhöht werden.
  3. Verwenden Sie XOR, um die Bits für die endgültigen IDs umzudrehen. Zum Beispiel XOR mit 0xD5AA96...2373(wie ein geheimer Schlüssel) und einige Bits werden umgedreht. (Immer wenn der geheime Schlüssel das 1-Bit aktiviert hat, wird das Bit der ID umgedreht). Dadurch werden die IDs noch schwieriger zu erraten und erscheinen zufälliger

Nach diesem Schema können der einzelne Server, der die IDs zuweist, die IDs bilden, ebenso wie die 20 oder 200 Server, die die Zuweisung von IDs anfordern. Der zuweisende Server muss eine Sperre / ein Semaphor verwenden, um zu verhindern, dass zwei anfordernde Server denselben Stapel erhalten (oder wenn er jeweils eine Verbindung akzeptiert, ist das Problem bereits gelöst). Wir möchten also nicht, dass die Warteschlange zu lang ist, um auf eine Zuordnung zu warten. Deshalb kann das Problem durch Zuweisen von 1000 oder 10000 gleichzeitig behoben werden.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.