Come posso creare un accorciatore di URL?


667

Voglio creare un servizio di accorciamento URL in cui è possibile scrivere un URL lungo in un campo di input e il servizio accorcia l'URL a " http://www.example.org/abcdef".

Invece di " abcdef" può esserci qualsiasi altra stringa contenente sei caratteri a-z, A-Z and 0-9. Ciò rende possibili 56 ~ 57 miliardi di stringhe.

Il mio approccio:

Ho una tabella di database con tre colonne:

  1. id, numero intero, incremento automatico
  2. long, string, l'URL lungo inserito dall'utente
  3. breve, stringa, l'URL abbreviato (o solo i sei caratteri)

Vorrei quindi inserire l'URL lungo nella tabella. Quindi selezionerei il valore di incremento automatico per " id" e ne creerei un hash. Questo hash dovrebbe quindi essere inserito come " short". Ma che tipo di hash dovrei costruire? Gli algoritmi di hash come MD5 creano stringhe troppo lunghe. Non uso questi algoritmi, credo. Funzionerà anche un algoritmo autocostruito.

La mia idea:

Per " http://www.google.de/" Ottengo l'id di incremento automatico 239472. Quindi faccio i seguenti passi:

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.

Ciò potrebbe essere ripetuto fino a quando il numero non sarà più divisibile. Pensi che questo sia un buon approccio? Hai un'idea migliore?

A causa del continuo interesse per questo argomento, ho pubblicato una soluzione efficiente per GitHub , con implementazioni per JavaScript , PHP , Python e Java . Aggiungi le tue soluzioni se ti piace :)


5
@gudge Il punto di queste funzioni è che hanno una funzione inversa. Ciò significa che puoi avere sia encode()e decode()funzioni. I passaggi sono quindi: (1) Salva URL nel database (2) Ottieni un ID riga univoco per quell'URL dal database (3) Converti l'ID intero in stringa breve con encode(), ad esempio 273984in f5a4(4) Usa la stringa breve (ad esempio f4a4) nel tuo URL condivisibili (5) Quando si riceve una richiesta per una stringa breve (ad es. 20a8), decodificare la stringa in un ID intero con decode()(6) Cercare l'URL nel database per un determinato ID. Per la conversione, utilizzare: github.com/delight-im/ShortURL
caw

@Marco, a che serve archiviare l'hash nel database?
Maksim Vi.

3
@MaksimVi. Se hai una funzione invertibile, non ce n'è. Se avessi una funzione hash unidirezionale, ce ne sarebbe una.
Caw

1
sarebbe sbagliato se usassimo un semplice algoritmo CRC32 per abbreviare un URL? Sebbene molto improbabile di una collisione (un output CRC32 di solito è lungo 8 caratteri e questo ci offre oltre 30 milioni di possibilità) Se un output CRC32 generato era già stato usato in precedenza e trovato nel database, potremmo salare l'URL lungo con un numero casuale fino a quando non troviamo un output CRC32 che è unico nel mio database. Quanto sarebbe brutto, diverso o brutto per una soluzione semplice?
Rakib,

Risposte:


816

Continuerei il tuo approccio "Converti il ​​numero in stringa". Tuttavia, ti renderai conto che l'algoritmo proposto fallisce se il tuo ID è un numero primo e maggiore di 52 .

Background teorico

È necessaria una funzione biiettiva f . Questo è necessario per trovare una funzione inversa g ('abc') = 123 per la tua funzione f (123) = 'abc' . Questo significa:

  • Non ci devono essere x1, x2 (con x1 ≠ x2) che renderà f (x1) = f (x2) ,
  • e per ogni y devi essere in grado di trovare una x in modo che f (x) = y .

Come convertire l'ID in un URL abbreviato

  1. Pensa a un alfabeto che vogliamo usare. Nel tuo caso, quello è [a-zA-Z0-9]. Contiene 62 lettere .
  2. Prendi una chiave numerica univoca generata automaticamente (ad esempio l'auto-incremento iddi una tabella MySQL).

    Per questo esempio, userò 125 10 (125 con una base di 10).

  3. Ora devi convertire 125 10 in X 62 (base 62).

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

    Ciò richiede l'uso della divisione intera e del modulo. Un esempio di pseudo-codice:

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

    Ora mappa gli indici 2 e 1 sul tuo alfabeto. Ecco come potrebbe apparire la tua mappatura (ad esempio con un array):

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

    Con 2 → c e 1 → b, riceverai cb 62 come URL abbreviato.

    http://shor.ty/cb
    

Come risolvere un URL abbreviato nell'ID iniziale

Il contrario è ancora più semplice. Fai solo una ricerca inversa nel tuo alfabeto.

  1. e9a 62 sarà risolto in "4a, 61a e 0a lettera dell'alfabeto".

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

  2. Ora trova il tuo record di database con WHERE id = 19158ed esegui il reindirizzamento.

Implementazioni di esempio (fornite dai commentatori)


18
Non dimenticare di disinfettare gli URL per codice javascript dannoso! Ricorda che javascript può essere codificato in base64 in un URL, quindi solo cercare 'javascript' non è abbastanza buono. J
Bjorn

3
Una funzione deve essere biiettiva (iniettiva e suriettiva) per avere un inverso.
Gumbo,

57
Spunti di riflessione, potrebbe essere utile aggiungere un checksum di due caratteri all'URL. Ciò impedirebbe l'iterazione diretta di tutti gli URL nel tuo sistema. Qualcosa di semplice come f (checksum (id)% (62 ^ 2)) + f (id) = url_id
koblas

6
Per quanto riguarda la sanificazione degli URL, uno dei problemi che dovrai affrontare sono gli spammer che usano il tuo servizio per mascherare i loro URL per evitare i filtri antispam. Devi limitare il servizio a noti attori validi o applicare il filtro antispam ai lunghi URL. Altrimenti verrai maltrattato dagli spammer.
Edward Falk,

74
Base62 potrebbe essere una scelta sbagliata perché ha il potenziale per generare parole f * (ad esempio, 3792586=='F_ck'con u al posto di _). Escluderei alcuni personaggi come u / U per minimizzare questo.
Paulo Scardine,

56

Perché dovresti usare un hash?

Puoi semplicemente utilizzare una semplice traduzione del valore di autoincremento in un valore alfanumerico. Puoi farlo facilmente usando una conversione di base. Supponi che lo spazio dei caratteri (AZ, az, 0-9, ecc.) Abbia 40 caratteri, converti l'id in un numero base-40 e usa i caratteri come cifre.


13
a parte il fatto che AZ, az e 0-9 = 62 caratteri, non 40, hai ragione.
Evan Teran,

Grazie! Dovrei usare l'alfabeto base-62 allora? en.wikipedia.org/wiki/Base_62 Ma come posso convertire gli ID in un numero base-62?
Caw,

Utilizzando un algoritmo di conversione base ofcourse - it.wikipedia.org/wiki/Base_conversion#Change_of_radix
shoosh

2
Per quanto riguarda "Perché dovresti voler utilizzare un hash?", Una conversione di base basata sull'incremento automatico creerà URL sequenziali, quindi dovresti essere a tuo agio con le persone in grado di "sfogliare" gli URL abbreviati di altre persone, destra?
Andrew Coleson,

2
con risorse e tempo sufficienti puoi "sfogliare" tutti gli URL di qualsiasi servizio di accorciamento degli 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;
    }   
}

Mi piace davvero l'idea, l'unico problema che ho è che continuo a far uscire la variabile num dalla funzione di decodifica (anche per molto tempo), hai idea di come farlo funzionare? o è solo teorico?
user1322801,

@ user1322801: Presumibilmente stai cercando di decodificare qualcosa di molto più grande di quello che la funzione di codifica può effettivamente gestire. Potresti ottenere qualche chilometraggio in più se hai convertito tutti gli "ints" in BigInteger, ma a meno che tu non abbia indici> 9223372036854775807, probabilmente dovrebbe essere abbastanza lungo.
biggusjimmus,

2
Posso sapere qual è l'importanza dell'inversione? cioè sb.reverse (). toString ();
decodificatore dotNet

Che 62 ^ 62 = 1,7 trilioni?
Noah Tony,

33

Non è una risposta alla tua domanda, ma non utilizzerei URL abbreviati con distinzione tra maiuscole e minuscole. Sono difficili da ricordare, di solito illeggibili (molti caratteri rendono 1 e 1, 0 e O e altri caratteri molto simili che sono quasi impossibili da dire la differenza) e assolutamente inclini all'errore. Prova a usare solo lettere minuscole o maiuscole.

Inoltre, prova ad avere un formato in cui mescoli numeri e caratteri in una forma predefinita. Ci sono studi che dimostrano che le persone tendono a ricordare una forma meglio di altre (pensa ai numeri di telefono, in cui i numeri sono raggruppati in una forma specifica). Prova qualcosa come num-char-char-num-char-char. So che questo abbasserà le combinazioni, soprattutto se non hai maiuscole e minuscole, ma sarebbe più utilizzabile e quindi utile.


2
Grazie, ottima idea. Non ci ho ancora pensato. È chiaro che dipende dal tipo di utilizzo che abbia senso o meno.
Caw

19
Non sarà un problema se le persone stanno semplicemente copiando e incollando gli URL brevi.
Edward Falk,

2
Lo scopo degli URL brevi non è di essere memorabile o facile da parlare. È solo fare clic o copiare / incollare.
Hugo Nogueira,

Sì, ho pensato che l'URL breve fosse solo per le persone che lo elencano o lo inviano tramite e-mail e quindi è breve e non occuperà 200 caratteri come fanno alcuni URL, quindi il caso non è un problema
polarità

29

Il mio approccio: prendere l'ID database, quindi Base36 codificarlo . NON userei le lettere maiuscole e minuscole, perché ciò rende un incubo la trasmissione di quegli URL al telefono, ma ovviamente si potrebbe facilmente estendere la funzione a 62 en / decoder base.


Grazie hai ragione. Sia che tu abbia 2.176.782.336 possibilità o 56.800.235.584, è lo stesso: entrambi saranno sufficienti. Quindi userò la codifica base 36.
Caw

Può essere ovvio, ma ecco un codice PHP a cui fa riferimento wikipedia per eseguire la codifica base64 in php tonymarston.net/php-mysql/converter.html
Ryan White,

8

Ecco la mia classe 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;
    }
}

6

Una soluzione Node.js e MongoDB

Poiché conosciamo il formato utilizzato da MongoDB per creare un nuovo ObjectId con 12 byte.

  • un valore di 4 byte che rappresenta i secondi dall'epoca di Unix,
  • un identificatore macchina a 3 byte,
  • un ID di processo a 2 byte
  • un contatore a 3 byte (nella tua macchina), che inizia con un valore casuale.

Esempio (scelgo una sequenza casuale) a1b2c3d4e5f6g7h8i9j1k2l3

  • a1b2c3d4 rappresenta i secondi dall'epoca di Unix,
  • 4e5f6g7 rappresenta l'identificatore della macchina,
  • h8i9 rappresenta l'ID del processo
  • j1k2l3 rappresenta il contatore, a partire da un valore casuale.

Poiché il contatore sarà unico se stiamo memorizzando i dati nella stessa macchina, possiamo ottenerli senza dubbio che saranno duplicati.

Quindi l'URL breve sarà il contatore ed ecco uno snippet di codice che presuppone che il tuo server funzioni correttamente.

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

1
In che modo un RDBMS sarebbe migliore di un archivio no-sql / key-value?
kjs3

@ kjs3 sì, hai ragione, poiché non ci sono relazioni con altre tabelle, non è necessario un RDBMS e un archivio di valori chiave sarà più veloce.
Firas Omrane,

4

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

4

È possibile eseguire l'hashing dell'intero URL, ma se si desidera solo abbreviare l'id, fare come suggerito da Marcel. Ho scritto questa implementazione di Python:

https://gist.github.com/778542


4

Continuo a incrementare una sequenza di numeri interi per dominio nel database e utilizzo Hashids per codificare il numero intero in un percorso URL.

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

Ho eseguito una sceneggiatura per vedere quanto tempo impiega a esaurire la lunghezza del personaggio. Per sei caratteri può fare 164,916,224collegamenti e poi arriva fino a sette caratteri. Usa bitly sette caratteri. Sotto i cinque personaggi mi sembra strano.

Gli hashids possono decodificare il percorso dell'URL in un numero intero ma una soluzione più semplice consiste nell'utilizzare l'intero collegamento breve sho.rt/ka8ds3come chiave primaria.

Ecco il concetto completo:

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

Ecco la mia versione per chiunque ne abbia bisogno.


1

Perché non tradurre semplicemente il tuo ID in una stringa? Hai solo bisogno di una funzione che associ una cifra tra, diciamo, 0 e 61 a una singola lettera (maiuscola / minuscola) o cifra. Quindi applica questo per creare, diciamo, codici di 4 lettere e avrai coperto 14,7 milioni di URL.


+1 per il pensiero semplicistico. E 'davvero così semplice. Ho appena pubblicato una risposta che sta facendo esattamente questo. Ho del codice di produzione che interroga il database per assicurarmi che non ci siano stringhe duplicate e che tutto sia unico.
Andrew Reese,

1

Ecco una decente funzione di codifica URL per 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

Non so se qualcuno lo troverà utile - è più un metodo "hack n slash", ma è semplice e funziona bene se vuoi solo caratteri specifici.

$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

Hai omesso O, 0 e io di proposito?

Ho appena creato una classe PHP basata sulla soluzione di Ryan.

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

Sì. Hai visto il commento appena sotto la dichiarazione di classe?
Svetoslav Marinov,

1

Dai un'occhiata a https://hashids.org/ è open source e in molte lingue.

La loro pagina delinea alcune delle insidie ​​di altri approcci.


0

Questo è quello che uso:

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

È molto veloce e può richiedere numeri interi lunghi.


0

Per un progetto simile, per ottenere una nuova chiave, creo una funzione wrapper attorno a un generatore di stringhe casuale che chiama il generatore fino a quando non ottengo una stringa che non è già stata utilizzata nella mia tabella hash. Questo metodo rallenterà quando lo spazio dei nomi inizierà a riempirsi, ma come hai detto, anche con solo 6 caratteri, hai un sacco di spazio dei nomi con cui lavorare.


