Skip to content

Commit

Permalink
Polish
Browse files Browse the repository at this point in the history
  • Loading branch information
trishume committed Mar 10, 2019
1 parent 86ab71e commit 6b57dd2
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 20 deletions.
13 changes: 13 additions & 0 deletions Readme.md
@@ -0,0 +1,13 @@
# Portmanteau Generator

Finds a portmanteau between two words using a graph search that finds words to link between them!

It favours larger overlaps between words, and tries first with common words and falls back to uncommon ones, for only the highest quality portmanteaus.

## How I built it

It's a Rust program that hosts a web app. The searching is done on the backend using a custom graph search implementation and prefix data store to link words together quickly.

## Why though

Made for TerribleHack XIII, the premier terrible ideas hackathon in Waterloo.
8 changes: 4 additions & 4 deletions src/data.rs
Expand Up @@ -4,8 +4,8 @@ pub struct PrefixBase {
data: HashMap<String, Vec<usize>>,
}

static MIN_PREFIX : usize = 2;
static MAX_PREFIX : usize = 5;
pub static MIN_PREFIX : usize = 2;
pub static MAX_PREFIX : usize = 6;

impl PrefixBase {
pub fn new(words: &[String]) -> Self {
Expand All @@ -22,15 +22,15 @@ impl PrefixBase {
PrefixBase { data }
}

pub fn successors(&self, s: &str, mut f: impl FnMut(usize)) {
pub fn successors(&self, s: &str, mut f: impl FnMut(usize, usize)) {
for len in MIN_PREFIX..MAX_PREFIX {
let s_len = s.len();
if len > s_len {
return;
}
if let Some(words) = self.data.get(&s[s_len-len..]) {
for w in words.iter().cloned() {
f(w)
f(w, len)
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/main.rs
Expand Up @@ -65,6 +65,6 @@ fn main() {
.unwrap()
.start();

println!("Started http server: 127.0.0.1:8080");
println!("Started http server: http://127.0.0.1:8080");
let _ = sys.run();
}
60 changes: 45 additions & 15 deletions src/search.rs
@@ -1,6 +1,7 @@
use crate::data::{PrefixBase, has_overlap};
use std::collections::{VecDeque, HashMap};
use std::collections::hash_map::Entry;
use crate::data::{PrefixBase, has_overlap, MAX_PREFIX};
use std::collections::{HashMap, BinaryHeap};
use std::cmp::Ordering;
use std::usize;

#[derive(Debug)]
pub enum SearchError {
Expand Down Expand Up @@ -62,39 +63,59 @@ impl Path {
}
}

#[derive(PartialEq, Eq)]
struct InvertOrder(usize);

impl PartialOrd for InvertOrder {
fn partial_cmp(&self, other: &InvertOrder) -> Option<Ordering> {
Some(other.0.cmp(&self.0))
}
}

impl Ord for InvertOrder {
fn cmp(&self, other: &InvertOrder) -> Ordering {
other.0.cmp(&self.0)
}
}

pub struct Searcher<'a> {
prefixes: &'a PrefixBase,
words: &'a [String],

q: VecDeque<usize>,
q: BinaryHeap<(InvertOrder, usize)>,
preds: HashMap<usize, Option<usize>>,
dists: Vec<usize>,
}

impl<'a> Searcher<'a> {
pub fn new(prefixes: &'a PrefixBase, words: &'a [String]) -> Self {
Searcher {
prefixes, words,
q: VecDeque::new(),
q: BinaryHeap::new(),
preds: HashMap::new(),
dists: (0..words.len()).map(|_| usize::MAX).collect(),
prefixes, words,
}
}

pub fn search(&mut self, start: &str, target: &str) -> Result<Path,SearchError> {
self.push_succs(start, None);
self.push_succs(start, 0, None);

let final_i = loop {
let i = if let Some(i) = self.q.pop_front() {
i
let (InvertOrder(d),i) = if let Some((d,i)) = self.q.pop() {
(d,i)
} else {
return Err(SearchError::NoPath)
};

// we may have found a better way here earlier
if d > self.dists[i] { continue; }

let word = &self.words[i];
if has_overlap(word, target) {
break i;
}

self.push_succs(word, Some(i));
self.push_succs(word, d, Some(i));
};

let mut path = vec![target.to_string()];
Expand All @@ -113,11 +134,20 @@ impl<'a> Searcher<'a> {
Ok(Path {words: path})
}

fn push_succs(&mut self, s: &str, i: Option<usize>) {
self.prefixes.successors(s, |wi| {
if let Entry::Vacant(v) = self.preds.entry(wi) {
v.insert(i);
self.q.push_back(wi);
fn push_succs(&mut self, s: &str, base_dist: usize, i: Option<usize>) {
self.prefixes.successors(s, |wi, len| {
// let weight = 1 + MAX_PREFIX - len;
let weight = match len {
1 => 100,
2 => 10,
3 => 2,
_ => 1
};
let next_dist = base_dist + weight;
if next_dist < self.dists[wi] {
self.dists[wi] = next_dist;
self.preds.insert(wi, i);
self.q.push((InvertOrder(next_dist), wi));
}
});
}
Expand Down
4 changes: 4 additions & 0 deletions templates/index.html
Expand Up @@ -4,12 +4,16 @@ <h2>Welcome!</h2>
<p>
Find a portmanteau between two words using a graph search that finds words to link between them!
</p>
<p>
It favours larger overlaps between words, and tries first with common words and falls back to uncommon ones, for only the highest quality portmanteaus.
</p>
<p>
Made for <a href="http://terriblehack.website">TerribleHack XIII</a>, the premier terrible ideas hackathon in Waterloo.
</p>
<h2>Examples</h2>
<ul>
<li><a href="/?start=terriblehack&end=portmanteau">terriblehack and portmanteau</a></li>
<li><a href="/?start=magical&end=walrus">magical and walrus</a></li>
<li><a href="/?start=portmanteau&end=generator">portmanteau and generator</a></li>
</ul>
{% endblock content %}

0 comments on commit 6b57dd2

Please sign in to comment.