Add sources.
This commit is contained in:
31
src/main/java/com/fabiosalvini/Main.java
Normal file
31
src/main/java/com/fabiosalvini/Main.java
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
17
src/main/java/com/fabiosalvini/utils/ArrayUtils.java
Normal file
17
src/main/java/com/fabiosalvini/utils/ArrayUtils.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user