Strona główna > Java > Ćwiczenie z wariacją

Ćwiczenie z wariacją

Ten filmik (budzi podziw :)) zainspirował mnie do wykonania pewnego ćwiczenia. Polegało ono na odgadnięciu tego samego, ale przy pomocy komputera – zadanie jak z pierwszego roku studiów, ale potrafi przynieść satysfakcje.
W poniższej implementacji została użyta brutalna siła🙂. Generowane są zbiory możliwych kombinacji działań matematycznych (wariacja z powtórzeniami) i każdy z nich jest sprawdzany we wszystkich konfiguracjach danych liczb😉 (wariacja bez powtórzeń). Po przeliczeniu, ponad 2 milionów przypadków, otrzymałem 8 sposobów na dojście do tej liczby (tak na prawdę były 2, reszta wynikała z przemienności mnożenia). Jeden ze sposobów, inny niż na filmie, to:
(3 + 100) = 103,
(103 * 6) = 618,
(618 * 75) = 46350,
(46350 / 50) = 927,
(927 + 25) = 952

Kod realizujący to sprowadzał się do:

		BigDecimal expected = new BigDecimal(952);
		
		String[] allowedOperations = {Value.ADD, Value.MULTIPLY, Value.SUBTRACT, Value.DIVIDE, Value.DIVIDE2};
		Integer[] givenValues = {25,50,75,100,3,6};
		
		long operationCount = 1;
		long expectedCount = 0;
		for (String[] operations : Permutation.variationWithRepetition(5, allowedOperations)) {
			for (Integer[] values : Permutation.variationWithoutRepetition(6, givenValues)) {
				// wykonanie dzialania
				Value value = Value.value(values[0]);
				for (int j = 0, k = 1; j < operations.length; j++,k++) {
					value = value.compute(operations[j], values[k]);
				}
				// drukuj jesli oczekiwana
				if (expected.equals(value.value)){
					expectedCount++;
					System.out.println(value.computation);
					System.out.println(operationCount);
				}
				operationCount++;
			}
		}

		System.out.println(operationCount);
		System.out.println(expectedCount);

Gdzie klasa Value pozwalająca na zapisywanie działań w taki sposób:

Value value = value(5).compute(MULTIPLY,5).compute(SUBTRACT, 5).compute(DIVIDE, 2);

Wygląda tak:

public class Value {
	
	public static final String DIVIDE = "divide";
	public static final String DIVIDE2 = "divide2";
	public static final String MULTIPLY = "multiply";
	public static final String SUBTRACT = "subtract";
	public static final String ADD = "add";

	public final BigDecimal value;
	public final String computation;
	
	private Value(BigDecimal value, String computation) {
		this.value = value;
		this.computation = computation;
	}
	
	public static Value value(int value){
		return new Value(new BigDecimal(value),""+value);
	}

	public static void main(String[] args) {
		System.out.println("Test: "+Value.class.getSimpleName());
		
		Value value = value(5).compute(MULTIPLY,5).compute(SUBTRACT, 5).compute(DIVIDE, 2);
		
		System.out.println(value.computation);
		System.out.println(value.value);
	}

	public Value compute(String operation, int i) {
		if (this.value == null){
			// nie mozna wyknywac operacj, poprostu zwroc orginal
			// pozwala na unikniecie NPE w trakcie przetwarzania łańcucha
			return this;
		}
		BigDecimal eval = null; 
		BigDecimal value2 = new BigDecimal(i);
		String evalStrin = "";
		
		if (ADD.equals(operation)){
			
			eval = this.value.add(value2);
			evalStrin = evalStrin(this.value, "+" ,value2);
			
		} else if (SUBTRACT.equals(operation)){
			
			eval = this.value.subtract(value2);
			evalStrin = evalStrin(this.value, "-" ,value2);
			
		} else if (MULTIPLY.equals(operation)){
			
			eval = this.value.multiply(value2);
			evalStrin = evalStrin(this.value, "*" ,value2);
			
		} else if (DIVIDE.equals(operation)){
			if(BigDecimal.ZERO.compareTo(value2) != 0){
				eval = this.value.divide(value2, MathContext.DECIMAL32);
			} 
			evalStrin = evalStrin(this.value, "/" ,value2);
			
		} else if (DIVIDE2.equals(operation)){
			if(BigDecimal.ZERO.compareTo(this.value) != 0){
				eval =  value2.divide(this.value, MathContext.DECIMAL32);
			} 
			evalStrin = evalStrin(value2, "/" ,this.value); // odwrocenie
			
		}
		String computation = this.computation + "; "+ evalStrin + " = " + eval;
		Value v = new Value(eval, computation);
		return v;
	}
	
	private static String evalStrin(BigDecimal v1,String operation, BigDecimal v2){
		return "( "+v1 + " "+operation+" " + v2+" )";
	}
	
}

Klasa Permutation, generująca zbiory, została zamieszczona na końcu.

Wnioski z tej zabawy
Fajnie jest czasem pomyśleć nad innymi problemami niż te z którymi ma się zwyczajowo styczność.

Kawałek kodu do generowania możliwych zbiorów pewnie kiedyś się jeszcze przyda. Pierwsze co przychodzi do głowy to generowanie haseł🙂, ale pewnie są bardziej życiowe przykłady. Chociaż trochę dziwne że nie można czegoś takiego dla javy łatwo znaleźć (może w jakichś matematycznych bibliotekach), dla pythona od razu znalazłem przykład.

Ciekawym pomysłem byłoby aby w generatorze zbiorów dać możliwość sterowania kolejnością zwracanych rezultatów. Tak aby zbiory z wieloma powtórzeniami (*,*,*,*,*) były zwracane na końcu, podczas gdy zróżnicowane w miarę na początku (+,*,*,-,/). Jakoś powinno da się ustawić pożądaną kolejność zwracania zbiorów dla danej klasy problemu, ten facet z filmiku pewnie musi tak robić🙂.

Można stracić dużo czasu na cyzelowanie implementacji. Zwłaszcza było tak w przypadku Permutation. Kod dający rozwiązanie powstał w jakieś 4 godzin, a zabawa z nim aby go poprawić (przyspieszyć i zrobić bardziej czytelnym) odwlekła się w czasie o jakiś miesiąc😉. I tak nie jest to piękne, ale najgorszy bałagan jest na samym dole. Ciekawe czy tak praca zwróci się w postaci łatwego zrozumienia o co tu biega, jak za jakiś czas wrócę do tego kod.

public class Permutation {

	public static void main(String[] args) {
		int count = 1;
		long start = System.currentTimeMillis();
		for (Integer[] intSet : variationWithoutRepetition(6, new Integer[] { 25, 50, 75, 100, 3, 6 })) {
			System.out.println(Arrays.toString(intSet) + " - " + (count++));
		}
		System.out.println("Czas = " + (System.currentTimeMillis() - start));
	}

	public static <E> Iterable<E[]> variationWithoutRepetition(final int wordLength, final E ... values) {
		return new Iterable<E[]>() {
			public Iterator<E[]> iterator() {
				return new Variation<E>(
						new UpdateContextWithoutRepetition(values.length, wordLength) 
						, values);
			}
		};
	}
	public static <E> Iterable<E[]> variationWithRepetition(final int wordLength, final E ... values) {
		return new Iterable<E[]>() {
			public Iterator<E[]> iterator() {
				return new Variation<E>(
						new UpdateContextWithRepetition(values.length, wordLength)
						, values);
			}
		};
	}
	
	private static class Variation<E> implements Iterator<E[]>  {
		E[] dictionery = null;
		UpdateContext conetxt;
		
		private Variation(UpdateContext conetxt, E ... dict) {
			this.dictionery = dict;
			this.conetxt = conetxt;
		}

		@Override
		public void remove() {
		}
		
		@Override
		public boolean hasNext() {
			return  conetxt.hasNext();
		}

		@Override
		public E[] next() {
			return remapToDictionery(conetxt.nextWord());
		}

		private E[] remapToDictionery(int[] currentIndex) {
			E[] nextWord = create(currentIndex.length);
			for (int i = 0; i < currentIndex.length; i++) {
				nextWord[i] = dictionery[currentIndex[i]];
			}
			return nextWord;
		}

		private E[] create(int length) {
			Class<?> componentType = this.dictionery[0].getClass();
			@SuppressWarnings("unchecked")
			E[] newInstance = (E[])java.lang.reflect.Array.newInstance(componentType, length);
			return newInstance;
		}
		
	}
	
	abstract static class UpdateContext {
		int dictioneryLength;
	    int[] currentIndexWord;
		long finishCount;
		long count = 0;
		int startFromLastIndex;
		private int currentIndex;
		
		public UpdateContext(int dictioneryLength, int wordLength) {
			this.dictioneryLength = dictioneryLength;
			
			this.finishCount = this.finishCount(wordLength);
			this.startFromLastIndex = wordLength - 1;
		}
		
		public boolean hasNext() {
			return (finishCount != count);
		}
		
		public int[] nextWord() {
			if (this.currentIndexWord == null){ // start
				this.currentIndexWord = this.createStartIndexWord();
			} else {
				updateIndex(startFromLastIndex);
			}
			count++;
			return this.currentIndexWord;
		}
		
		private void updateIndex(int lastLetterInWordToChangeIndex) {
			updateCurrentIndex(lastLetterInWordToChangeIndex);
			if (isLastPosibleWord()) {
				// ostatni mozliwy index
				// ustaw na poczatek i zwieksze w poprzedzajacym
				if (lastLetterInWordToChangeIndex > 0) {
					updateIndex(lastLetterInWordToChangeIndex - 1);
				}
				// po powrocie z rekurencyjnego wywolania uaktualnij kontekst
				updateCurrentIndex(lastLetterInWordToChangeIndex);
				setLettet(getFirstPosibleLetter());
			} else {
				setLettet(getNextPosibleLetter());
			}
		}
		
		public void updateCurrentIndex(int currentIndexValue){
			currentIndex = currentIndexValue;
			update(currentIndex);
		}
		public boolean isLastPosibleWord() {
			return currentIndexWord[currentIndex] == getLastPosibleLetterInWord();
		}
		public int getNextPosibleLetter(){
			return getNextPosibleLetterForGivenIndex(currentIndex);
		}
		public int getFirstPosibleLetter() {
			return firstPosibleLetter();
		}
		public void setLettet(int letter){
			currentIndexWord[currentIndex] = letter;
		}
		
		protected abstract int firstPosibleLetter();

		protected abstract int[] createStartIndexWord();

		protected abstract int getNextPosibleLetterForGivenIndex(int lastWordToChangeIndex);

		protected abstract void update(int lastWordToChangeIndex);

		protected abstract long finishCount(int dictLength);
		
		protected abstract int getLastPosibleLetterInWord();
	}
	
	private static class UpdateContextWithoutRepetition extends UpdateContext {
		
		int[] letterThatCanBeUsed;

		public UpdateContextWithoutRepetition(int dictioneryLength, int wordLength) {
			super(dictioneryLength, wordLength);
		}
		@Override
		public int firstPosibleLetter(){
			return letterThatCanBeUsed[0];
		}
		@Override
		public int getLastPosibleLetterInWord(){
			return letterThatCanBeUsed[letterThatCanBeUsed.length - 1];
		}
		@Override
		public int getNextPosibleLetterForGivenIndex(int lastWordToChangeIndex){
			int letterIndexInPosibleWords = findItPositionInArray(this.currentIndexWord[lastWordToChangeIndex], letterThatCanBeUsed);
			return letterThatCanBeUsed[letterIndexInPosibleWords+1];
		}
		
