Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Re-worked benchmarks and programs to print or count solutions.
  • Loading branch information
renatoathaydes committed Oct 3, 2021
1 parent 6bb754e commit c65fd3c
Show file tree
Hide file tree
Showing 8 changed files with 23,598 additions and 107 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Expand Up @@ -2,9 +2,12 @@ out/
build/
target/
*.txt
*.fasl
!dictionary.txt
!input.txt
!output.txt
!words-quarter.txt
Cargo.lock
phone_encoder
benchmark_runner
benchmark-result.csv
62 changes: 47 additions & 15 deletions benchmark.sh
Expand Up @@ -8,52 +8,84 @@ set -e
# If compilation is required, also add instructions to do that before "Generating INPUTS"
#

JVM_OPTIONS="-Xms20M -Xmx100M"

# Hack to rename the Java process to Main1 and Main2 so the printed cmd name makes more sense
echo "java \"\$@\"" > Main1
echo "java \"\$@\"" > Main2
chmod +x Main1 Main2

COMMANDS=(
"java -cp build/java Main" # Java 1
"java -cp build/java Main2" # Java 2
"sbcl --script src/lisp/main.lisp" # Common Lisp
"./phone_encoder" # Rust
"./Main1 $JVM_OPTIONS -cp build/java Main" # Java 1
"./Main2 $JVM_OPTIONS -cp build/java Main2" # Java 2
"sbcl --script src/lisp/main.fasl" # Common Lisp
"./rust" # Rust
)

