Show Lecture.DRY as a slide show.
☔ ☔ ☔
Keep it DRY
D R Y
Don’t Repeat Yourself
a()
does something quite similar to function b()
,
don’t just copy & paste a()
to b()
, and tweak b()
.
b()
could call a()
.
c()
,
and both a()
and b()
should call c()
.
Here’s code to see if a number is present in a vector:
bool present(vector<int> vec, int target) { for (auto v : vec) if (v == target) return true; return false; }
Apparently, the programmer didn’t know about set<int>
or the
find
algorithm.
We also need to find if a value is absent from a vector:
bool present(vector<int> vec, int target) { for (auto v : vec) if (v == target) return true; return false; } bool absent(vector<int> vec, int target) { for (auto v : vec) if (v != target) // Changed == to != return true; return false; }
absent()
is incorrect.
Let’s try this:
bool present(vector<int> vec, int target) { for (auto v : vec) if (v == target) return true; return false; } bool absent(vector<int> vec, int target) { for (auto v : vec) if (v == target) return false; // changed true to false return true; // changed false to true }
Better.
Sure, the previous solution is correct, but it’s not
maintainable. What if we have to modify present()
,
say, to treat negative values as equal to positive values?
Will we remember to also modify absent()
?
bool present(vector<int> vec, int target) { for (auto v : vec) if (v == target) return true; return false; } bool absent(vector<int> vec, int target) { return !present(vec, target)); }
Correct, and DRY.
As we all know, a reference creates an alias for another thing. For example:
int a = 42; int &r = a; cout << "r=" << r << " a=" << a << '\n'; a = 99; cout << "r=" << r << " a=" << a << '\n'; r = 56; cout << "r=" << r << " a=" << a << '\n';
r=42 a=42 r=99 a=99 r=56 a=56
Let’s say that we want to change all the fives in this vector to negative fives:
vector<int> values = {3,1,4,1,5,9,2,6,5,3,5}; for (size_t i=0; i<values.size(); i++) if (values[i] == 5) values[i] = -5; for (size_t i=0; i<values.size(); i++) cout << values[i] << ' ';
3 1 4 1 -5 9 2 6 -5 3 -5
That works, but it’s a lot of repetition—quite a few instances of
values[i]
.
Let’s use a more modern loop to write out the numbers:
vector<int> values = {3,1,4,1,5,9,2,6,5,3,5}; for (size_t i=0; i<values.size(); i++) if (values[i] == 5) values[i] = -5; for (int v : values) cout << v << ' ';
3 1 4 1 -5 9 2 6 -5 3 -5
Hooray! One fewer values[i]
!
Let’s use that same technique to change 5 to −5:
vector<int> values = {3,1,4,1,5,9,2,6,5,3,5}; for (int v : values) if (v == 5) v = -5; for (int v : values) cout << v << ' ';
3 1 4 1 5 9 2 6 5 3 5
That didn’t work. Why?
REFERENCES!
vector<int> values = {3,1,4,1,5,9,2,6,5,3,5}; for (int &v : values) if (v == 5) v = -5; for (int v : values) cout << v << ' ';
3 1 4 1 -5 9 2 6 -5 3 -5
I need &v
in the first loop, because I’m changing the number.
I’m not changing anything in the second loop, so a reference
isn’t required.
Consider this file:
% cat ~cs253/pub/ducks Huey (red) Dewey (blue) Louie (green)
I want code that reads that file into a vector<string>
, and converts
the lower-case vowels to asterisks, so it looks like censorship.
const string home = getpwnam("cs253")->pw_dir; ifstream in(home+"/pub/ducks"); vector<string> ducks; string line; while (getline(in, line)) ducks.push_back(line); for (size_t i=0; i<ducks.size(); i++) for (size_t j=0; j<ducks[i].size(); j++) if (ducks[i][j]=='a' || ducks[i][j]=='e' || ducks[i][j]=='i' || ducks[i][j]=='o' || ducks[i][j]=='u') ducks[i][j] = '*'; for (size_t i=0; i<ducks.size(); i++) cout << ducks[i] << '\n';
H**y (r*d) D*w*y (bl**) L**** (gr**n)
That was horrible. So soon, we’ve forgotten all that we’ve learned.
const string home = getpwnam("cs253")->pw_dir; ifstream in(home+"/pub/ducks"); vector<string> ducks; string line; while (getline(in, line)) ducks.push_back(line); for (string &s : ducks) for (size_t j=0; j<s.size(); j++) if (s[j]=='a' || s[j]=='e' || s[j]=='i' || s[j]=='o' || s[j]=='u') s[j] = '*'; for (string s : ducks) cout << s << '\n';
H**y (r*d) D*w*y (bl**) L**** (gr**n)
Better, but still awful.
const string home = getpwnam("cs253")->pw_dir; ifstream in(home+"/pub/ducks"); vector<string> ducks; string line; while (getline(in, line)) ducks.push_back(line); for (string &s : ducks) for (char &c : s) if (c=='a' || c=='e' || c=='i' || c=='o' || c=='u') c = '*'; for (string s : ducks) cout << s << '\n';
H**y (r*d) D*w*y (bl**) L**** (gr**n)
Finally, it’s DRY! Still, we’re copying strings in the last loop.
const string home = getpwnam("cs253")->pw_dir; ifstream in(home+"/pub/ducks"); vector<string> ducks; string line; while (getline(in, line)) ducks.push_back(line); for (string &s : ducks) for (char &c : s) if (c=='a' || c=='e' || c=='i' || c=='o' || c=='u') c = '*'; for (const string &s : ducks) cout << s << '\n';
H**y (r*d) D*w*y (bl**) L**** (gr**n)
DRY and efficient.
const string home = getpwnam("cs253")->pw_dir; ifstream in(home+"/pub/ducks"); vector<string> ducks; string line; while (getline(in, line)) ducks.push_back(line); for (string &s : ducks) for (size_t p=0; (p = s.find_first_of("aeiou",p)) != s.npos; p++) s[p] = '*'; for (const string &s : ducks) cout << s << '\n';
H**y (r*d) D*w*y (bl**) L**** (gr**n)
A different approach.