ฉันจะสร้างตัวย่อ URL ได้อย่างไร


667

ฉันต้องการสร้างบริการตัวย่อ URL ที่คุณสามารถเขียน URL แบบยาวลงในช่องป้อนข้อมูลและบริการจะย่อ URL ให้เป็น " http://www.example.org/abcdef"

แทนที่จะเป็น " abcdef" จะมีสตริงอื่นที่มีอักขระหกตัวที่บรรจุa-z, A-Z and 0-9อยู่ นั่นทำให้สตริงที่เป็นไปได้ 56 ~ 57 พันล้าน

แนวทางของฉัน:

ฉันมีตารางฐานข้อมูลที่มีสามคอลัมน์:

  1. id, จำนวนเต็ม, การเพิ่มอัตโนมัติ
  2. long, string, URL แบบยาวที่ผู้ใช้ป้อน
  3. สั้น, สตริง, URL ที่ย่อ (หรือเพียงแค่หกตัวอักษร)

ฉันจะแทรก URL ที่มีความยาวลงในตาราง จากนั้นฉันจะเลือกค่าการเพิ่มอัตโนมัติสำหรับ " id" และสร้างแฮชของมัน ควรแทรกแฮชนี้เป็น " short" แต่ควรจะสร้างแฮชแบบไหน อัลกอริธึมแฮเช่น MD5 สร้างสายยาวเกินไป ฉันไม่ได้ใช้อัลกอริทึมเหล่านี้ฉันคิดว่า อัลกอริทึมที่สร้างขึ้นเองจะทำงานเช่นกัน

ความคิดของฉัน:

สำหรับ " http://www.google.de/" 239472ฉันจะได้รับรหัสเพิ่มโดยอัตโนมัติ จากนั้นฉันทำตามขั้นตอนต่อไปนี้:

short = '';
if divisible by 2, add "a"+the result to short
if divisible by 3, add "b"+the result to short
... until I have divisors for a-z and A-Z.

สามารถทำซ้ำได้จนกว่าจำนวนจะไม่สามารถหารได้อีกต่อไป คุณคิดว่านี่เป็นวิธีการที่ดีหรือไม่? คุณมีความคิดที่ดีกว่านี้ไหม?

เนื่องจากความสนใจอย่างต่อเนื่องในหัวข้อนี้ผมได้รับการตีพิมพ์เป็นโซลูชั่นที่มีประสิทธิภาพในการ GitHubกับการใช้งานสำหรับJavaScript , PHP , PythonและJava เพิ่มโซลูชันของคุณหากคุณต้องการ :)


5
@gudge จุดของฟังก์ชั่นเหล่านั้นคือพวกเขามีฟังก์ชั่นอินเวอร์ส ซึ่งหมายความว่าคุณสามารถมีทั้งencode()และdecode()ฟังก์ชั่น ขั้นตอนจึง: (1) บันทึก URL ในฐานข้อมูล (2) รับ ID แถวที่ไม่ซ้ำกัน URL นั้นจากฐานข้อมูล (3) รหัสแปลงจำนวนเต็มสตริงสั้น ๆ ที่มีencode()เช่น273984การf5a4(4) ใช้สตริงสั้น (เช่นf4a4) ในของคุณ URL ที่แชร์ได้ (5) เมื่อได้รับคำขอสตริงสั้น (เช่น20a8) ให้ถอดรหัสสตริงเป็น ID จำนวนเต็มด้วยdecode()(6) ค้นหา URL ในฐานข้อมูลสำหรับ ID ที่กำหนด สำหรับการแปลงใช้: github.com/delight-im/ShortURL
caw

@Marco ประเด็นของการจัดเก็บแฮชในฐานข้อมูลคืออะไร
Maksim Vi

3
@MaksimVi หากคุณมีฟังก์ชั่นกลับด้านก็ไม่มีใคร หากคุณมีฟังก์ชันแฮชทางเดียวก็จะมีหนึ่ง
caw

1
มันจะผิดถ้าเราใช้อัลกอริทึม CRC32 ง่าย ๆ เพื่อย่อ URL แม้ว่าจะไม่เกิดการชนกันมาก (เอาต์พุต CRC32 มักจะยาว 8 อักขระและนั่นทำให้เรามีความเป็นไปได้มากกว่า 30 ล้านครั้ง) หากมีการใช้เอาต์พุต CRC32 ที่สร้างขึ้นก่อนหน้านี้แล้วและพบในฐานข้อมูลเราสามารถใส่ URL ยาวด้วยตัวเลขสุ่ม จนกว่าเราจะพบเอาต์พุต CRC32 ซึ่งเป็นค่าเฉพาะในฐานข้อมูลของฉัน สิ่งนี้เลวหรือแตกต่างหรือน่าเกลียดสำหรับวิธีแก้ปัญหาอย่างง่ายหรือไม่?
Rakib

