Last active
August 5, 2021 17:29
-
-
Save therohk/5fe639cfcf7e53c6e100a98ecf020ef3 to your computer and use it in GitHub Desktop.
Counter for any base representation with provided charset
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.therohk.gist; | |
import java.math.BigInteger; | |
import java.util.HashSet; | |
import java.util.Iterator; | |
import java.util.Set; | |
public final class BaseCounterGist implements Iterator<String> { | |
//------------------------------------------------------------------------- | |
private static final String charset26 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; | |
private static final String charset36 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; | |
//------------------------------------------------------------------------- | |
private final int base; | |
private final String charset; | |
private final int width; | |
private volatile String value; | |
//------------------------------------------------------------------------- | |
public static BaseCounterGist getInstance(int width) { | |
return new BaseCounterGist(leftSub(charset26, 1), charset26, 26, width); | |
} | |
public static BaseCounterGist getInstance(String initial) { | |
return new BaseCounterGist(initial, charset26, 26, initial.length()); | |
} | |
public static BaseCounterGist getInstance(String initial, String charset, int base, int width) { | |
return new BaseCounterGist(initial, charset, base, width); | |
} | |
public static BaseCounterGist getInstance(String charset, int width) { | |
return new BaseCounterGist(Character.toString(charset.charAt(0)), charset, charset.length(), width); | |
} | |
public static BaseCounterGist getInstance(int base, int width) { | |
if(base < 2 || base > 36) | |
throw new IllegalArgumentException("base between 2 and 36 only"); | |
String charset = charset36.substring(0, base); | |
return new BaseCounterGist(Character.toString(charset.charAt(0)), charset, base, width); | |
} | |
public static String changeStringBase(String value, String sourceCharset, String targetCharset) { | |
BaseCounterGist sourceCounter = getInstance(value, sourceCharset, sourceCharset.length(), value.length()); | |
int width = 1 + (int) Math.floor(value.length() * Math.log(sourceCharset.length()) / Math.log(targetCharset.length())); | |
BaseCounterGist targetCounter = getInstance(targetCharset, width); | |
targetCounter.increment(sourceCounter.getValueBigInteger()); | |
String targetValue = stripStart(targetCounter.getValue(), Character.toString(targetCharset.charAt(0))); | |
return targetValue; | |
} | |
private BaseCounterGist(String initial, String charset, int base, int width) { | |
if(base < 2 || base > 64) | |
throw new IllegalArgumentException("base between 2 and 64 only"); | |
this.base = base; | |
if(charset.length() < base) | |
throw new IllegalArgumentException("charset smaller than base"); | |
if(!hasUniqueChars(charset)) | |
throw new IllegalArgumentException("charset must contain unique chars"); | |
this.charset = charset.substring(0, base); | |
if(width < 2 || width > 64) | |
throw new IllegalArgumentException("width must be between 2 and 64"); | |
this.width = width; | |
if(initial == null || initial.length() == 0) | |
throw new IllegalArgumentException("invalid initial value"); | |
if(!containsOnly(initial, charset.toCharArray())) | |
throw new IllegalArgumentException("invalid base for initial value"); | |
if(initial.length() > width) | |
throw new IllegalArgumentException("overflow for initial value"); | |
this.value = leftPad(initial, width, convertToChar(0)); | |
} | |
//------------------------------------------------------------------------- | |
public String increment() { | |
return increment(charset.substring(1, 2)); | |
} | |
public String increment(String increment) { | |
if(increment == null || increment.length() == 0) | |
throw new IllegalArgumentException("invalid value"); | |
if(!containsOnly(increment, charset.toCharArray())) | |
throw new IllegalArgumentException("invalid base for increment value"); | |
if(increment.length() > width) | |
throw new IllegalArgumentException("overflow for increment value"); | |
char[] valArr = reverseToArray(value); | |
char[] incArr = reverseToArray(increment); | |
final StringBuilder sb = new StringBuilder(); | |
boolean overflow = false; | |
for(int pos=0;pos<=valArr.length - 1;pos++) { | |
int temp; | |
if(incArr.length - 1 < pos) { | |
temp = convertToInt(valArr[pos]) + (overflow ? 1 : 0); | |
} else { | |
temp = convertToInt(valArr[pos]) + convertToInt(incArr[pos]) + (overflow ? 1 : 0); | |
} | |
if(temp <= base - 1) { | |
overflow = false; | |
sb.append(convertToChar(temp)); | |
} else { | |
overflow = true; | |
sb.append(convertToChar(temp - base)); | |
} | |
} | |
if(overflow == false) | |
throw new IllegalArgumentException("overflow on final value"); | |
value = sb.reverse().toString(); | |
return getValue(); | |
} | |
public String increment(long increment) { | |
return increment(convertToBase(increment)); | |
} | |
public String increment(BigInteger increment) { | |
return increment(convertToBase(increment)); | |
} | |
//------------------------------------------------------------------------- | |
public String decrement(String decrement) { | |
if(decrement == null || decrement.length() == 0) | |
throw new IllegalArgumentException("invalid value"); | |
if(!containsOnly(decrement, charset.toCharArray())) | |
throw new IllegalArgumentException("invalid base for decrement value"); | |
if(decrement.length() > width) | |
throw new IllegalArgumentException("overflow for decrement value"); | |
char[] valArr = reverseToArray(value); | |
char[] decArr = reverseToArray(decrement); | |
final StringBuilder sb = new StringBuilder(); | |
int carry = convertToInt(charset.charAt(1)) + convertToInt(charset.charAt(charset.length()-1)); | |
boolean overcarry = false; | |
for(int pos=0;pos<=valArr.length - 1;pos++) { | |
int temp; | |
if(decArr.length - 1 < pos) { | |
temp = convertToInt(valArr[pos]) - (overcarry ? 1 : 0); | |
overcarry = temp < 0 ? true : false; | |
temp = temp < 0 ? temp + carry : temp; | |
} else if(convertToInt(valArr[pos]) < convertToInt(decArr[pos])) { | |
temp = carry + convertToInt(valArr[pos]) - convertToInt(decArr[pos]) - (overcarry ? 1 : 0); | |
overcarry = true; | |
} else { | |
temp = convertToInt(valArr[pos]) - convertToInt(decArr[pos]) - (overcarry ? 1 : 0); | |
overcarry = temp < 0 ? true : false; | |
temp = temp < 0 ? temp + carry : temp; | |
} | |
sb.append(convertToChar(temp)); | |
} | |
if(overcarry == false) | |
throw new IllegalArgumentException("overflow on final value"); | |
value = sb.reverse().toString(); | |
return getValue(); | |
} | |
public String decrement(long decrement) { | |
return decrement(convertToBase(decrement)); | |
} | |
public String decrement(BigInteger decrement) { | |
return decrement(convertToBase(decrement)); | |
} | |
//------------------------------------------------------------------------- | |
private int convertToInt(char value) { | |
return charset.indexOf(value); | |
} | |
private char convertToChar(int value) { | |
return charset.charAt(value); | |
} | |
private String convertToBase(long value) { | |
final StringBuilder sb = new StringBuilder(); | |
long divisor = value; | |
while(divisor > base - 1) { | |
int remainder = (int) divisor % base; | |
sb.append(convertToChar(remainder)); | |
divisor = divisor / base; | |
} | |
sb.append(convertToChar((int) divisor)); | |
return sb.reverse().toString(); | |
} | |
private String convertToBase(BigInteger value) { | |
final StringBuilder sb = new StringBuilder(); | |
BigInteger divisor = value; | |
BigInteger bigBase = new BigInteger(Integer.toString(base)); | |
while(divisor.compareTo(bigBase.subtract(BigInteger.ONE)) == 1) { | |
BigInteger remainder = divisor.remainder(bigBase); | |
sb.append(convertToChar(remainder.intValue())); | |
divisor = divisor.divide(bigBase); | |
} | |
sb.append(convertToChar(divisor.intValue())); | |
return sb.reverse().toString(); | |
} | |
public String getValue() { | |
if(value == null || value.length() == 0) | |
throw new IllegalArgumentException("invalid value"); | |
if(value.length() < width) | |
value = leftPad(value, width, convertToChar(0)); | |
return value; | |
} | |
public long getValueLong() { | |
char[] valArr = reverseToArray(value); | |
long value = 0; | |
for(int pos=0;pos<=valArr.length - 1;pos++) { | |
int index = charset.indexOf(valArr[pos]); | |
value += Math.pow(base, pos)*index; | |
} | |
return value; | |
} | |
public BigInteger getValueBigInteger() { | |
char[] valArr = reverseToArray(value); | |
BigInteger value = BigInteger.ZERO; | |
for(int pos=0;pos<=valArr.length - 1;pos++) { | |
int index = charset.indexOf(valArr[pos]); | |
BigInteger place = new BigInteger(Integer.toString(base)); | |
place = place.pow(pos); | |
place = place.multiply(new BigInteger(Integer.toString(index))); | |
value = value.add(place); | |
} | |
return value; | |
} | |
@Override | |
public boolean hasNext() { | |
String terminal = repeat(charset.charAt(charset.length()-1), width); | |
if(terminal.equals(value)) | |
return false; | |
return true; | |
} | |
@Override | |
public String next() { | |
return increment(); | |
} | |
@Override | |
public String toString() { | |
return getValue(); | |
} | |
//------------------------------------------------------------------------- | |
public static char[] reverseToArray(final String str) { | |
if (str == null) | |
return null; | |
return new StringBuilder(str).reverse().toString().toCharArray(); | |
} | |
public static String leftSub(String str, int len) { | |
if (str == null) | |
return null; | |
if (len < 0) | |
return ""; | |
if (str.length() <= len) | |
return str; | |
return str.substring(0, len); | |
} | |
public static String leftPad(String str, int size, char padChar) { | |
if (str == null) | |
return null; | |
int padSize = size - str.length(); | |
if (padSize <= 0) | |
return str; | |
char[] buf = new char[padSize]; | |
for (int i = padSize - 1; i >= 0; i--) | |
buf[i] = padChar; | |
return new String(buf).concat(str); | |
} | |
public static String stripStart(String str, String stripChars) { | |
int strLen; | |
if (str == null || (strLen = str.length()) == 0) | |
return str; | |
int start = 0; | |
if (stripChars == null) { | |
while (start != strLen && Character.isWhitespace(str.charAt(start))) | |
start++; | |
} else if (stripChars.length() == 0) { | |
return str; | |
} else { | |
while (start != strLen && stripChars.indexOf(str.charAt(start)) != -1) | |
start++; | |
} | |
return str.substring(start); | |
} | |
public static boolean containsOnly(CharSequence sequence, char... search) { | |
if (search == null || sequence == null) | |
return false; | |
if (sequence.length() == 0) | |
return true; | |
if (search.length == 0) | |
return false; | |
return indexOfAnyExcept(sequence, search) == -1; | |
} | |
public static int indexOfAnyExcept(CharSequence sequence, char... search) { | |
if(sequence == null || sequence.length() == 0) | |
return -1; | |
if(search == null || search.length == 0) | |
return -1; | |
int csLen = sequence.length(); | |
int csLast = csLen - 1; | |
int searchLen = search.length; | |
int searchLast = searchLen - 1; | |
outer: | |
for (int i = 0; i < csLen; i++) { | |
char ch = sequence.charAt(i); | |
for (int j = 0; j < searchLen; j++) { | |
if (search[j] == ch) { | |
if (i < csLast && j < searchLast && Character.isHighSurrogate(ch)) { | |
if (search[j + 1] == sequence.charAt(i + 1)) | |
continue outer; | |
} else | |
continue outer; | |
} | |
} | |
return i; | |
} | |
return -1; | |
} | |
public static boolean hasUniqueChars(final String string) { | |
Set<Character> set = new HashSet<Character>(); | |
for(int pos = 0; pos < string.length(); pos++) { | |
char ch = string.charAt(pos); | |
if(!set.add(ch)) | |
return false; | |
} | |
return true; | |
} | |
public static String repeat(char ch, int repeat) { | |
if(repeat < 1) | |
return new String(); | |
char[] buf = new char[repeat]; | |
for (int i = repeat - 1; i >= 0; i--) | |
buf[i] = ch; | |
return new String(buf); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment