Add sources.

This commit is contained in:
2021-05-21 20:10:51 +02:00
parent f06335a021
commit a98226dc50
12 changed files with 606 additions and 0 deletions

View File

@@ -0,0 +1,31 @@
package com.fabiosalvini;
import com.fabiosalvini.permutations.ElementsInputReader;
import com.fabiosalvini.permutations.PermutationsGenerator;
import com.fabiosalvini.permutations.PermutationsPrinter;
public class Main {
public static void main(String[] args) {
printPermutations(readElements());
}
private static long[] readElements() {
ElementsInputReader inputReader = new ElementsInputReader(System.in);
try {
return inputReader.readElements();
} catch (Exception e) {
throw new RuntimeException("Error reading elements. Format is: 123,456,789,...", e);
}
}
private static void printPermutations(long[] elements) {
try (PermutationsPrinter printer = new PermutationsPrinter(System.out)) {
PermutationsGenerator generator = new PermutationsGenerator(elements, printer);
generator.compute();
} catch (Exception e) {
throw new RuntimeException("Error printing permutations", e);
}
}
}

View File

@@ -0,0 +1,36 @@
package com.fabiosalvini.permutations;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Arrays;
/**
* Class to read the permutation elements from the given input stream.
* Elements must be parsable as long and be separated by a comma.
*/
public class ElementsInputReader {
private static final String SEPARATOR = ",";
private final InputStream inputStream;
public ElementsInputReader(InputStream inputStream) {
this.inputStream = inputStream;
}
/**
* Read the elements from the input stream.
*
* @return the array of elements.
* @throws IOException if elements cannot be read or they have the wrong format.
*/
public long[] readElements() throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
String line = reader.readLine();
return Arrays.stream(line.split(SEPARATOR))
.mapToLong(Long::parseLong)
.toArray();
}
}

View File

@@ -0,0 +1,49 @@
package com.fabiosalvini.permutations;
import java.util.function.Consumer;
import static com.fabiosalvini.utils.ArrayUtils.swap;
/**
* Class to generate all permutations of an array of elements.
* <p>
* The computation is based on Heap's algorithm (https://en.wikipedia.org/wiki/Heap%27s_algorithm).
*/
public class PermutationsGenerator {
private final long[] elements;
private final int[] indexes;
private final Consumer<long[]> consumer;
/**
* Create a new generator.
*
* @param elements the array of elements.
* @param consumer the consumer that will receive every permutation.
*/
public PermutationsGenerator(long[] elements, Consumer<long[]> consumer) {
this.elements = elements;
this.consumer = consumer;
indexes = new int[elements.length];
}
/**
* Compute all permutations using Heap's algorithm.
*/
public void compute() {
consumer.accept(elements);
int i = 0;
while (i < elements.length) {
if (indexes[i] < i) {
swap(elements, i % 2 == 0 ? 0 : indexes[i], i);
consumer.accept(elements);
indexes[i]++;
i = 0;
} else {
indexes[i] = 0;
i++;
}
}
}
}

View File

@@ -0,0 +1,59 @@
package com.fabiosalvini.permutations;
import java.io.*;
import java.util.function.Consumer;
/**
* Class to print permutations represented with arrays to the given output stream.
* Each permutation is printed in a new line with each element separated by a comma.
* <p>
* Example: [123,456,789] => "123,456,789\n" (new line separator is platform-dependent).
*/
public class PermutationsPrinter implements Consumer<long[]>, Closeable {
// Defining the separator as a constructor parameter incurs in a performance penalty.
private static final String SEPARATOR = ",";
private final BufferedWriter out;
public PermutationsPrinter(OutputStream outStream) {
this.out = new BufferedWriter(new OutputStreamWriter(outStream));
}
@Override
public void accept(long[] elements) {
String result = formatElements(elements);
try {
out.write(result);
out.newLine();
} catch (IOException e) {
throw new RuntimeException("Error writing permutation to the output stream", e);
}
}
/**
* Flush and close the output stream.
*
* @throws IOException if the stream cannot be closed.
*/
@Override
public void close() throws IOException {
out.close();
}
/**
* Format the given permutation to a string.
*
* @param elements the permutation.
* @return a string where each element is separated by {@value #SEPARATOR}.
*/
private String formatElements(long[] elements) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < elements.length - 1; i++) {
sb.append(elements[i]);
sb.append(SEPARATOR);
}
sb.append(elements[elements.length - 1]);
return sb.toString();
}
}

View File

@@ -0,0 +1,17 @@
package com.fabiosalvini.utils;
public class ArrayUtils {
/**
* In-place swap of two array elements.
*
* @param array the input array.
* @param i index of the first element.
* @param j index of the second element.
*/
public static void swap(long[] array, int i, int j) {
long tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
}

View File

@@ -0,0 +1,20 @@
package com.fabiosalvini.permutations;
import org.junit.jupiter.api.Test;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
public class ElementsInputReaderTest {
@Test
public void readElements() throws IOException {
byte[] input = "123,456,789".getBytes();
ByteArrayInputStream inputStream = new ByteArrayInputStream(input);
ElementsInputReader reader = new ElementsInputReader(inputStream);
long[] elements = reader.readElements();
assertArrayEquals(new long[]{123, 456, 789}, elements);
}
}

View File

@@ -0,0 +1,77 @@
package com.fabiosalvini.permutations;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
public class PermutationsGeneratorTest {
/**
* Generate a stream of arrays to be used in parametrized tests.
*/
@SuppressWarnings("unused")
private static Stream<long[]> elementsGenerator() {
List<long[]> elementsList = new LinkedList<>();
ArrayList<Long> elements = new ArrayList<>();
for (int i = 1; i < 10; i++) {
elements.add((long) i);
elementsList.add(
elements.stream().mapToLong(num -> num).toArray()
);
}
return elementsList.stream();
}
@ParameterizedTest
@MethodSource("elementsGenerator")
public void permutationsAreUniqueAndWithoutDuplicates(long[] elements) {
Set<String> permutations = new HashSet<>();
Consumer<long[]> consumer = (perm) -> {
assertFalse(hasDuplicates(perm), "Permutation contains duplicated elements");
String str = toString(perm);
assertFalse(permutations.contains(str), "Permutation " + str + " is duplicated");
permutations.add(str);
};
PermutationsGenerator generator = new PermutationsGenerator(elements, consumer);
generator.compute();
assertEquals(factorial(elements.length), permutations.size(), "Wrong permutations count");
}
/**
* Returns true if the input array has duplicated elements.
*/
private boolean hasDuplicates(long[] elements) {
return Arrays.stream(elements).distinct().toArray().length != elements.length;
}
/**
* Convert the input array into a string, so it can be stored in a Set to check for duplicates.
*/
private String toString(long[] elements) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < elements.length - 1; i++) {
sb.append(elements[i]);
sb.append(",");
}
sb.append(elements[elements.length - 1]);
return sb.toString();
}
/**
* Compute n!.
*/
private long factorial(int n) {
long factorial = 1;
for (int i = 1; i <= n; i++) {
factorial = factorial * i;
}
return factorial;
}
}

View File

@@ -0,0 +1,21 @@
package com.fabiosalvini.permutations;
import org.junit.jupiter.api.Test;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class PermutationsPrinterTest {
@Test
public void permutationsFormat() throws IOException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try (PermutationsPrinter printer = new PermutationsPrinter(outputStream)) {
printer.accept(new long[]{123, 456, 789});
}
String result = outputStream.toString();
assertEquals("123,456,789" + System.lineSeparator(), result);
}
}