This code will "work", and in any imaginable sane use case it will work "fast enough". So I feel like my comments are pedantic; nevertheless:
Variable declarations
I think this is often a matter of opinion, but I prefer to use var
unless absolutely necessary. Specifying type is usually redundant and distracting, whereas var
conveys what the programmer actually meant: "Just make me a variable, I don't care about the type, figure out yourself which one is correct, you have type safety anyway!"
var starting = direction.Split('-')[0];
var destination = direction.Split('-')[1];
The compiler will use C#'s strict typing features to resolve the appropriate type for each variable (based on return type of string.Split
in this case) and replace the var
with string
before generating CIL bytecode. Note that this is at compile time - it is just syntactic sugar with no bearing on program logic, unlike, say, JavaScript's var
.
Calling split
twice
You split direction
twice. Since the algorithm isn't "split string, take first part, split string, take second part", the implementation is confusing to read. A more natural approach would be "split string, distribute two parts accordingly":
var parts = direction.Split('-');
var starting = parts[0];
var destination = parts[1];
This inflates your line count a bit, but it is more intuitive, and slightly more efficient (especially if there were many tokens, the code was deep inside a loop and/or the splitting pattern was a complicated Regex that takes a long time to run). Plus, you can inspect the outcome of the split easily when debugging in Visual Studio by mousing over parts
.
Inefficient consonant search
Your replace algorithm will run over the entire string. However, you don't actually care about the characters between the first and second consonants. You could implement a more appropriate algorithm:
i=0, j=0
head_found = false, tail_found = false
while !(head_found | tail_found)
if(!head_found)
if(Is_consonant(s[i])) head_found = true;
else i++
if(!tail_found)
if(Is_consonant(s[j])) tail_found = true;
else j--
return [i, j]
Your current algorithm runs in linear time (proportional to length of input strings). The solution above runs in constant time (proportional to mean of the Poisson distribution describing the number of leading/trailing vowels in city names).
For this particular case, you could probably get away with even simple recursive functions:
int Index_of_first_consonant(string s)
if(Is_consonant(s.First())) return 0
else return 1+Index_of_first_consonant(s.Substring(1))
int Index_of_last_consonant(string s)
if(Is_consonant(s.Last())) return s.Length-1
else return Index_of_last_consonant(s.Take(s.Length-1))
Inefficient lookup
For what you are doing, the typical solution is to use either Dictionary
or HashSet<char>
which includes both lowercase and uppercase consonants (the extra space usage is trivial in this case). You will have to predefine it:
private static HashSet<char> consonants = new HashSet<char>{ 'B', 'b', 'C', 'c', 'D', 'd' /* TODO: include all consonants */ };
Unfortunately var
cannot be used for fields. If you are lazy, you can add a static constructor for your class and populate this consonants
programmatically, with your inefficient vowel exclusion method (efficiency won't matter because the static constructor will run only once per program execution). But you should still use a HashSet
for tasks like these, which essentially test membership of a set.
Major bugs
Your code will crash for strings which don't have enough consonants. (Strangely named cities or erroneously provided null strings) You can fix extra whitespace with myString.Trim()
, but that won't handle symbols (for example, one way of transliterating Cyrillic names can generate words ending with '
).
You also don't handle non-letters correctly. With your example input London - Berlin
, I think the first consonant of the destination and the last consonant of the starting point will be returned as spaces.
Naming
The common convention is to use nouns for properties and fields, and verbs (often in the imperative) for methods. Also, your method Consonants
doesn't actually produce the consonants, it produces a string describing first and last consonants. Therefore the name is a bit misleading, you might consider something like Print_first_last_consonants
.
Return expression
My personal preference is to assign return values to an intermediate variable first.
var result = /* calculate result here */;
return result;
This way, when your program crashes and you come across this method while trudging through the stack, it will be easier for you to decide if the error propagated to this method or not.
Also, for the actual calculation, I would have used
startConsonants.First() + startConsonants.Last() + "-" + destConsonants.First() + destConsonants.Last()
since it is easier to read, unless you anticipate that you will be changing the output format drastically and/or frequently.
-
with spaces like in your example. Is this a bug, or did you just make a mistake when you wrote the example value? \$\endgroup\$ – Sam Jan 20 '14 at 3:46