Skip to content

Conversation

@MayssaraOmar
Copy link
Collaborator

@MayssaraOmar MayssaraOmar commented Jun 19, 2018

This is the first time I use unique pointers instead of raw pointers to build a data structure. I want to know if this is the best way to use them, what are the privileges of using smart pointers in general and when should I use smart pointers over raw pointers and vice versa, whether in building data structures or in general.

@flerdacodeu flerdacodeu self-requested a review June 21, 2018 12:56
@flerdacodeu
Copy link
Owner

Really sorry for the delay on this! I had messed up with my comments.

In terms of the use of smart points to manage your data structure, I think you did a great job.

The main advantage of smart pointers (in my opinion) is that you avoid accidentally forgetting to delete things.

In this specific case, you could simply ignore the destructor (which could have ended up as a complex piece of code traversing the data structure) and your remove methods are a bit simpler to.

The advantages are usually more evident where you have large data structures that you might need to clean up in non-trivial ways, if there are various ways to modify them, or in case of exceptional behavior. The last one, exceptional behavior, is something you did not have here, so let me give you more details.

Let's assume that you need to allocate an object for your function: you might end up storing the object by the end of the function, but you might also return early or throw an exception; if you do have this behavior (something that happens only on special cases), then you need to free allocated object in these special code paths, but it is really easy to forget to do so.

bool findWords(const Grid& grid, vector<string>* words) {
  Dictionary* dic = new Dictionary();
  if (!loadDictionary(dic)) {
    return false;
  }
  return findWordsInDirectionary(grid, *dic, words);
}

Now, if you cannot load the dictionary object, you will leak that object, as you forgot to call delete. The same code with a smart pointer will work correctly by deleting the object. This is not a perfect example (you could have had the dictionary instead of a pointer to id), but in some cases it is not always possible to do so.

Essentially smart pointers are a way to simplify your code, as the smart pointer takes care of many of the things that are needed for you.

@@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems you accidentally added a number of files that are configuration files for your IDE.
I would suggest you delete those.

using namespace std;

void get_words_from_grid(vector<vector<char> > &grid, int row, int col,
string word, map<pair<int, int>, bool> &vis,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional: I would avoid shortening variable names like "visited" to "vis", because it makes the code harder to read.


void get_words_from_grid(vector<vector<char> > &grid, int row, int col,
string word, map<pair<int, int>, bool> &vis,
map<pair<pair<int, int>, string>, bool> &memo, dictionary &dic,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"memo" is not very clear in this context: what is this supposed to represent?
You can add this as a comment to the function.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For example the following grid with the word "match" in dictionary:
a a r
t m d
c h x
We can obtain the word "match" by 2 ways:
1- by going from 'm' to 'a' upwards and then 't', 'c', 'h'
2- by going from 'm' to 'a' diagonally and then 't', 'c', 'h'
Here the ('t', 'c', 'h') is the repeated work as we call the function with the same input (row = 1, col = 0, word = "ca") twice.
We avoid repeating work by marking (1,0,"ca") true, in the second call, (1,0,"ca") will be true and the function will return.

void get_words_from_grid(vector<vector<char> > &grid, int row, int col,
string word, map<pair<int, int>, bool> &vis,
map<pair<pair<int, int>, string>, bool> &memo, dictionary &dic,
unordered_set<string> &words) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style: A common convention is to make output parameters (parameters that are outputs or modified by a function) pointers rather than references.

return;
}

if (dic.isWord(word + grid[row][col])) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This depends on exactly how isPrefix() works, but it is possible it would not return true for a completed word that is a prefix of another word. In that case, I would suggest checked isWord() first.


#include <iostream>
#include "test_dictionary.h"
#include "test_wordSearch.h"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional: it is often the case that tests are put in the same file as the main function that invoked them.

empty_dictionary();
}
void test_dictionary::insert_oneword() {
dic.insert("hello");
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is often the case that in your test you want to check that things behaved correctly.
For example, here you could say:

   dic.insert("hello");
   EXPECT_TRUE(dic.isWord("hello"));
   EXPECT_TRUE(dic.isPrefix("h"));
   EXPECT_TRUE(dic.isPrefix("he"));
   EXPECT_TRUE(dic.isPrefix("hel"));
   EXPECT_TRUE(dic.isPrefix("hell"));
   EXPECT_TRUE(dic.isPrefix("hello"));

Otherwise your test runs the code but it does not check that things worked.
This shows how it is not always possible to test functions completely in isolation.


dictionary test_dictionary::dic;

void test_dictionary::run_all_tests() {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One problem with the current test setup is that all tests share the same dictionary, which means the order you run the tests has an effect on the result.
An alternative would be to make the test class have non-static methods and for each test you create an instance.

It would then look something like:

void test_dictionary::run_all_tests() {
  new test_directionary().insert_oneword();
  new test_directionary().insert_vector();
  ...
}


#include "dictionary.h"

class test_dictionary {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style: Generally classes are named in CamelCase, so I would suggest DictionaryTests for this class.

dic.insert("cat");
dic.insert("m");
}
void test_wordSearch::run_all_tests() {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly to above, it would make sense to run all the tests on a different instance to avoid interactions between them.

Since you have a setup method you could:

  • call setup() in the constructor
  • call setup() explicitly for each test

For the latter on, you could use:

test_wordSearch& test_WordSearch::setup() {
  ...
  return *this;
}

...


void test_wordSearch::run_all_tests() {
  new test_wordSearch().setup().empty_grid();
  new test_wordSearch().setup().one_cell_grid_with_match_in_dictionary();
  ...
}

Does this make sense?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what I understood:

1- If I call setup() in the constructor, then all tests still share the same dictionary and there are interactions between tests(unless I'm making a new instance for each test).

2- I don't need to make a different instance for each test if each test is completely independent (has its own dictionary), for example calling setup(dictionary*) explicitly for each test with its own dictionary.

I think method 2 is simpler as I don't have the responsibility of freeing the memory I allocated for each test.

Copy link
Collaborator

@ssancho2 ssancho2 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! You used unique_ptr ! One more thing to add to what Flavio said: unique_ptr has a strong semantic connotation. If you see a raw pointer, who knows when (or even if) it will be deleted, who is responsible for doing that, who owns the object? The unique_ptr makes all that explicit.

dictionary dic;
setup(&dic);
// build grid
vector<vector<char> > grid(3);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be done like:
vector<vector<char> > grid = { {'a', 'a', 'r'}, {'t', 'c', 'D'}} ;

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My code doesn't compile with this kind of vector initialization :/ I'll leave it as it is for now and I'll look into the problem

static void get_words_from_grid(const vector<vector<char> > &grid, int row, int col,
string prefix, map<pair<int, int>, bool>* visited,
map<pair<pair<int, int>, string>, bool>* cache, const dictionary &dic,
unordered_set<string>* words) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some comments on the function / parameters:
I like the cache, avoiding checking twice the same thing, but why do you think is important? How likely it's that you will hit it?
I think the visited parameter will be slightly better (more performant) as a vector<vector>

Copy link
Collaborator Author

@MayssaraOmar MayssaraOmar Jul 2, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ssancho2 I think it depends on the type of inputs, if there is a lot of duplicates nearby in the grid, then the cache might be useful to optimize time. However, in all other types of input, it reserves unnecessary memory.

for (int j = -1; j <= 1; j++) {
if (!vis[make_pair(row + i, col + j)]) {
vis[make_pair(row + i, col + j)] = true;
if (!(*visited)[make_pair(row + i, col + j)]) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe you can keep make_pair(row + i, col + j) in a variable.

words.insert(word + grid[row][col]);
// word exits in dictionary
if (dic.isWord(prefix + grid[row][col])) {
words->insert(prefix + grid[row][col]);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are doing prefix + grid[row][col] in several places, may be easier to do prefix += grid[row][col]; before this if

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants