Add sources.
This commit is contained in:
parent
f06335a021
commit
a98226dc50
31
.gitignore
vendored
31
.gitignore
vendored
|
@ -1 +1,32 @@
|
|||
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