#clojure #functionalprogramming #scala #base62
- How a URL Shortening Application Works
- Designing the Shortening URL system like Bit.ly, loading 6 billion clicks a month
- First Video in Base62 language series 3rd Vlog video Show Notes
- Second Video in Base62 language (Java) series 4th Vlog video Show Notes
- Third Video in Base62 language (Scala) series 5th Vlog video Show Notes
- Fourth Video in Base62 language (Clojure) series 6th Vlog video Show notes
- Fifth Video in Base62 language (Python) series 7th Vlog video Show notes
- Sixth Video in Base62 language (JavaScript/TypeScript) series 8th Vlog video Show notes
- Seventh Video in Base62 language (Rust) series 9th Vlog video Show notes
- Eighth Video in Base62 language (Go Lang) series 10th Vlog video Show notes
- Comparing Basic FP support part 1 --Rick Hightower
- Is Java a good FP language? Comparing Basic FP support part 2 --Rick Hightower
- Translating to Clojure: a learning task (Part 1) --Tom Hicks
Porting Base62Encoder/Decoder from Scala to Clojure Java.
Scala | Clojure |
---|---|
def main(args: Array[String]): Unit = {
val id = 12345678910L
val strId = convertToEncodedString(id)
val newId = convertToLong(strId)
println(s"$id $strId $newId")
val longURL = "https://www.somewebiste.com/dp/..."
val urlId = Math.abs(longURL.hashCode)
val shortHandle = convertToEncodedString(urlId)
println(s"$urlId $shortHandle ${convertToLong(shortHandle)}")
} |
(defn -main
[& args]
(printf " %s %d\n %s %d \n"
(convertToEncodedString 12345678910)
(convertToLong (convertToEncodedString 12345678910))
(convertToEncodedString (Math/abs (.hashCode "https://www.somewebiste.com/dp/0201616165...")))
(Math/abs (.hashCode "https://www.somewebiste/dp/0201616165..."))
)
)
|
Scala | Clojure |
---|---|
def convertToEncodedString(id: Long): String = {
val builder: List[String] = List()
val placeHolder = findStartBucket(id)
val results = accumulateDigits(placeHolder, id, builder)
val bucketValue = powDigitsBase(1)
val digitIndex: Int = (results._2 / bucketValue).toInt
val acc = results._2 - (bucketValue * digitIndex)
val newBuilder: List[String] = appendSafeToList(results._3, digitIndex)
//Put the remainder in the ones column
val place1DigitIndex = (acc % bucketValue).toInt
val finalBuilder = newBuilder ++ List(DIGITS(place1DigitIndex).toString)
finalBuilder.mkString("")
}
private def accumulateDigits(placeHolder: Int, acc: Long,
builder: List[String]): (Int, Long, List[String]) = {
if (!(placeHolder > 1)) {
return (placeHolder, acc, builder)
}
val bucketValue = powDigitsBase(placeHolder)
val digitIndex = (acc / bucketValue).toInt
accumulateDigits(placeHolder - 1, acc - (bucketValue * digitIndex),
appendSafeToList(builder, digitIndex))
} |
(defn convertToEncodedString [id]
(let [
placeHolder (findStartBucket id)
results (accumulateDigits placeHolder id [] )
]
(let [
bucketValue (powDigitsBase 1)
digitIndex (int (/ (nth results 1) bucketValue))
acc (- (nth results 1) (* bucketValue digitIndex))
newBuilder (appendSafeToList (nth results 2) digitIndex)
place1DigitIndex (int (mod acc bucketValue ))
finalBuilder (conj newBuilder (nth DIGITS place1DigitIndex))
]
(clojure.string/join "" finalBuilder)
)
)
)
(defn accumulateDigits [placeHolder, acc, builder]
(if (not (> placeHolder 1))
[placeHolder, acc, builder]
(let [
bucketValue (powDigitsBase placeHolder)
digitIndex (int (/ acc bucketValue))
]
(accumulateDigits
(- placeHolder 1)
(- acc (* bucketValue digitIndex) )
(appendSafeToList builder digitIndex)
)
)
)
) |
Scala | Clojure |
---|---|
private def findStartBucket(value: Long): Int = {
val i = Range(0, 15, 1).find(i => value < powDigitsBase(i.toLong))
i.getOrElse(0)
} |
(defn findStartBucket [value]
(first (filter (fn [i] (< value (powDigitsBase i ))) INDEXES))
) |
Scala | Clojure |
---|---|
private def powDigitsBase(exponent: Long): Long =
doPow(exponent, DIGITS.length)
private def doPow(exponent: Long, base: Int): Long = {
if (exponent == 0) return 1
doPow(exponent - 1, base) * base
}
|
(defn powDigitsBase [exponent]
(doPow exponent (count DIGITS))
)
(defn doPow [exponent, base]
(if (== exponent 0) 1
(* (doPow (- exponent 1) base) base)
)
) |
Scala | Clojure |
---|---|
private def appendSafeToList(builder: List[String], digitIndex: Int): List[String] = {
if (digitIndex != 0) builder ++ List((DIGITS(digitIndex)).toString)
else if (builder.nonEmpty) builder ++ List((DIGITS(digitIndex)).toString)
else builder
} |
(defn appendSafeToList [builder, digitIndex]
(if (not (= digitIndex 0))
(conj
builder,
(nth DIGITS digitIndex)
)
(if (> (count builder) 0)
(conj
builder,
(nth DIGITS digitIndex)
)
builder
)
)
) |
Scala | Clojure |
---|---|
def convertToLong(strId: String): Long =
doConvertToLong(strId.toCharArray)
private def doConvertToLong(chars: Array[Char]): Long = {
val (acc, _) = chars.reverse.foldLeft(0L, 0) { (pos, ch) =>
val (acc, position) = pos
val value = computeValue(ch, position)
(acc + value, position + 1)
}
acc
} |
(defn convertToLong [strId]
(nth (doConvertToLong (seq (char-array strId)) ) 0)
)
(defn doConvertToLong [chars]
(reduce
(fn [pos ch]
(let [
acc (nth pos 0)
position (nth pos 1)
value (computeValue ch position)
]
[(+ acc value), (+ position 1)]
)
)
[(long 0), 0]
(reverse chars))
) |
Scala | Clojure |
---|---|
private def computeValue(c: Char, position: Int) = {
val digitIndex = findIndexOfDigitInTable(c)
val multiplier = powDigitsBase(position)
digitIndex * multiplier
} |
(defn computeValue [c, position]
(let [
digitIndex (clojure.string/index-of DIGITS c)
multiplier (powDigitsBase position)
]
(* digitIndex multiplier)
)
) |
Functional Scala | Scala |
---|---|
private def findIndexOfDigitInTable(c: Char): Int = {
val i = DIGITS.indexOf(c);
if (i == -1) {
throw new IllegalStateException("Unknown char #" + c + "#")
}
i
} |
private def findIndexOfDigitInTable(c: Char): Int = {
for (i <- 0 until DIGITS.length) {
val digit = DIGITS(i)
if (c == digit) return i
}
throw new IllegalStateException("Unknown char #" + c + "#")
} |
(ns my-project.core
(:gen-class))
(def DIGITS (clojure.string/join "" [
0 1 2 3 4 5 6 7 8 9
'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
])
)
(def INDEXES ( range (long 0) (long 11) (long 1) ))
(defn doPow [exponent, base]
(if (== exponent 0) 1
(* (doPow (- exponent 1) base) base)
)
)
(defn powDigitsBase [exponent]
(doPow exponent (count DIGITS))
)
(defn findStartBucket [value]
(first (filter (fn [i] (< value (powDigitsBase i ))) INDEXES))
)
(defn appendSafeToList [builder, digitIndex]
(if (not (= digitIndex 0))
(conj
builder,
(nth DIGITS digitIndex)
)
(if (> (count builder) 0)
(conj
builder,
(nth DIGITS digitIndex)
)
builder
)
)
)
(defn accumulateDigits [placeHolder, acc, builder]
(if (not (> placeHolder 1))
[placeHolder, acc, builder]
(let [
bucketValue (powDigitsBase placeHolder)
digitIndex (int (/ acc bucketValue))
]
(accumulateDigits
(- placeHolder 1)
(- acc (* bucketValue digitIndex) )
(appendSafeToList builder digitIndex)
)
)
)
)
(defn convertToEncodedString [id]
(let [
placeHolder (findStartBucket id)
results (accumulateDigits placeHolder id [] )
]
(let [
bucketValue (powDigitsBase 1)
digitIndex (int (/ (nth results 1) bucketValue))
acc (- (nth results 1) (* bucketValue digitIndex))
newBuilder (appendSafeToList (nth results 2) digitIndex)
place1DigitIndex (int (mod acc bucketValue ))
finalBuilder (conj newBuilder (nth DIGITS place1DigitIndex))
]
(clojure.string/join "" finalBuilder)
)
)
)
(defn computeValue [c, position]
(let [
digitIndex (clojure.string/index-of DIGITS c)
multiplier (powDigitsBase position)
]
(* digitIndex multiplier)
)
)
(defn doConvertToLong [chars]
(reduce
(fn [pos ch]
(let [
acc (nth pos 0)
position (nth pos 1)
value (computeValue ch position)
]
[(+ acc value), (+ position 1)]
)
)
[(long 0), 0]
(reverse chars))
)
(defn convertToLong [strId]
(nth (doConvertToLong (seq (char-array strId)) ) 0)
)
(defn -main
[& args]
(printf " %s %d\n %s %d \n"
(convertToEncodedString 12345678910)
(convertToLong (convertToEncodedString 12345678910))
(convertToEncodedString (Math/abs (.hashCode "https://www.somewebiste.com/dp/0201616165/?_encoding=UTF8&pd_rd_w=vwEcs&content-id=amzn1.sym.8cf3b8ef-6a74-45dc-9f0d-6409eb523603&pf_rd_p=8cf3b8ef-6a74-45dc-9f0d-6409eb523603&pf_rd_r=BQ0KD40K57XG761DBNBA&pd_rd_wg=DtkHk&pd_rd_r=f94b60b7-9080-4065-b77f-6377ec854d17&ref_=pd_gw_ci_mcx_mi")))
(Math/abs (.hashCode "https://www.somewebiste.com/dp/0201616165/?_encoding=UTF8&pd_rd_w=vwEcs&content-id=amzn1.sym.8cf3b8ef-6a74-45dc-9f0d-6409eb523603&pf_rd_p=8cf3b8ef-6a74-45dc-9f0d-6409eb523603&pf_rd_r=BQ0KD40K57XG761DBNBA&pd_rd_wg=DtkHk&pd_rd_r=f94b60b7-9080-4065-b77f-6377ec854d17&ref_=pd_gw_ci_mcx_mi"))
)
)
package main.normal;
public class Base62Encoder {
public static void main(String[] args) {
final long id = 12345678910L;
final String strId = convertToEncodedString(id);
final long newId = convertToLong(strId);
System.out.printf("%d %s %d\n", id, strId, newId);
final String longURL = "https://www.somewebiste.com/dp/0201616165/?_encoding=UTF8&pd_rd_w=vwEcs&content-id=amzn1.sym.8cf3b8ef-6a74-45dc-9f0d-6409eb523603&pf_rd_p=8cf3b8ef-6a74-45dc-9f0d-6409eb523603&pf_rd_r=BQ0KD40K57XG761DBNBA&pd_rd_wg=DtkHk&pd_rd_r=f94b60b7-9080-4065-b77f-6377ec854d17&ref_=pd_gw_ci_mcx_mi";
final long urlId = Math.abs(longURL.hashCode()); //or save URL in DB and get ID from DB row.
final String shortHandle = convertToEncodedString(urlId);
System.out.printf("%d %s %d\n", urlId, shortHandle, convertToLong(shortHandle));
}
private static final char[] DIGITS = {
//0 1 2 3 4 5 6 7 8 9
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
//10 11 12 13 14 15 16 17 18 19 20 21
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',
//22 23 24 25 26 27 28 29 30 31 32 33 34 35
'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
// 36 37 38 39 40 41 42 43 44 45 46 47
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', //Easy to add more characters if not using lookup tables.
// 48 49 50 51 52 53 54 55 56 57 58 59 60 61 // 62 63, 64
'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', // '*', '@', '$'
};
public static long convertToLong(String strId) {
return convertToLong(strId.toCharArray());
}
public static String convertToEncodedString(final long id) {
final StringBuilder builder = new StringBuilder();
int placeHolder = findStartBucket(id);
long bucketValue;
long acc = id;
int digitIndex;
while (placeHolder > 1) {
//bucketValue = buckets[index];
bucketValue = powDigitsBase(placeHolder);
digitIndex = (int) (acc / bucketValue);
acc = acc - (bucketValue * digitIndex);
appendSafe(builder, digitIndex);
placeHolder--;
}
//bucketValue = buckets[1];
bucketValue = powDigitsBase(1);
digitIndex = (int) (acc / bucketValue);
acc = acc - (bucketValue * digitIndex);
appendSafe(builder, digitIndex);
//Put the remainder in the ones column
digitIndex = (int) (acc % bucketValue);
builder.append(DIGITS[digitIndex]);
return builder.toString();
}
private static long convertToLong(char[] chars) {
long acc = 0;
int position = 0;
for (int index = chars.length - 1; index > -1; index--) {
char c = chars[index];
long value = computeValue(c, position);
acc += value;
position++;
}
return acc;
}
private static int findIndexOfDigitInTable(char c) {
for (int i = 0; i < DIGITS.length; i++) {
char digit = DIGITS[i];
if (c == digit) {
return i;
}
}
throw new IllegalStateException("Unknown char #" + c + "#");
}
private static long computeValue(char c, int position) {
final int digitIndex = findIndexOfDigitInTable(c);
final long multiplier = powDigitsBase(position);
//final long multiplier = buckets[position];
return digitIndex * multiplier;
}
private static void appendSafe(StringBuilder builder, int digitIndex) {
if (digitIndex != 0) {
builder.append(DIGITS[digitIndex]);
} else {
if (builder.length() > 0) {
builder.append(DIGITS[digitIndex]);
}
}
}
private static long powDigitsBase( final long exponent) {
return doPow(exponent, DIGITS.length);
}
private static long doPow( final long exponent, final long base) {
long result = 1;
long exp = exponent;
while (exp != 0) {
result *= base;
--exp;
}
return result;
}
private static int findStartBucket(long value) {
for (int i = 0; i < 15; i++) {
if (value < powDigitsBase(i)) {
return i-1;
}
}
return 0;
}
}