Add sources.
This commit is contained in:
parent
f06335a021
commit
a98226dc50
31
.gitignore
vendored
31
.gitignore
vendored
|
@ -1 +1,32 @@
|
||||||
target/
|
target/
|
||||||
|
!.mvn/wrapper/maven-wrapper.jar
|
||||||
|
!**/src/main/**/target/
|
||||||
|
!**/src/test/**/target/
|
||||||
|
|
||||||
|
### STS ###
|
||||||
|
.apt_generated
|
||||||
|
.classpath
|
||||||
|
.factorypath
|
||||||
|
.project
|
||||||
|
.settings
|
||||||
|
.springBeans
|
||||||
|
.sts4-cache
|
||||||
|
|
||||||
|
### IntelliJ IDEA ###
|
||||||
|
/.idea/
|
||||||
|
*.iws
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
|
||||||
|
### NetBeans ###
|
||||||
|
/nbproject/private/
|
||||||
|
/nbbuild/
|
||||||
|
/dist/
|
||||||
|
/nbdist/
|
||||||
|
/.nb-gradle/
|
||||||
|
build/
|
||||||
|
!**/src/main/**/build/
|
||||||
|
!**/src/test/**/build/
|
||||||
|
|
||||||
|
### VS Code ###
|
||||||
|
.vscode/
|
||||||
|
|
63
Dockerfile
Normal file
63
Dockerfile
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
# Image to build the application
|
||||||
|
FROM ubuntu:bionic
|
||||||
|
|
||||||
|
RUN \
|
||||||
|
# Create builder user
|
||||||
|
groupadd builder && \
|
||||||
|
useradd builder -s /bin/bash -m -g builder -G sudo && \
|
||||||
|
echo 'builder:builder' |chpasswd && \
|
||||||
|
mkdir /home/builder/app && \
|
||||||
|
apt-get update && \
|
||||||
|
# Install utilities
|
||||||
|
apt-get install -y \
|
||||||
|
wget \
|
||||||
|
curl \
|
||||||
|
vim \
|
||||||
|
git \
|
||||||
|
zip \
|
||||||
|
bzip2 \
|
||||||
|
python \
|
||||||
|
build-essential \
|
||||||
|
software-properties-common \
|
||||||
|
sudo && \
|
||||||
|
# install OpenJDK 11
|
||||||
|
add-apt-repository ppa:openjdk-r/ppa && \
|
||||||
|
apt-get update && \
|
||||||
|
apt-get install -y openjdk-11-jdk && \
|
||||||
|
update-java-alternatives -s java-1.11.0-openjdk-amd64 && \
|
||||||
|
# Install Maven
|
||||||
|
apt-get install -y maven && \
|
||||||
|
# cleanup
|
||||||
|
apt-get clean && \
|
||||||
|
rm -rf \
|
||||||
|
/var/lib/apt/lists/* \
|
||||||
|
/tmp/* \
|
||||||
|
/var/tmp/*
|
||||||
|
|
||||||
|
RUN \
|
||||||
|
# fix builder user permissions
|
||||||
|
chown -R builder:builder \
|
||||||
|
/home/builder && \
|
||||||
|
# cleanup
|
||||||
|
rm -rf \
|
||||||
|
/home/builder/.cache/ \
|
||||||
|
/var/lib/apt/lists/* \
|
||||||
|
/tmp/* \
|
||||||
|
/var/tmp/*
|
||||||
|
|
||||||
|
USER builder
|
||||||
|
|
||||||
|
WORKDIR "/home/builder/app"
|
||||||
|
|
||||||
|
# Copy sources
|
||||||
|
COPY . /home/builder/app/
|
||||||
|
|
||||||
|
# Compile project
|
||||||
|
RUN mvn package
|
||||||
|
|
||||||
|
# Image to run the application
|
||||||
|
FROM openjdk:11.0.11-jre-slim
|
||||||
|
|
||||||
|
COPY --from=0 /home/builder/app/target/permutations.jar /app/permutations.jar
|
||||||
|
|
||||||
|
ENTRYPOINT ["java", "-jar", "/app/permutations.jar"]
|
145
README.md
Normal file
145
README.md
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
# Challenge
|
||||||
|
|
||||||
|
Technical challenge for an interview at [Credimi S.p.A.](https://www.credimi.com/)
|
||||||
|
|
||||||
|
Suppose you're given a file with a single string of comma separated numbers like the following
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ cat input
|
||||||
|
123,456,789
|
||||||
|
```
|
||||||
|
|
||||||
|
Your program must print to STDOUT all possible permutations with repetitions, like follows (actual order is not
|
||||||
|
relevant)
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ cat input | <your program> > output
|
||||||
|
$ cat output
|
||||||
|
123,456,789
|
||||||
|
123,789,456
|
||||||
|
456,789,123
|
||||||
|
456,123,789
|
||||||
|
789,123,456
|
||||||
|
789,456,123
|
||||||
|
```
|
||||||
|
|
||||||
|
Your solution must print to STDOUT and any extraneous output will be considered an error (but you may print to STDERR at
|
||||||
|
your convenience). Your program will be tested against inputs of growing size, the more they can take, the better!
|
||||||
|
|
||||||
|
The solution must contain documentation explaining time and space complexity of your implementation.
|
||||||
|
|
||||||
|
You should use only the standard library of your language of choice, any additional requirement should be documented and
|
||||||
|
motivated.
|
||||||
|
|
||||||
|
Java, Python, Scala or Rust are preferred.
|
||||||
|
|
||||||
|
# Solution
|
||||||
|
|
||||||
|
## Typo
|
||||||
|
|
||||||
|
The challenge description contains an error.
|
||||||
|
It asks to print all possible permutations WITH repetitions, but the example shows the permutations without
|
||||||
|
repetition.
|
||||||
|
If we consider permutations with repetitions, we would also need an additional parameter that is the desired length of
|
||||||
|
the permutation. The typo is probably "with repetition" instead of "without repetition".
|
||||||
|
|
||||||
|
## Algorithm
|
||||||
|
|
||||||
|
The most efficient algorithm to compute all permutations is
|
||||||
|
[Heap's Algorithm](https://en.wikipedia.org/wiki/Heap%27s_algorithm).
|
||||||
|
It minimizes elements swapping: every permutation is generated interchanging a single pair of elements from the previous
|
||||||
|
one.
|
||||||
|
|
||||||
|
Heap algorithm cannot be parallelized, we could use a less efficient algorithm with better average time assuming thread
|
||||||
|
count > 1 (see for example
|
||||||
|
[Ouellet Indexed](https://www.codeproject.com/Articles/1250925/Permutations-Fast-implementations-and-a-new-indexi)).
|
||||||
|
However, the bottleneck of our program is the writing to stdout, in some tests I saw that the multi-threaded version is
|
||||||
|
actually slower, because threads need to synchronize to write to the output.
|
||||||
|
|
||||||
|
## Choices
|
||||||
|
|
||||||
|
### Element types
|
||||||
|
|
||||||
|
The challenge description does not specify the type of the input elements, they are just "numbers".
|
||||||
|
I decided to represent them with the Java primitive type `long`.
|
||||||
|
There is no noticeable performance improvement representing them with an `int` instead.
|
||||||
|
|
||||||
|
### Repeated elements
|
||||||
|
|
||||||
|
It's not specified what is the desired output if a number is repeated.
|
||||||
|
I've decided to not check for duplicate elements, since they affect the algorithm time and space complexity.
|
||||||
|
If one or more numbers is repeated, some permutations will be duplicated:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ echo "1,1" | <my program>
|
||||||
|
1,1
|
||||||
|
1,1
|
||||||
|
```
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
I wrote the project in Java because it's the language where I have more experience.
|
||||||
|
It's a maven project that targets JDK11+.
|
||||||
|
|
||||||
|
### Time and space complexity
|
||||||
|
|
||||||
|
Given n elements, there are n! permutations. Heap's algorithm compute a new permutation in `O(1)` time. Each permutation
|
||||||
|
of n elements must also be printed to the stdout (`O(n)` time).
|
||||||
|
Time complexity is `O(n*n!)`.
|
||||||
|
|
||||||
|
The program uses two `n` sized arrays and a fixed buffer. Space complexity is `O(n)`.
|
||||||
|
|
||||||
|
### Classes
|
||||||
|
|
||||||
|
I divided the program into three classes:
|
||||||
|
|
||||||
|
- `ElementsInputReader`: to read the input elements.
|
||||||
|
- `PermutationsGenerator`: to compute all the elements permutations.
|
||||||
|
- `PermutationsPrinter`: to print permutations to the output.
|
||||||
|
|
||||||
|
### Generics
|
||||||
|
|
||||||
|
I didn't use generics to compute permutations of an array `T[]` because the program performance would be a lot worse
|
||||||
|
than using primitive types.
|
||||||
|
|
||||||
|
## How to run the application
|
||||||
|
|
||||||
|
### Compilation
|
||||||
|
|
||||||
|
If you have JDK11+ and Maven installed, you can simply run
|
||||||
|
|
||||||
|
```shell
|
||||||
|
mvn package
|
||||||
|
```
|
||||||
|
|
||||||
|
The jar file will be in `target/permutations.jar`.
|
||||||
|
|
||||||
|
Otherwise, you can use the provided Dockerfile
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker build -t permutations .
|
||||||
|
```
|
||||||
|
|
||||||
|
(Maven compilation fails if you had built the project locally, be sure to delete `target` directory).
|
||||||
|
|
||||||
|
### Run
|
||||||
|
|
||||||
|
If you have built the project using Docker, you can simply run it
|
||||||
|
|
||||||
|
```shell
|
||||||
|
echo 1,2,3 | docker run --rm -i permutations
|
||||||
|
```
|
||||||
|
|
||||||
|
However running it with Docker has a big effect on performances.
|
||||||
|
To extract the jar file, run
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker create --name permutations permutations
|
||||||
|
docker cp permutations:/app/permutations.jar .
|
||||||
|
```
|
||||||
|
|
||||||
|
Then run it with locally installed JRE (11+)
|
||||||
|
|
||||||
|
```shell
|
||||||
|
echo 1,2,3 | java -jar permutations.jar
|
||||||
|
```
|
57
pom.xml
Normal file
57
pom.xml
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>com.fabiosalvini</groupId>
|
||||||
|
<artifactId>permutations</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>11</maven.compiler.source>
|
||||||
|
<maven.compiler.target>11</maven.compiler.target>
|
||||||
|
<junit-version>5.7.2</junit-version>
|
||||||
|
<surefire-version>2.22.2</surefire-version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter-engine</artifactId>
|
||||||
|
<version>${junit-version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter-params</artifactId>
|
||||||
|
<version>${junit-version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<finalName>${artifactId}</finalName>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<version>${surefire-version}</version>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<archive>
|
||||||
|
<manifest>
|
||||||
|
<addClasspath>true</addClasspath>
|
||||||
|
<classpathPrefix>libs/</classpathPrefix>
|
||||||
|
<mainClass>com.fabiosalvini.Main</mainClass>
|
||||||
|
</manifest>
|
||||||
|
</archive>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
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);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user