echo "Compiling Java sources"
rm -rf build || true
javac src/java/*.java -d build/java
javac src/java/util/*.java -d build/util

echo "Compiling Lisp sources"
cd src/lisp/
sbcl --noinform --eval "(compile-file \"main.lisp\")" --eval "(quit)"
cd ../..

echo "Compiling Rust sources"
cd src/rust/phone_encoder && cargo build --release && cp target/release/phone_encoder ../../../
cd src/rust/phone_encoder && cargo build --release && cp target/release/phone_encoder ../../../rust
cd ../benchmark_runner && cargo build --release && cp target/release/benchmark_runner ../../../
cd ../../..

echo "Generating inputs"
INPUTS=(phones_1000.txt phones_10_000.txt phones_50_000.txt phones_100_000_with_empty.txt)
rm "${INPUTS[@]}" > /dev/null 2>&1 || true
PRINT_INPUTS=(phones_1000.txt phones_100_000.txt phones_500_000.txt phones_1_000_000_with_empty.txt)
COUNT_INPUTS=(phones_1000.txt phones_2000.txt)
java -cp "build/util" util.GeneratePhoneNumbers 1000 > phones_1000.txt
java -cp "build/util" util.GeneratePhoneNumbers 10000 > phones_10_000.txt
java -cp "build/util" util.GeneratePhoneNumbers 50000 > phones_50_000.txt
java -cp "build/util" util.GeneratePhoneNumbers 100000 "true" > phones_100_000_with_empty.txt
java -cp "build/util" util.GeneratePhoneNumbers 2000 > phones_2000.txt
java -cp "build/util" util.GeneratePhoneNumbers 100000 > phones_100_000.txt
java -cp "build/util" util.GeneratePhoneNumbers 500000 > phones_500_000.txt
java -cp "build/util" util.GeneratePhoneNumbers 1000000 "true" > phones_1_000_000_with_empty.txt

CHECK_FILE="proc_out.txt"
DEFAULT_INPUT="input.txt"
DEFAULT_OUTPUT="output.txt"
DICTIONARY="dictionary.txt"
DE_DICTIONARY="dictionary.txt"
EN_DICTIONARY="words-quarter.txt"

echo "Checking all programs for correctness"
for CMD in "${COMMANDS[@]}"
do
echo "Checking: $CMD"
$CMD $DICTIONARY $DEFAULT_INPUT > $CHECK_FILE
$CMD print $DE_DICTIONARY $DEFAULT_INPUT > $CHECK_FILE
diff -q <(sort $CHECK_FILE) <(sort $DEFAULT_OUTPUT)
echo "OK"
$CMD count $DE_DICTIONARY $DEFAULT_INPUT > $CHECK_FILE
if [ "$(cat $CHECK_FILE)" == "262" ]; then
echo "OK"
else
echo "count check failed, expected 262 but got '$(cat $CHECK_FILE)'"
exit 1
fi
done

CSV_OUT="benchmark-result.csv"

run_bench() {
DATA=$(./benchmark_runner $*)
echo "$DATA"
echo "$DATA" >> "$CSV_OUT"
}

echo "Benchmarking..."
echo "Proc,Run,Memory(bytes),Time(ms)"
echo "Proc,Run,Memory(bytes),Time(ms)" > "$CSV_OUT"

for CMD in "${COMMANDS[@]}"
do
echo "===> $CMD"
# shellcheck disable=SC2086
for file in "${INPUTS[@]}"; do ./benchmark_runner $CMD $DICTIONARY "$file"; done;
for file in "${PRINT_INPUTS[@]}"; do run_bench "$CMD print $DE_DICTIONARY $file"; done;
for file in "${COUNT_INPUTS[@]}"; do run_bench "$CMD count $DE_DICTIONARY $file"; done;
done

echo "Cleaning up"
rm "${INPUTS[@]}" "$CHECK_FILE" phone_encoder benchmark_runner
rm "${PRINT_INPUTS[@]} ${COUNT_INPUTS[@]} $CHECK_FILE ./Main1 ./Main2 ./rust ./benchmark_runner" > /dev/null 2>&1 || true
131 changes: 90 additions & 41 deletions src/java/Main.java
Expand Up @@ -11,7 +11,6 @@
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static java.nio.charset.StandardCharsets.US_ASCII;
Expand All @@ -22,26 +21,79 @@
* @author Renato Athaydes
*/
final class Main {
public static void main( String[] args ) throws IOException {
var words = new InputParser( WordsInputCleaner::clean )
.parse( new File( args.length > 0 ? args[ 0 ] : "tests/words.txt" ) );

var encoder = new PhoneNumberEncoder( words );

var printer = new BufferedWriter(new OutputStreamWriter(System.out, US_ASCII));

try (printer) {
new InputParser(PhoneNumberCleaner::clean)
.parse(new File( args.length > 1 ? args[ 1 ] : "tests/numbers.txt" ) )
.forEach( phone -> encoder.encode(phone, ( item ) -> {
try {
printer.write( item.toString() );
printer.write( '\n' );
} catch (IOException e) {
throw new RuntimeException( e );
}
}));
public static void main(String[] args) throws IOException {
if (args.length < 3) {
throw new RuntimeException("missing args: print-or-count dictionary numbers...");
}

var solutionHandler = SolutionHandler.named(args[0]);

var words = new InputParser(WordsInputCleaner::clean)
.parse(new File(args[1]));

var encoder = new PhoneNumberEncoder(words);

try {
new InputParser(PhoneNumberCleaner::clean).parse(new File(args[2]))
.forEach(phone -> encoder.encode(phone, (solution) -> {
solutionHandler.handle(phone.original(), solution);
}));
} finally {
solutionHandler.endFile();
}
}
}

interface SolutionHandler {
static SolutionHandler named(String name) {
return switch (name) {
case "print" -> new StdoutPrinter();
case "count" -> new SolutionCounter();
default -> throw new IllegalArgumentException("Unknown option");
};
}

void handle(String phoneNumber, Iterable<String> words);

void endFile();
}

final class StdoutPrinter implements SolutionHandler {
private final BufferedWriter writer =
new BufferedWriter( new OutputStreamWriter( System.out, US_ASCII ) );

@Override
public void handle(String phoneNumber, Iterable<String> words) {
try {
writer.write(phoneNumber + ": " + String.join(" ", words) + "\n");
} catch ( IOException e ) {
throw new RuntimeException( e );
}
}

@Override
public void endFile() {
try {
writer.close();
} catch ( IOException e ) {
throw new RuntimeException( e );
}
}
}

final class SolutionCounter implements SolutionHandler {
private int count;

@Override

public void handle(String phoneNumber, Iterable<String> words) {
count++;
}

@Override
public void endFile() {
System.out.println(count);
count = 0;
}
}

Expand Down Expand Up @@ -76,7 +128,7 @@ final class PhoneNumberEncoder {
//System.out.println( "Took " + ( System.currentTimeMillis() - start ) + "ms to load dictionary" );
}

void encode( Item phone, Consumer<Item> onSolution ) {
void encode( Item phone, Consumer<Iterable<String>> onSolution ) {
dictionary.forEachSolution( phone, onSolution );
}
}
Expand All @@ -99,12 +151,9 @@ static class Node {
this.digit = digit;
}

static Item toItem( List<Node> nodes, Item phone ) {
var solution = nodes.stream()
.map( node -> node.item == null ? node.digit : node.item.original() )
.map( Object::toString )
.collect( Collectors.joining( " " ) );
return new Item( phone.original(), solution );
@Override
public String toString() {
return item == null ? Integer.toString(digit) : item.original();
}
}

Expand Down Expand Up @@ -138,10 +187,14 @@ void put( char[] chars, int index, Item item ) {
}
}

void forEachSolution( Item phone, Consumer<Item> onSolution ) {
void forEachSolution( Item phone, Consumer<Iterable<String>> onSolution ) {
char[] chars = phone.result().toCharArray();
completeSolution( List.of(), chars, 0, true, solution ->
onSolution.accept( Node.toItem( solution, phone ) ) );
completeSolution( new ArrayList( 8 ), chars, 0, true, (nodes) ->
onSolution.accept( asIterable( nodes ) ));
}

private Iterable<String> asIterable( List<Node> nodes ) {
return () -> nodes.stream().map(Object::toString).iterator();
}

/**
Expand Down Expand Up @@ -169,13 +222,14 @@ boolean completeSolution( List<Node> solution,
// each word in this trie may provide a new solution
for ( Item word : trie.values ) {
wordFound = true;
var nextSolution = append( solution, new Node( word ) );
solution.add( new Node( word ) );
// accept solution if we're at the end
if ( atEndOfInput ) {
onSolution.accept( nextSolution );
onSolution.accept( solution );
} else {
root.completeSolution( nextSolution, chars, index + 1, true, onSolution );
root.completeSolution( solution, chars, index + 1, true, onSolution );
}
solution.remove(solution.size() - 1);
}
}

Expand All @@ -193,13 +247,15 @@ boolean completeSolution( List<Node> solution,

private void tryInjectDigit( List<Node> solution, char[] chars,
int index, Consumer<List<Node>> onSolution ) {
solution = append( solution, new Node( chars[ index ] - 48 ) );
solution.add( new Node( chars[ index ] - 48 ) );
// accept solution if we're at the end
if ( index + 1 == chars.length ) {
onSolution.accept( solution );
solution.remove( solution.size() - 1 );
return;
}
root.completeSolution( solution, chars, index + 1, false, onSolution );
solution.remove( solution.size() - 1 );
}

// E | J N Q | R W X | D S Y | F T | A M | C I V | B K U | L O P | G H Z
Expand All @@ -220,13 +276,6 @@ static int charToDigit( char c ) {
default -> throw new RuntimeException( "Invalid char: " + c );
};
}

static <T> List<T> append( List<T> list, T item ) {
var result = new ArrayList<T>( list.size() + 1 );
result.addAll( list );
result.add( item );
return Collections.unmodifiableList( result );
}
}

record Item(String original, String result) {
Expand Down

0 comments on commit c65fd3c

Please sign in to comment.