Questo approccio ha funzionato per te nel lungo periodo?
Chris,

Ad essere sincero, non ho idea di quale progetto mi riferissi lì :-P
Joel Berger,

0

Ho una variante del problema, in quanto immagazzino pagine Web di molti autori diversi e devo impedire la scoperta di pagine tramite congetture. Quindi i miei brevi URL aggiungono un paio di cifre extra alla stringa Base-62 per il numero di pagina. Queste cifre extra sono generate dalle informazioni nel record della pagina stessa e garantiscono che siano validi solo 1 URL su 3844 (supponendo Base-62 a 2 cifre). Puoi vedere una descrizione dello schema su http://mgscan.com/MBWL .


0

Ottima risposta, ho creato un'implementazione Golang del 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
}

Ospitato su 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

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

Esempio di test con il test 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

Funzione basata sulla classe 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

Ecco un'implementazione di Node.js che è probabile che bit.ly. genera una stringa di sette caratteri altamente casuale.

Utilizza la crittografia Node.js per generare un set di caratteri 25 altamente casuale anziché selezionare casualmente sette caratteri.

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

Cosa intendi con "bit.ly." ?
Peter Mortensen,

0

La mia versione di 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")

0

Per una soluzione Node.js / JavaScript di qualità, consultare il modulo ID-shortener , che è stato accuratamente testato ed è stato utilizzato in produzione per mesi.

Fornisce un accorciatore id / URL efficiente supportato dall'archiviazione innestabile di default su Redis e puoi persino personalizzare il tuo set di caratteri ID breve e se l'accorciamento è idempotente o meno . Questa è una distinzione importante che non tutti gli accorciatori di URL tengono conto.

In relazione ad altre risposte qui, questo modulo implementa l'eccellente risposta accettata da Marcel Jackwerth sopra.

Il nucleo della soluzione è fornito dal seguente frammento di 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

0

Perché non generare semplicemente una stringa casuale e aggiungerla all'URL di base? Questa è una versione molto semplificata di farlo in 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);
}

Quindi aggiungi semplicemente l'appendi la stringa casuale a baseURL:

string tinyURL = baseUrl + RandomString(5);

Ricorda che questa è una versione molto semplificata ed è possibile che il metodo RandomString possa creare stringhe duplicate. In produzione, tieni in considerazione le stringhe duplicate per assicurarti di avere sempre un URL univoco. Ho del codice che tiene conto delle stringhe duplicate eseguendo una query su una tabella di database che potrei condividere se qualcuno fosse interessato.


0

Questo è il mio pensiero iniziale, e si può fare più pensiero, oppure si può fare qualche simulazione per vedere se funziona bene o se è necessario qualche miglioramento:

La mia risposta è ricordare l'URL lungo nel database e utilizzare l'ID 0per 9999999999999999(o per quanto sia necessario il numero elevato).

Ma l'ID 0 a 9999999999999999può essere un problema, perché

  1. può essere più breve se utilizziamo esadecimale, o anche base62 o base64. (base64 proprio come YouTube usando A- Z a- z 0- 9 _e -)
  2. se aumenta da 0a 9999999999999999in modo uniforme, quindi gli hacker possono visitare in questo ordine e sapere che cosa URL persone stanno inviando l'un l'altro, in modo che possa essere un problema di privacy

Possiamo farlo:

  1. avere un server assegnato 0a 999un server, Server A, quindi ora il Server A ha 1000 di tali ID. Quindi, se ci sono 20 o 200 server che desiderano costantemente nuovi ID, non deve continuare a chiedere ogni nuovo ID, ma piuttosto chiedere una volta per 1000 ID
  2. per l'ID 1, ad esempio, invertire i bit. Così 000...00000001diventa 10000...000, in modo che quando convertito in base64, aumenterà gli ID in modo non uniforme ogni volta.
  3. usa XOR per capovolgere i bit per gli ID finali. Ad esempio, XOR con 0xD5AA96...2373(come una chiave segreta) e alcuni bit verranno capovolti. (ogni volta che la chiave segreta ha 1 bit attivato, capovolge il bit dell'ID). Ciò renderà gli ID ancora più difficili da indovinare e apparire più casuali

Seguendo questo schema, il singolo server che alloca gli ID può formare gli ID, così come i 20 o 200 server che richiedono l'assegnazione degli ID. Il server di allocazione deve utilizzare un blocco / semaforo per impedire a due server richiedenti di ottenere lo stesso batch (o se accetta una connessione alla volta, questo risolve già il problema). Quindi non vogliamo che la linea (coda) sia troppo lunga per aspettare un'assegnazione. Ecco perché l'allocazione di 1000 o 10000 alla volta può risolvere il problema.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.