		private static int findItPositionInArray(int it, int[] array){
			for (int i = 0; i < array.length; i++) {
				if(array[i] == it){
					return i;
				}
			}
			return -1;
		} 
		
		@Override
		protected void update(int lastWordToChangeIndex) {
			this.letterThatCanBeUsed = getLetterThatCanBeUsed(lastWordToChangeIndex, currentIndexWord);
		}
		
		@Override
		public int[] createStartIndexWord() {
			return createRange(0, dictioneryLength);
		}
		
		private static int[] createRange(int start, int to){
			int[] range = new int[to - start];
			for (int i = 0; i < range.length; i++) {
				range[i] = i+start;
			}
			return range;
		}
		
		private int[] getLetterThatCanBeUsed(int lastWordToChangeIndex, int[] indexWord) {
			int[] arrayCopyFristValuesToGivenIndex = copyToGivenIndex(indexWord,lastWordToChangeIndex);
			int[] letterThatCanBeUsed = subtractFromRange(arrayCopyFristValuesToGivenIndex);
			return letterThatCanBeUsed;
		}
		
		private static int[] copyToGivenIndex(int[] remove, int toIndex) {
			int[] dest = new int[toIndex];
			System.arraycopy(remove, 0, dest, 0, dest.length);
			return dest;
		}
		
		private int[] subtractFromRange(int[] toRemove) {
			int[] range  = createRange(0, dictioneryLength);
			int resultSize = range.length - toRemove.length;
			if (resultSize == 0) {
				return range;
			}
			int[] result = new int[resultSize];
			
			int signeAsToRemove = -1; // value not in range
			for (int asIndexInRaneg : toRemove) {
				range[asIndexInRaneg] = signeAsToRemove; 
			}
			
			int index = 0;
			for (int valueFromRange : range) {
				if (valueFromRange != signeAsToRemove){
					result[index++] = valueFromRange;
				}
			}
			
			return result;
		}
		
		@Override
		public long finishCount(int dictLength){
			return factorial(dictioneryLength) / factorial(dictioneryLength - dictLength);
		}
		
		private static long factorial(int value) {
			long accumulator = 1L;
			for (long counter = value; ! (counter == 0); counter--) {
				accumulator = (counter * accumulator);
			}
			return accumulator;
		}
		
	}
	
	private static class UpdateContextWithRepetition extends UpdateContext {
		public UpdateContextWithRepetition(int dictioneryLength, int wordLength) {
			super(dictioneryLength, wordLength);
		}
		@Override
		public int firstPosibleLetter() {
			return 0;
		}
		@Override
		public int getLastPosibleLetterInWord() {
			return dictioneryLength - 1;
		}
		@Override
		public int getNextPosibleLetterForGivenIndex(int lastWordToChangeIndex) {
			return this.currentIndexWord[lastWordToChangeIndex] + 1;
		}
		@Override
		protected void update(int lastWordToChangeIndex) {
			// nic nie trzeba robic
		}
		@Override
		public long finishCount(int dictLength){
			return (long)Math.pow(dictioneryLength, dictLength);
		}
		@Override
		public int[] createStartIndexWord() {
			int[] startWord = new int[dictioneryLength];
			for (int i = 0; i < startWord.length; i++) {
				startWord[i] = 0;
			}
			return startWord;
		}
		
	}
	
}
Kategorie:Java Tags: ,
  1. Brak komentarzy.
  1. No trackbacks yet.

Skomentuj

Wprowadź swoje dane lub kliknij jedną z tych ikon, aby się zalogować:

Logo WordPress.com

Komentujesz korzystając z konta WordPress.com. Log Out / Zmień )

Zdjęcie z Twittera

Komentujesz korzystając z konta Twitter. Log Out / Zmień )

Facebook photo

Komentujesz korzystając z konta Facebook. Log Out / Zmień )

Google+ photo

Komentujesz korzystając z konta Google+. Log Out / Zmień )

Connecting to %s

%d bloggers like this: