Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CS Games 2023 Team A Application in C++ and/or Python #11

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
24 changes: 20 additions & 4 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,31 @@

PLEASE FILL IN THE FOLLOWING!

> ok

## Full Name
John Johnson

Cole Fuerth

## UWindsor Email
[email protected]

[[email protected]](mailto:[email protected])

## Application Form
- [ ] I certify I have submitted the [application form](https://forms.office.com/r/R4A1JyB3Xf)

- [x] I certify I have submitted the [application form](https://forms.office.com/r/R4A1JyB3Xf)

## Briefly explain how your solution works and how to run it

My solution...
### Brief

I first developed my solution in Python, then remade it in C++ for speed. **I would prefer the C++ solution to be considered, since it is considerably faster**, but it should still be noted that the Python solution came first, and runs almost as fast. The Python is also (understandably) much easier to read and understand, since the solutions are very much the same algorithmically.
### How it works

My solution first tries to find exceptional cases that clearly will not fit first, then picks the better order of characters of the word (reversing the word will not change whether it fits the puzzle, and the order with the least starting points runs exponentially faster). Once it is known all the characters in the string appear on the board frequent enough to potentially fit, and that we have the optimal order of characters, a **depth first** approach is employed. Using sets to keep track of points, the board, and history, we can keep track of everything using set operations. `dfs` exhaustively tries all possible paths for the string, until eventually either fitting or exhausting all possibilities.

### Running the code

On **leetcode** (how I assume you are testing), you can copy and paste either of the solutions that are found under the **leetcode** folder directly onto the site. The C++ solution is in the top 1.5% of submissions; the Python solution is in the top 10% for runtime.

**Locally**, I am testing using `make` with the provided Makefile, or `python3 solution.py`. The test case is hard coded into `main` on each of the source files in the root directory.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.vscode/
solution
submissionCode*
11 changes: 11 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@

all: build run

build:
g++ -o solution solution.cpp -Wall -Wextra

run: build
./solution

clean:
rm -f solution
103 changes: 103 additions & 0 deletions leetcode/solution.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@

// include sets
#include <set>
#include <map>
#include <iterator>
#include <vector>
#include <iostream>
#include <string>
#include <sstream>
#include <algorithm>

using namespace std;
typedef pair<int, int> pairs;
typedef unsigned int uint;

/**
* @brief depth first search to find the word. For each potential point, we recursively scheck valid next points that are not visited yet.
* @param board: A list of pair coordinates to check for the next move in s
* @param s: The string to check for
* @param board: A map of the board to check for the next move in s
* @param path: The path taken so far, do not repeat
* @param boardKeys: The keys of the board, used to find adjacent points
* @return: A boolean indicating if the string can be found on the board
*/
bool dfs(set<pairs> &points, string s, map<pairs, char> &board, set<pairs> &path, set<pairs> &boardKeys)
{
if (s.length() == 0)
return true;
for (auto it : points)
{
if (board[it] == s[0])
{
path.insert(it);
set<pairs> adj;
adj.insert(pairs(it.first - 1, it.second));
adj.insert(pairs(it.first + 1, it.second));
adj.insert(pairs(it.first, it.second - 1));
adj.insert(pairs(it.first, it.second + 1));
set<pairs> intersection;
set_intersection(adj.begin(), adj.end(), boardKeys.begin(), boardKeys.end(), inserter(intersection, intersection.begin()));
set<pairs> new_points;
set_difference(intersection.begin(), intersection.end(), path.begin(), path.end(), inserter(new_points, new_points.begin()));
if (dfs(new_points, s.substr(1), board, path, boardKeys))
return true;
path.erase(it);
}
}
return false;
}

class Solution
{
public:
bool exist(vector<vector<char>> &board, string word)
{
// reverse the word
string rword = word;
reverse(rword.begin(), rword.end());

set<pairs> points; // points to check for the next move
set<pairs> boardKeys; // keys of the board, for checking for valid adjacent points
map<pairs, char> board_map; // coordinate -> char map of the board
map<char, int> board_char_count; // char -> count map of the board
for (uint i = 0; i < board.size(); i++)
{
for (uint j = 0; j < board[i].size(); j++)
{
points.insert(make_pair(i, j));
board_map[make_pair(i, j)] = board[i][j];
boardKeys.insert(make_pair(i, j));
if (board_char_count.find(board[i][j]) == board_char_count.end())
board_char_count[board[i][j]] = 1;
else
board_char_count[board[i][j]]++;
}
}
set<pairs> path;

map<char, int> word_char_count;
for (uint i = 0; i < word.length(); i++)
{
if (word_char_count.find(word[i]) == word_char_count.end())
word_char_count[word[i]] = 1;
else
word_char_count[word[i]]++;
}

// check the case where any character in the word does not appear on the board
for (uint i = 0; i < word.size(); i++)
{
if (board_char_count.find(word[i]) == board_char_count.end())
return false;
else if (board_char_count[word[i]] < word_char_count[word[i]])
return false;
}

// use word if the first character has less occurences than the reverse of the word
if (board_char_count[word[0]] > board_char_count[rword[0]])
word = rword;

return dfs(points, word, board_map, path, boardKeys);
}
};
45 changes: 45 additions & 0 deletions leetcode/solution.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@

from itertools import product

def search(points: set, s: str, board: dict, path: set = None) -> bool:
# points is a list of tuples (x, y) for points to try to connect to the first character of s
# s is the string to connect, check the list of points to see if they can be connected to form s
# board is the board of characters
# path is a list of points that have been used to connect to s so far
# return True if s can be connected, False otherwise
if path is None:
path = set()
if len(s) == 0:
return True
for p in points:
if s[0] == board[p]:
path.add(p)
# list of points left, right, up, down from p that are in the board keyset
adj = set([(p[0] + 1, p[1]), (p[0] - 1, p[1]), (p[0], p[1] + 1),
(p[0], p[1] - 1)]).intersection(board.keys()).difference(path)
if search(adj, s[1:], board, path):
return True
path.remove(p)
return False

class Solution:
def exist(self, board: List[List[str]], word: str) -> bool:
# convert board to a dictionary of (x, y) -> character
board = {(x, y): board[x][y] for x, y in product(
range(len(board)), range(len(board[0])))}

# if any char in the word is not in the board, return False
if not set(word).issubset(set(board.values())):
return False

# if there are less occurences of the last character than the first character, reverse the word
boardstr = ''.join(board.values())
if boardstr.count(word[0]) > boardstr.count(word[-1]):
word = word[::-1]

# you take the input, and see if for each character, you can follow a path from character to character. The same character cannot be used more than once
return search(
set(board.keys()),
word,
board
)
138 changes: 138 additions & 0 deletions solution.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@

// include sets
#include <set>
#include <map>
#include <iterator>
#include <vector>
#include <iostream>
#include <string>
#include <sstream>
#include <algorithm>

using namespace std;
typedef pair<int, int> pairs;
typedef unsigned int uint;

/**
* @brief depth first search to find the word. For each potential point, we recursively scheck valid next points that are not visited yet.
* @param board: A list of pair coordinates to check for the next move in s
* @param s: The string to check for
* @param board: A map of the board to check for the next move in s
* @param path: The path taken so far, do not repeat
* @param boardKeys: The keys of the board, used to find adjacent points
* @return: A boolean indicating if the string can be found on the board
*/
bool dfs(set<pairs> &points, string s, map<pairs, char> &board, set<pairs> &path, set<pairs> &boardKeys)
{
if (s.length() == 0)
return true;
for (auto it : points)
{
if (board[it] == s[0])
{
path.insert(it);
set<pairs> adj;
adj.insert(pairs(it.first - 1, it.second));
adj.insert(pairs(it.first + 1, it.second));
adj.insert(pairs(it.first, it.second - 1));
adj.insert(pairs(it.first, it.second + 1));
set<pairs> intersection;
set_intersection(adj.begin(), adj.end(), boardKeys.begin(), boardKeys.end(), inserter(intersection, intersection.begin()));
set<pairs> new_points;
set_difference(intersection.begin(), intersection.end(), path.begin(), path.end(), inserter(new_points, new_points.begin()));
if (dfs(new_points, s.substr(1), board, path, boardKeys))
return true;
path.erase(it);
}
}
return false;
}

bool exist(vector<vector<char>> &board, string word)
{
// reverse the word
string rword = word;
reverse(rword.begin(), rword.end());

set<pairs> points; // points to check for the next move
set<pairs> boardKeys; // keys of the board, for checking for valid adjacent points
map<pairs, char> board_map; // coordinate -> char map of the board
map<char, int> board_char_count; // char -> count map of the board
for (uint i = 0; i < board.size(); i++)
{
for (uint j = 0; j < board[i].size(); j++)
{
points.insert(make_pair(i, j));
board_map[make_pair(i, j)] = board[i][j];
boardKeys.insert(make_pair(i, j));
if (board_char_count.find(board[i][j]) == board_char_count.end())
board_char_count[board[i][j]] = 1;
else
board_char_count[board[i][j]]++;
}
}
set<pairs> path;

map<char, int> word_char_count;
for (uint i = 0; i < word.length(); i++)
{
if (word_char_count.find(word[i]) == word_char_count.end())
word_char_count[word[i]] = 1;
else
word_char_count[word[i]]++;
}

// check the case where any character in the word does not appear on the board
for (uint i = 0; i < word.size(); i++)
{
if (board_char_count.find(word[i]) == board_char_count.end())
return false;
else if (board_char_count[word[i]] < word_char_count[word[i]])
return false;
}

// use word if the first character has less occurences than the reverse of the word
if (board_char_count[word[0]] > board_char_count[rword[0]])
word = rword;

return dfs(points, word, board_map, path, boardKeys);
}

int main(void)
{
// assume input comes from stdin
// board input is single characters, space separated for columns and newlines for rows
// word is on its own line
// example input:
// ```
// A B C E
// S F C S
// A D E E
// word
// ```

// read board and word
vector<vector<char>> board = {{'A', 'A', 'A', 'A', 'A', 'A'},
{'A', 'A', 'A', 'A', 'A', 'A'},
{'A', 'A', 'A', 'A', 'A', 'A'},
{'A', 'A', 'A', 'A', 'A', 'A'},
{'A', 'A', 'A', 'A', 'A', 'B'},
{'A', 'A', 'A', 'A', 'B', 'A'}};

string word = "AAAAAAAAAAAAABB";

// print the board and word
cout << "Board:" << endl;
for (uint i = 0; i < board.size(); i++)
{
for (uint j = 0; j < board[i].size(); j++)
{
cout << board[i][j];
}
cout << endl;
}
cout << "Word: " << word << endl;

// print result
cout << exist(board, word) << endl;
}
Loading