URL 단축기를 어떻게 만듭니 까?


667

입력 필드에 긴 URL을 쓸 수 있고 URL이 " http://www.example.org/abcdef"(으)로 단축되는 URL 단축기 서비스를 만들고 싶습니다 .

" abcdef" 대신 6자를 포함하는 다른 문자열이있을 수 있습니다 a-z, A-Z and 0-9. 이는 56 ~ 57 십억 개의 문자열을 가능하게합니다.

내 접근 방식 :

세 개의 열이있는 데이터베이스 테이블이 있습니다.

  1. id, 정수, 자동 증가
  2. long, string, 사용자가 입력 한 긴 URL
  3. short, string, 단축 URL (또는 6 자)

그런 다음 긴 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.

숫자를 더 이상 나눌 수 없을 때까지 반복 할 수 있습니다. 이것이 좋은 접근 방법이라고 생각하십니까? 더 좋은 아이디어가 있습니까?

이 주제에 대한 지속적인 관심으로 인해 JavaScript , PHP , PythonJava 를 구현 하는 GitHub에 대한 효율적인 솔루션을 발표했습니다 . 원하는 경우 솔루션을 추가하십시오 :)


5
@gudge이 함수의 요점은 역함수를 가지고 있다는 것입니다. 이것은 기능 encode()decode()기능 을 모두 가질 수 있음을 의미합니다 . 따라서 단계는 다음과 같습니다. (1) 데이터베이스에 URL 저장 (2) 데이터베이스에서 해당 URL에 대한 고유 행 ID 가져 오기 (3) encode()273984를 들어 f5a4(4)를 사용하여 정수 ID를 짧은 문자열로 변환 f4a4합니다. 공유 가능한 URL (5) 짧은 문자열 (예 :)에 대한 요청을 받으면 (6) 주어진 ID에 대한 데이터베이스에서 URL을 조회 20a8하여 문자열을 정수 ID로 디코딩하십시오 decode(). 변환을 위해 다음을 사용하십시오 : github.com/delight-im/ShortURL
caw

@Marco, 데이터베이스에 해시를 저장하는 요점은 무엇입니까?
Maksim Vi.

3
@MaksimVi. 뒤집을 수없는 기능이 있다면 아무것도 없습니다. 단방향 해시 기능이 있다면 기능이있을 것입니다.
caw

1
간단한 CRC32 알고리즘을 사용하여 URL을 줄이면 잘못된 것일까 요? 충돌 가능성은 거의 없지만 (CRC32 출력의 길이는 일반적으로 8 자이며 3 천만 개가 넘는 가능성을 제공합니다.) 생성 된 CRC32 출력이 이미 사용되고 데이터베이스에서 발견 된 경우 긴 URL에 임의의 숫자를 지정할 수 있습니다 데이터베이스에서 고유 한 CRC32 출력을 찾을 때까지 이것이 간단한 해결책으로 얼마나 나쁘거나 다르거 나 추악합니까?
Rakib

답변:


816

"숫자를 문자열로 변환"접근 방식을 계속합니다. 그러나 ID가 소수이고 52보다 크면 제안 된 알고리즘이 실패 함을 알 수 있습니다 .

이론적 배경

Bijective 기능 이 필요합니다 . f . f (123) = 'abc' 함수에 대해 역함수 g ( 'abc') = 123 을 찾을 수 있어야 합니다. 이것은 다음을 의미합니다.

  • f (x1) = f (x2)를 만드는 x1, x2 (x1 ≠ x2 포함) 가 없어야합니다 .
  • 모든 y에 대해 f (x) = y가 되도록 x 를 찾을 수 있어야합니다 .

ID를 단축 URL로 변환하는 방법

  1. 우리가 사용하고자하는 알파벳을 생각하십시오. 귀하의 경우에는입니다 [a-zA-Z0-9]. 그것은 62 글자를 포함 합니다 .
  2. 자동 생성 된 고유 한 숫자 키 ( id예 : MySQL 테이블 의 자동 증분) 를 가져옵니다 .

    이 예에서는 125 10 (기본은 10 인 125)을 사용합니다.

  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를 사용하면 단축 URL로 cb 62 가 수신됩니다 .

    http://shor.ty/cb
    

단축 된 URL을 초기 ID로 해결하는 방법

그 반대가 더 쉽습니다. 당신은 당신의 알파벳에서 역방향 조회를 수행합니다.

  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리디렉션을 수행하십시오.

구현 예 (코멘트 작성자가 제공)


18
악성 자바 스크립트 코드의 URL을 삭제하는 것을 잊지 마십시오! javascript는 URL에서 base64로 인코딩 될 수 있으므로 'javascript'를 검색하는 것만으로는 충분하지 않습니다 .j
Bjorn

3
역함수를 갖기 위해서는 함수가 형용사 (주 사형 형용사) 여야합니다 .
Gumbo

57
생각할만한 음식, URL에 두 문자 체크섬을 추가하는 것이 유용 할 수 있습니다. 그러면 시스템의 모든 URL이 직접 반복되지 않습니다. F (체크섬 (ID) % (62 ^ 2)) 등의 뭔가 간단 + F (ID) = url_id
koblas

6
URL을 삭제하는 한, 스팸 필터를 피하기 위해 서비스를 사용하여 URL을 마스킹하는 스패머가 직면하게되는 문제 중 하나입니다. 알려진 훌륭한 행위자로 서비스를 제한하거나 긴 URL에 스팸 필터링을 적용해야합니다. 그렇지 않으면 스패머가 남용 할 것입니다.
Edward Falk

74
Base62는 f * 단어를 생성 할 가능성이 있으므로 (예를 들어, 3792586=='F_ck'_ 대신 u를 사용하여) 잘못된 선택 일 수 있습니다 . 이것을 최소화하기 위해 u / U와 같은 일부 문자를 제외합니다.
Paulo Scardine

56

왜 해시를 사용하고 싶습니까?

자동 증분 값을 영숫자 값으로 간단히 변환하면됩니다. 기본 변환을 사용하면 쉽게 할 수 있습니다. 문자 공간 (AZ, az, 0-9 등)에 40자가 있다고 가정하고 ID를 기본 40 숫자로 변환하고 문자를 숫자로 사용하십시오.


13
AZ, az 및 0-9 = 62 자 (40이 아니라)라는 사실을 제외하고는 귀하가 표시에 있습니다.
Evan Teran

감사! Base-62 알파벳을 사용해야합니까? en.wikipedia.org/wiki/Base_62 하지만 아이디를 기본 62 숫자로 어떻게 변환 할 수 있습니까?
caw

- 기본 변환 알고리즘 당연히 사용 en.wikipedia.org/wiki/Base_conversion#Change_of_radix
shoosh

2
'왜 해시를 사용하고 싶습니까?'와 관련하여 자동 증가를 기반으로하는 기본 변환은 순차적 URL을 생성하므로 다른 사람들의 단축 된 URL을 "탐색"할 수있는 사람들에게 익숙해야합니다. 권리?
Andrew Coleson

2
충분한 리소스와 시간이 있으면 URL 단축 서비스의 모든 URL을 "찾아 볼"수 있습니다.
shoosh 2009

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

나는 아이디어가 정말 마음에 든다. 내가 가진 유일한 문제는 디코드 함수의 num 변수를 경계 밖으로 계속 가져 오는 것입니다 (오랫 동안조차도). 어떻게 작동시키는 지 알고 있습니까? 아니면 이론적입니까?
user1322801

@ user1322801 : 아마도 인코딩 기능이 실제로 처리 할 수있는 것보다 훨씬 큰 것을 해독하려고합니다. 모든 "ints"를 BigInteger로 변환하면 더 많은 마일리지를 얻을 수 있지만> 9223372036854775807 인덱스가 없으면 오랫동안 충분할 것입니다.
biggusjimmus

2
반전의 중요성이 무엇인지 알 수 있습니까? 즉 sb.reverse (). toString ();
dotNet 디코더

62 ^ 62 = 1.7 조?
노아 토니

33

귀하의 질문에 대한 답변은 아니지만 대소 문자를 구분하는 단축 URL을 사용하지 않습니다. 그것들은 기억하기 어렵고, 일반적으로 읽을 수 없습니다 (많은 글꼴은 1과 l, 0과 O와 다른 문자는 매우 유사하여 차이를 말하기가 거의 불가능합니다). 그리고 명백한 오류가 발생하기 쉽습니다. 소문자 또는 대문자 만 사용하십시오.

또한 미리 정의 된 형식으로 숫자와 문자를 혼합하는 형식을 사용하십시오. 사람들이 다른 형태보다 한 형태를 더 잘 기억하는 경향이 있다는 연구 결과가 있습니다 (전화 번호는 특정 형태로 그룹화되어 있다고 생각하십시오). num-char-char-num-char-char와 같은 것을 시도하십시오. 대문자와 소문자가없는 경우 조합이 낮아질 것이라는 것을 알고 있지만 더 유용하고 유용 할 것입니다.


2
아주 좋은 생각 감사합니다. 나는 아직 그것에 대해 생각하지 않았습니다. 그것이 의미가 있든 없든 사용의 종류에 달려 있다는 것이 분명합니다.
caw

19
사람들이 짧은 URL을 엄격하게 복사하여 붙여 넣는 경우 문제가되지 않습니다.
Edward Falk

2
짧은 URL의 목적은 기억하기 어렵거나 말하기 쉽지 않습니다. 클릭 또는 복사 / 붙여 넣기 만합니다.
Hugo Nogueira

그래 나는 경우는 문제가되지 않습니다 그래서 짧은 URL이, 사람들이 그것을 나열하거나 이메일을위한 것이며이 짧고 일부 URL 등의 200 개 문자가 않습니다 차지하지 않도록 생각
nonopolarity

29

내 접근 방식 : 데이터베이스 ID를 취한 다음 Base36 인코딩하십시오 . 대문자와 소문자를 모두 사용하지 않을 것입니다. 전화로 URL을 전송하는 것은 악몽이지만, 물론 함수를 기본 62 en / decoder로 쉽게 확장 할 수 있기 때문입니다.


고마워요 당신이 2,176,782,336 가능성 또는 56,800,235,584를 가지고 있든, 동일합니다 : 둘 다 충분합니다. 따라서 기본 36 인코딩을 사용합니다.
caw

명백 할 수도 있지만 여기에 wikipedia에서 참조 된 PHP 코드가 있습니다. PHP tonymarston.net/php-mysql/converter.html
Ryan White

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

6

Node.js 및 MongoDB 솔루션

MongoDB가 12 바이트의 새 ObjectId를 작성하는 데 사용하는 형식을 알고 있기 때문에.

  • 유닉스 시대 이후 초를 나타내는 4 바이트 값
  • 3 바이트 머신 식별자
  • 2 바이트 프로세스 ID
  • 컴퓨터에서 임의의 값으로 시작하는 3 바이트 카운터

예 (임의의 순서를 선택합니다) a1b2c3d4e5f6g7h8i9j1k2l3

  • a1b2c3d4는 유닉스 시대 이후의 초를 나타냅니다.
  • 4e5f6g7은 기계 식별자를 나타냅니다.
  • h8i9는 프로세스 ID를 나타냅니다.
  • 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");
    })
});

1
RDBMS가 비 SQL / 키-값 저장소보다 나은 점은 무엇입니까?
kjs3

@ kjs3 예 다른 테이블과의 관계가 없기 때문에 RDBMS가 필요하지 않으므로 키 값 저장소가 더 빠릅니다.
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;
    }   
}


4

나는 데이터베이스에 도메인 당 정수 순서를 증가하고 사용 계속 Hashids를 URL 경로로 정수를 인코딩 할 수 있습니다.

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

문자 길이를 모두 사용할 때까지 시간이 얼마나 걸리는지 스크립트를 실행했습니다. 6자인 경우 164,916,224링크를 수행 한 다음 최대 7 자입니다. 조금씩 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)
}


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

여기 누구에게나 필요한 버전이 있습니다.


1

ID를 문자열로 변환하지 않는 이유는 무엇입니까? 0과 61 사이의 숫자를 단일 문자 (대 / 소문자) 또는 숫자로 매핑하는 함수 만 있으면됩니다. 그런 다음 이것을 적용하여 4 자 코드를 작성하십시오. 그러면 1470 만 개의 URL이 포함됩니다.


단순한 사고에 +1. 정말 간단합니다. 방금 정확히 이것을하고있는 답변을 게시했습니다. 중복 문자열이없고 모든 것이 고유한지 확인하기 위해 데이터베이스를 쿼리하는 프로덕션 코드가 있습니다.
Andrew Reese

1

다음은 PHP를위한 알맞은 URL 인코딩 함수입니다.

// 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 및 i를 생략 했습니까?

Ryan의 솔루션을 기반으로 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;
        }
    }
?>

