Although this question already has been answered I will add another answer at a higher abstraction layer.
Your Code
In short and blunt: I would not hire you with this code. The code is MUCH to complex and reeks of bugs (because of the complexity). Also you use a stateful class for a task that needs no persistent state over multiple invocations.
Please let me show you what I would expect from you and why.
Interviews
The point with interview questions is not only to find out wether you know the basics of a language. The main point of an interview is to find out if you would be good at the job.
So first, you will answer the question. Then the next question will very likely be, what are the advantages and disadvantages of your solution?
Answering the question: Loop
(sorry for syntax errors -- my last C was a long time ago)
The request to not [to] use additional data structures basically rules out all other answers here. A new array is still a data structure, albeit a very simple structure.
If you were to restrict yourself to primitives only you need to loop:
bool isOnlyUniqueValues(char[] test, int test_length) {
// Test for each value in the test vector if a duplicate exists.
// i/k or „candidate_index/search_index for variable names is
// a matter of taste for such short algorithms
for (int i=0; i < test_length; i++) {
for (int k=i + 1; k < test_length; k++) {
if (test[i] == test[k]) return false;
}
}
return true;
}
This loop takes each character in the string and compares it to all the other characters in the string.
For example ("." is a placeholder for unique chars):
AA.. // <- The duplicate will be detected after one step (i=0, k=1)
0123
.A.A // <- The duplicate will be detected after 3 + 2 = 5 steps (i=1, k=3)
0123
..AA // <- The duplicate will be detected after 3 + 2 = 5 steps (i=2, k=3)
0123
.... // <- Uniqueness will be detected after 3 + 2 + 1 = 6 steps (i=2, k=3)
0123
....... // <- Uniqueness will be detected after 55 steps (i=8, k=9)
0123..9
What are the advantages and disadvantages of the solution?
This solution works well for small datasets (up to a few hundred chars in the string).
PRO:
- Easy to read
- For small datasets (a few hundred bytes) very cache efficient and probably the fastest solution
- No additional data structure
CON:
- Runtime of O(n^2), slows down with long (thousands of items) test vectors
- Worst case behaviour on unique items
- Fluctuating execution time (could be a problem in realtime or near realtime scenarios )
How would you improve the solution?
This is the interesting question! It shows if you find a solution to a problem or are just a "code monkey" that needs to be told every step of the solution.
First ask some questions (and these are always the same):
- What is the (business) purpose of this function?
- How large are the test data sets?
- How frequent is it going to be executed?
There are other questions to be asked (Is this timing sensitive?, ...) but these three suffice here.
The (business) purpose and context
This will give you the context your code will under and which problem it should solve for the business. Remember: The best code is the code that does not need to be written! Maybe there are other solutions to the business problem.
This will you also give you an idea of your runtime context. Is this a server? Then RAM will probably much less of a problem than on an ATTiny.
Don't overdo this (esp. in an interview), but asking shows that you are capable of more than writing curly braces.
Size of the data set
This is a very important question. For small sizes you very likely will prefer easy to read code over overly complicated code. The larger the dataset, the more important the runtime complexity of your code:
- Nested loops: O(n^2)
- Set: O(n)
- Filter (Bloom, Cuckoo, HyperLogLog ...)
In this case the size has the length of the alphabet (e.g. 255 for a string of bytes) as upper bound. Such a short alphabet case would make option #1 the best solution hands down.
Frequency of execution
If this code is called once per month then efficiency is probably not the main concern. If this code is in a timing sensitive code path (e.g. part of a web request) then performance is a must.
Other solutions
Here are the three solutions.
Plain nested loops
This is the solution presented. It meets all stated requirement (functionally: duplicates are found. No additional data structures or memory).
Set
A solution with a set works like the "map" solution from your example, although there is one difference:
A map basically maps Keys to Values (K -> V). A set is a unique set of values. Using a map really just exploits the fact that a maps keys form a set.
To rephrase the interview question: Is this string a set?.
bool isOnlyUniqueValues(char[] test, int test_length) {
// I assume there is a std:set?
std:set test_as_set = ...;
for (int i=0; i < test_length; i++) {
if (test_as_set.contains(test[i]){ return false;}
test_as_set.put(test[i]);
}
return true;
}
PRO:
- Easy to read
- For small to largish datasets (a few thousand bytes) efficient
- Only simple additional data structure
CON:
- Memory overhead of O(n)
- Uses additional data structures
Filter
Filters are "approximate set membership data structures". The important word here is approximate.
The main differences versus the Set solution are
- Filters are extremely fast and only use small(ish) amounts of memory
- Filters can give you a false positive answer
- Filters a tuneable with respect to the "false positive" rate
The „might contain“ method of a filter can sometimes return true when the item is not in the set. If it returns false the value is guaranteed to be absent.
For details look at the Wikipedia page for Bloom Filter.
bool isOnlyUniqueValues(char[] test, int test_length) {
// I assume there is a Filter implementation
bloom_filter filter = ...; // here you also configure the error rate
for (int i=0; i < test_length; i++) {
// Closely look: The method is "might_contain"
if (bloom_filter.might_contain(test[i]){ return false;}
bloom_filter.put(test[i]);
}
return true;
}
PRO:
- Easy to read
- Efficient for extremely large datasets
- Good candidate for embedded systems (trade very small error rates for large performance and size improvements)
CON:
- Memory overhead
- Uses additional data structures
- Considered "advantaged" by most devs
- Overkill for most use cases
- Only gives probabilistic answers
'a'
and'A'
the same? Is'\0'
or similar allowed as valid characters? What counts asdata structure
(e.g. what about arrays)? The problem description is far too open to interpretation to be meaningful, and without some more context it's hard if not impossible to verify whether the implementation actually solves the problem. \$\endgroup\$