คำตอบ:


816

ฉันจะใช้วิธีการ "แปลงหมายเลขเป็นสตริง" ต่อไป แต่คุณจะรู้ว่าอัลกอริทึมที่นำเสนอของคุณล้มเหลวหาก ID ของคุณเป็นสำคัญและยิ่งใหญ่กว่า 52

พื้นหลังทางทฤษฎี

คุณจำเป็นต้องมีbijective ฟังก์ชั่น F นี่เป็นสิ่งจำเป็นเพื่อให้คุณสามารถค้นหาฟังก์ชันผกผันg ('abc') = 123สำหรับฟังก์ชัน f (123) = 'abc' ของคุณ หมายความว่า:

  • จะต้องไม่มีx1, x2 (กับ x1 ≠ x2)ที่จะทำให้f (x1) = f (x2) ,
  • และทุกปีคุณจะต้องสามารถที่จะหาxเพื่อให้f (x) y

วิธีแปลง ID เป็น URL แบบย่อ

  1. นึกถึงตัวอักษรที่เราต้องการใช้ [a-zA-Z0-9]ในกรณีของคุณว่า มันมี62 ตัวอักษร
  2. ใช้คีย์ตัวเลขที่สร้างขึ้นโดยอัตโนมัติและไม่ซ้ำกัน ( idตัวอย่างเช่นการเพิ่มขึ้นอัตโนมัติของตาราง MySQL)

    สำหรับตัวอย่างนี้ฉันจะใช้ 125 10 (125 กับฐาน 10)

  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 คุณจะได้รับ cb 62เป็น URL แบบย่อ

    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 สำหรับรหัสจาวาสคริปต์ที่เป็นอันตราย! โปรดจำไว้ว่าจาวาสคริปต์ที่สามารถเข้ารหัส base64 ใน URL เพื่อเพียงการค้นหาสำหรับ 'จาวาสคริปต์' ไม่ enough.j ดี
บียอร์

3
ฟังก์ชั่นจะต้องเป็น bijective (injective และ surjective) เพื่อให้มีค่าผกผัน
Gumbo

57
อาหารสำหรับความคิดอาจเป็นประโยชน์ในการเพิ่มการตรวจสอบอักขระสองตัวใน URL ซึ่งจะป้องกันการวนซ้ำโดยตรงของ URL ทั้งหมดในระบบของคุณ บางอย่างง่าย ๆ เช่น f (เช็คซัม (id)% (62 ^ 2)) + f (id) = url_id
koblas

6
ปัญหาหนึ่งที่คุณกำลังเผชิญคือผู้ส่งอีเมลขยะที่ใช้บริการของคุณเพื่อปิดบัง URLS เพื่อหลีกเลี่ยงตัวกรองสแปม คุณต้อง จำกัด การให้บริการแก่นักแสดงที่รู้จักดีหรือใช้การกรองสแปมกับ URL ที่ยาว มิฉะนั้นคุณจะถูกทำร้ายโดยผู้ส่งอีเมลขยะ
Edward Falk

74
Base62 อาจเป็นตัวเลือกที่ไม่ถูกต้องเนื่องจากมีความเป็นไปได้ที่จะสร้างคำ f * (ตัวอย่างเช่น3792586=='F_ck'กับ u ในตำแหน่ง _) ฉันจะยกเว้นบางตัวละครเช่น u / U เพื่อย่อขนาดนี้
เปาโล Scardine

56

ทำไมคุณต้องการใช้แฮช

คุณสามารถใช้การแปลแบบง่ายของค่าการเพิ่มอัตโนมัติเป็นค่าตัวอักษรและตัวเลข คุณสามารถทำได้อย่างง่ายดายโดยใช้การแปลงฐาน สมมติว่าคุณมีช่องว่างตัวอักษร (AZ, az, 0-9 และอื่น ๆ ) มี 40 ตัวแปลง ID เป็นหมายเลขฐาน 40 และใช้อักขระเป็นตัวเลข


13
นอกเหนือจากข้อเท็จจริงที่ว่า AZ, az และ 0-9 = 62 ตัวอักษรไม่ใช่ 40 คุณอยู่ในเครื่องหมาย
Evan Teran

ขอบคุณ! ฉันควรใช้ตัวอักษรฐาน 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

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
ฉันขอทราบความสำคัญของการย้อนกลับได้อย่างไร ie sb.reverse (). toString ();
dotNet Decoder

นั่นคือ 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 แบบสั้นนั้นมีไว้สำหรับคนที่จะแสดงรายการหรือส่งอีเมลเท่านั้นดังนั้นจึงเป็นแบบสั้นและจะไม่ใช้อักขระเกิน 200 ตัวเหมือนที่ URL บางตัวทำดังนั้นกรณีจึงไม่มีปัญหา
nonopolarity

29

วิธีการของฉัน: Take ID ฐานข้อมูลแล้วBase36 เข้ารหัสมัน ฉันจะไม่ใช้ทั้งตัวอักษรบนและตัวพิมพ์เล็กเพราะนั่นทำให้การส่ง URL เหล่านั้นทางโทรศัพท์เป็นฝันร้าย แต่แน่นอนว่าคุณสามารถขยายฟังก์ชั่นนี้ให้เป็นฐาน 62 en / ถอดรหัสได้อย่างง่ายดาย


ขอบคุณคุณพูดถูก ไม่ว่าคุณจะมีความเป็นไปได้ 2,176,782,336 หรือ 56,800,235,584 ก็เหมือนกัน: ทั้งสองจะเพียงพอ ดังนั้นฉันจะใช้การเข้ารหัสฐาน 36
caw

มันอาจจะชัดเจน แต่นี่คือโค้ด PHP ที่อ้างอิงใน wikipedia เพื่อทำการเข้ารหัส base64 ใน 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 ใช้เพื่อสร้าง ObjectId ใหม่ที่มีขนาด 12 ไบต์

  • ค่า 4 ไบต์คิดเป็นวินาทีตั้งแต่ Unix epoch
  • ตัวระบุเครื่อง 3 ไบต์
  • ID กระบวนการ 2 ไบต์
  • ตัวนับ 3 ไบต์ (ในเครื่องของคุณ) เริ่มต้นด้วยค่าสุ่ม

ตัวอย่าง (ฉันเลือกลำดับแบบสุ่ม) a1b2c3d4e5f6g7h8i9j1k2l3

  • a1b2c3d4 แสดงถึงวินาทีนับตั้งแต่ยุค Unix
  • 4e5f6g7 แสดงถึงตัวระบุเครื่อง
  • h8i9 แสดงถึงรหัสกระบวนการ
  • j1k2l3 หมายถึงตัวนับเริ่มต้นด้วยค่าสุ่ม

เนื่องจากตัวนับจะไม่ซ้ำกันหากเราจัดเก็บข้อมูลในเครื่องเดียวกันเราสามารถรับได้โดยไม่ต้องสงสัยว่ามันจะซ้ำกัน

ดังนั้น URL สั้น ๆ จะเป็นตัวนับและนี่คือข้อมูลโค้ดสมมติว่าเซิร์ฟเวอร์ของคุณทำงานอย่างถูกต้อง

const mongoose = require('mongoose');
const Schema = mongoose.Schema;

// Create a schema
const shortUrl = new Schema({
    long_url: { type: String, required: true },
    short_url: { type: String, required: true, unique: true },
  });
const ShortUrl = mongoose.model('ShortUrl', shortUrl);

// The user can request to get a short URL by providing a long URL using a form

app.post('/shorten', function(req ,res){
    // Create a new shortUrl */
    // The submit form has an input with longURL as its name attribute.
    const longUrl = req.body["longURL"];
    const newUrl = ShortUrl({
        long_url : longUrl,
        short_url : "",
    });
    const shortUrl = newUrl._id.toString().slice(-6);
    newUrl.short_url = shortUrl;
    console.log(newUrl);
    newUrl.save(function(err){
        console.log("the new URL is added");
    })
});

1
RDBMS จะดีกว่าที่เก็บ no-sql / key-value อย่างไร
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)

ฉันรันสคริปต์เพื่อดูว่าต้องใช้เวลานานเท่าใดจนกว่าจะหมดความยาวของอักขระ สำหรับหกตัวอักษรมันสามารถทำการ164,916,224เชื่อมโยงและจากนั้นไปถึงเจ็ดตัวอักษร Bitly ใช้อักขระเจ็ดตัว ภายใต้ตัวละครทั้งห้านั้นดูแปลกสำหรับฉัน

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