예. 클래스 선언 바로 아래에 주석이 보입니까?
Svetoslav Marinov

1

https://hashids.org/를 살펴보십시오. 오픈 소스이며 여러 언어로 제공됩니다.

그들의 페이지는 다른 접근 방식의 함정 중 일부를 설명합니다.


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 자만해도 작업 할 네임 스페이스가 충분합니다.


이 접근 방식이 장기적으로 당신에게 도움이 되었습니까?
Chris

솔직히 말해서, 나는 어떤에 내가 거기에 P는 언급했다 투영 아무 생각이
조엘 버거

0

여러 저자의 웹 페이지를 저장하고 추측으로 페이지를 발견하지 못하게해야한다는 점에서 문제의 변형이 있습니다. 따라서 짧은 URL은 페이지 번호의 Base-62 문자열에 두 자리 숫자를 추가합니다. 이러한 추가 숫자는 페이지 레코드 자체의 정보에서 생성되며 3844 개의 URL 중 1 개만 유효합니다 (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

스칼라 구현 :

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

스칼라 테스트를 사용한 테스트 예제 :

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

제온 크로스 클래스 기반 기능

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

다음은 bit.ly 일 가능성이있는 Node.js 구현입니다. 매우 임의적 인 7 자 문자열을 생성합니다.

Node.js 암호화를 사용하여 임의로 7 개의 문자를 선택하는 대신 무작위로 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"는 무슨 뜻인가요? ?
Peter Mortensen

0

내 파이썬 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

양질의 Node.js / JavaScript 솔루션 은 철저히 테스트되었으며 몇 달 동안 프로덕션 환경에서 사용 된 id-shortener 모듈을 참조하십시오 .

플러그 가능한 스토리지를 기본으로 Redis 로 지원하는 효율적인 id / URL 단축기를 제공하며 짧은 ID 문자 세트와 단축이 dem 등한 지 여부를 사용자 정의 할 수도 있습니다. . 이는 모든 URL 단축기가 고려하지 않는 중요한 차이점입니다.

여기의 다른 답변과 관련하여이 모듈은 위에 Marcel 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

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을 기억하고 ID 0를 사용하는 것입니다 9999999999999999(그러나 많은 수가 필요합니다).

그러나 ID 0 9999999999999999은 문제가 될 수 있습니다.

  1. 16 진수 또는 base62 또는 base64를 사용하면 더 짧을 수 있습니다. (유튜브 같은 base64로 사용 A- Z a- z 0- 9 _-)
  2. 그것은에서 증가하는 경우 09999999999999999균일은 개인 정보 보호 문제가 될 수 있으므로, 다음 해커는 그 순서대로 방문 및 URL의 사람들이 서로를 보내는 것을 알 수 있습니다

우리는 할 수있어:

  1. 하나 개의 서버가 할당이 0999이제 서버 A는 ID가 1000을 가지고, 하나 개의 서버, 서버 A를. 따라서 지속적으로 새로운 ID를 원하는 20 또는 200 대의 서버가있는 경우, 각각의 새로운 ID를 요구할 필요는 없지만 1000 개의 ID를 요구하는 것이 좋습니다.
  2. 예를 들어 ID 1의 경우 비트를 반전시킵니다. 그래서 000...00000001하게 10000...000베이스 64로 전환 될 때,이 ID를 매번 증가 불균일 것 그래서.
  3. XOR을 사용하여 최종 ID의 비트를 뒤집습니다. 예를 들어, 0xD5AA96...2373비밀 키와 같은 XOR 이 있고 일부 비트는 뒤집 힙니다. 비밀 키에 1 비트가 설정되어 있으면 ID의 비트가 뒤집 힙니다. 이렇게하면 ID를 추측하기가 더 어려워지고 무작위로 표시됩니다

이 체계에 따라 ID를 할당하는 단일 서버가 ID를 형성 할 수 있으며 20 또는 200 개의 서버가 ID 할당을 요청할 수 있습니다. 할당 서버는 잠금 / 세마포어를 사용하여 두 요청 서버가 동일한 배치를 얻지 못하게해야합니다 (또는 한 번에 하나의 연결 만 허용하는 경우 이미 문제를 해결 함). 따라서 우리는 라인 (큐)이 너무 길어서 할당을 기다리는 것을 원하지 않습니다. 따라서 한 번에 1000 또는 10000을 할당하면 문제를 해결할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.