Skip to content

Instantly share code, notes, and snippets.

@therohk
Last active August 5, 2021 17:29
Show Gist options
  • Save therohk/5fe639cfcf7e53c6e100a98ecf020ef3 to your computer and use it in GitHub Desktop.
Save therohk/5fe639cfcf7e53c6e100a98ecf020ef3 to your computer and use it in GitHub Desktop.
Counter for any base representation with provided charset
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