หากคุณไม่ต้องการคิดค้นล้อใหม่ ... http://lilurl.sourceforge.net/


1
"ขออภัยดูเหมือนว่าสแปมเมอร์จะมาถึงสิ่งนี้ลองใช้ tinyurl แทน"
takeshin

ไปยังเว็บไซต์ตัวอย่าง ซอร์สโค้ดยังคงสามารถดาวน์โหลดได้จาก Sourceforge
Alister Bulman


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 ตัวอักษรและคุณมี URL ครอบคลุม 14.7 ล้าน URL


+1 สำหรับการคิดแบบง่ายๆ มันง่ายจริงๆ ฉันเพิ่งโพสต์คำตอบที่ทำตรงนี้ ฉันมีรหัสการผลิตบางอย่างที่สืบค้นฐานข้อมูลเพื่อให้แน่ใจว่าไม่มีสตริงที่ซ้ำกันและทุกอย่างไม่ซ้ำใคร
Andrew Reese

1

นี่คือฟังก์ชั่นการเข้ารหัส URL ที่ดีสำหรับ PHP ...

// From http://snipplr.com/view/22246/base62-encode--decode/
private function base_encode($val, $base=62, $chars='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ') {
    $str = '';
    do {
        $i = fmod($val, $base);
        $str = $chars[$i] . $str;
        $val = ($val - $i) / $base;
    } while($val > 0);
    return $str;
}

1

ไม่ทราบว่าใครจะพบว่ามีประโยชน์นี้ - มันเป็นวิธีการ 'แฮ็ค n สแลช' มากกว่า แต่ก็ง่ายและใช้งานได้ดีถ้าคุณต้องการตัวอักษรเฉพาะ

$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 โดยเจตนาหรือไม่?

ฉันเพิ่งสร้างคลาส PHP ตามโซลูชันของ 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;
        }
    }
?>

ใช่. คุณเห็นความคิดเห็นด้านล่างประกาศคลาสหรือไม่
Svetoslav Marinov


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

สำหรับโครงการที่คล้ายกันเพื่อรับคีย์ใหม่ฉันสร้างฟังก์ชัน wrapper รอบตัวสร้างสตริงแบบสุ่มที่เรียกตัวสร้างจนกว่าฉันจะได้รับสตริงที่ไม่ได้ใช้ใน hashtable ของฉัน วิธีนี้จะช้าลงเมื่อพื้นที่ชื่อของคุณเริ่มเต็ม แต่อย่างที่คุณพูดถึงแม้จะมีเพียง 6 ตัวอักษรคุณก็มีเนมสเปซมากมายที่จะใช้งานได้


วิธีนี้ได้ผลสำหรับคุณในระยะยาวหรือไม่?
Chris

พูดตามตรงฉันไม่มีความคิดที่จะอ้างถึงโครงการที่ฉัน:
Joel Berger

0

ฉันมีปัญหาหลายอย่างซึ่งฉันเก็บหน้าเว็บจากผู้แต่งหลายคนและต้องป้องกันการค้นพบหน้าเว็บด้วยการคาดเดา ดังนั้น URL สั้น ๆ ของฉันจึงเพิ่มตัวเลขพิเศษสองสามตัวลงในสตริง Base-62 สำหรับหมายเลขหน้า ตัวเลขพิเศษเหล่านี้สร้างขึ้นจากข้อมูลในหน้าบันทึกตัวเองและพวกเขามั่นใจว่ามีเพียง 1 ใน 3844 URL เท่านั้นที่ถูกต้อง (สมมติว่าเป็นเลข 2 หลัก -62) ท่านสามารถเข้าดูรายละเอียดของร่างที่http://mgscan.com/MBWL


0

คำตอบที่ดีมากฉันได้สร้างการใช้งาน Golang ของ bjf:

package bjf

import (
    "math"
    "strings"
    "strconv"
)

const alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"

func Encode(num string) string {
    n, _ := strconv.ParseUint(num, 10, 64)
    t := make([]byte, 0)

    /* Special case */
    if n == 0 {
        return string(alphabet[0])
    }

    /* Map */
    for n > 0 {
        r := n % uint64(len(alphabet))
        t = append(t, alphabet[r])
        n = n / uint64(len(alphabet))
    }

    /* Reverse */
    for i, j := 0, len(t) - 1; i < j; i, j = i + 1, j - 1 {
        t[i], t[j] = t[j], t[i]
    }

    return string(t)
}

func Decode(token string) int {
    r := int(0)
    p := float64(len(token)) - 1

    for i := 0; i < len(token); i++ {
        r += strings.Index(alphabet, string(token[i])) * int(math.Pow(float64(len(alphabet)), p))
        p--
    }

    return r
}

โฮสต์ที่ github: https://github.com/xor-gate/go-bjf


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

การใช้งานใน 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)
  }

}

0

ฟังก์ชั่นที่ใช้ใน Xeoncross Class

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

นี่คือการดำเนินการ Node.js ที่มีแนวโน้มที่จะ bit.ly สร้างสตริงเจ็ดอักขระแบบสุ่มสูง

มันใช้การเข้ารหัสลับของ Node.js เพื่อสร้างชุดอักขระ 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

My 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

สำหรับโซลูชัน Node.js / JavaScript ที่มีคุณภาพดูโมดูลid-shortenerซึ่งผ่านการทดสอบอย่างละเอียดและมีการใช้ในการผลิตเป็นเวลาหลายเดือน

มันมีตัวย่อ id / URL ที่มีประสิทธิภาพซึ่งสนับสนุนโดยการเริ่มต้นที่เก็บข้อมูลแบบเสียบได้ Redisและคุณยังสามารถปรับแต่งชุดอักขระรหัสสั้น ๆ ของคุณหรือไม่และการตัดทอนเป็นidempotent นี่เป็นข้อแตกต่างที่สำคัญที่ต้องคำนึงถึงตัวย่อ 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. มันอาจจะสั้นลงถ้าเราใช้เลขฐานสิบหกหรือแม้กระทั่ง base62 หรือ base64 (base64 เช่นเดียวกับ YouTube ที่ใช้A- Z a- z 0- 9 _และ- )
  2. ถ้ามันเพิ่มจาก0เป็น9999999999999999สม่ำเสมอจากนั้นแฮกเกอร์สามารถเยี่ยมชมพวกเขาในลำดับนั้นและรู้ว่าสิ่งที่ผู้คนกำลังส่งซึ่งกันและกัน URL ดังนั้นมันอาจเป็นปัญหาความเป็นส่วนตัว

พวกเราสามารถทำได้:

  1. มีหนึ่งเซิร์ฟเวอร์ที่จัดสรร0ให้999กับเซิร์ฟเวอร์หนึ่งเซิร์ฟเวอร์ A ดังนั้นตอนนี้เซิร์ฟเวอร์ A มี 1,000 ID ดังกล่าว ดังนั้นหากมีเซิร์ฟเวอร์ 20 หรือ 200 เซิร์ฟเวอร์ที่ต้องการรหัสใหม่อยู่ตลอดเวลาก็ไม่จำเป็นต้องถาม ID ใหม่แต่ละครั้ง แต่ให้ถาม 1,000 ID ครั้งเดียว
  2. สำหรับ ID 1 ตัวอย่างเช่นย้อนกลับบิต ดังนั้น 000...00000001จะกลายเป็น10000...000ดังนั้นเมื่อแปลงเป็น base64 มันจะเพิ่ม ID ที่ไม่สม่ำเสมอในแต่ละครั้ง
  3. ใช้ XOR เพื่อพลิกบิตสำหรับรหัสสุดท้าย ตัวอย่างเช่น XOR ด้วย0xD5AA96...2373(เช่นคีย์ลับ) และบิตบางส่วนจะถูกพลิก (เมื่อใดก็ตามที่รหัสลับมีการเปิด 1 บิตมันจะพลิกบิตของ ID) สิ่งนี้จะทำให้รหัสนั้นยากต่อการคาดเดาและสุ่มมากขึ้น

หลังจากใช้รูปแบบนี้เซิร์ฟเวอร์เดียวที่จัดสรร ID สามารถจัดรูปแบบ ID และเซิร์ฟเวอร์ 20 หรือ 200 สามารถร้องขอการจัดสรร ID ได้ เซิร์ฟเวอร์การปันส่วนต้องใช้การล็อค / สัญญาณเพื่อป้องกันเซิร์ฟเวอร์ที่ร้องขอสองชุดไม่สามารถรับแบตช์เดียวกัน (หรือถ้ายอมรับการเชื่อมต่อทีละครั้งการแก้ปัญหานี้จะแก้ปัญหาแล้ว) ดังนั้นเราไม่ต้องการให้บรรทัด (คิว) ยาวเกินไปสำหรับการรอรับการจัดสรร นั่นคือเหตุผลที่การจัดสรร 1,000 หรือ 10,000 ต่อครั้งสามารถแก้ปัญหาได้

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.