5CCYB041
We continue working on our DNA shotgun sequencing project
You can find the most up to date version in the project's solution/
folder
Make sure your code is up to date now!
Let's imagine that this is the current estimate of the sequence:
CTGAATGCTTGGGCTGAAAGGGCGCGAGACGTATTCCCCGGTTGCAGACG
and this is a candidate fragment:
CCCTCATCACAACTGAATGCTTGGGCTGAA
We could compute the overlap by comparing the overlapping region, starting from the smallest overlap:
Let's imagine that this is the current estimate of the sequence:
CTGAATGCTTGGGCTGAAAGGGCGCGAGACGTATTCCCCGGTTGCAGACG
and this is a candidate fragment:
CCCTCATCACAACTGAATGCTTGGGCTGAA
We could compute the overlap by comparing the overlapping region, starting from the smallest overlap:
CTGAATGCTTGGGCTGAAAGGGCGCGAGACGTATTCCCCGGTTGCCCCTCATCACAACTGAATGCTTGGGCTGAA
Let's imagine that this is the current estimate of the sequence:
CTGAATGCTTGGGCTGAAAGGGCGCGAGACGTATTCCCCGGTTGCAGACG
and this is a candidate fragment:
CCCTCATCACAACTGAATGCTTGGGCTGAA
We could compute the overlap by comparing the overlapping region, starting from the smallest overlap:
CTGAATGCTTGGGCTGAAAGGGCGCGAGACGTATTCCCCGGTTGC CCCTCATCACAACTGAATGCTTGGGCTGAA
Let's imagine that this is the current estimate of the sequence:
CTGAATGCTTGGGCTGAAAGGGCGCGAGACGTATTCCCCGGTTGCAGACG
and this is a candidate fragment:
CCCTCATCACAACTGAATGCTTGGGCTGAA
We could compute the overlap by comparing the overlapping region, starting from the smallest overlap:
CTGAATGCTTGGGCTGAAAGGGCGCGAGACGTATTCCCCGGTTGC CCCTCATCACAACTGAATGCTTGGGCTGAA
Let's imagine that this is the current estimate of the sequence:
CTGAATGCTTGGGCTGAAAGGGCGCGAGACGTATTCCCCGGTTGCAGACG
and this is a candidate fragment:
CCCTCATCACAACTGAATGCTTGGGCTGAA
We could compute the overlap by comparing the overlapping region, starting from the smallest overlap:
CTGAATGCTTGGGCTGAAAGGGCGCGAGACGTATTCCCCGGTTGC CCCTCATCACAACTGAATGCTTGGGCTGAA
Let's imagine that this is the current estimate of the sequence:
CTGAATGCTTGGGCTGAAAGGGCGCGAGACGTATTCCCCGGTTGCAGACG
and this is a candidate fragment:
CCCTCATCACAACTGAATGCTTGGGCTGAA
We could compute the overlap by comparing the overlapping region, starting from the smallest overlap:
CTGAATGCTTGGGCTGAAAGGGCGCGAGACGTATTCCCCGGTTGC CCCTCATCACAACTGAATGCTTGGGCTGAA
We have a match with an overlap of 5 bases!
Let's imagine that this is the current estimate of the sequence:
CTGAATGCTTGGGCTGAAAGGGCGCGAGACGTATTCCCCGGTTGCAGACG
and this is a candidate fragment:
CCCTCATCACAACTGAATGCTTGGGCTGAA
We could compute the overlap by comparing the overlapping region, starting from the smallest overlap:
CTGAATGCTTGGGCTGAAAGGGCGCGAGACGTATTCCCCGGTTGC CCCTCATCACAACTGAATGCTTGGGCTGAA
No! If we carry on, we'll find a larger overlap
A better strategy is to start with the biggest overlap first, and gradually reduce it
A better strategy is to start with the biggest overlap first, and gradually reduce it
CTGAATGCTTGGGCTGAAAGGGCGCGAGACGTATTCCCCGGTTGC CCCTCATCACAACTGAATGCTTGGGCTGAA
A better strategy is to start with the biggest overlap first, and gradually reduce it
CTGAATGCTTGGGCTGAAAGGGCGCGAGACGTATTCCCCGGTTGC CCCTCATCACAACTGAATGCTTGGGCTGAA
A better strategy is to start with the biggest overlap first, and gradually reduce it
CTGAATGCTTGGGCTGAAAGGGCGCGAGACGTATTCCCCGGTTGC CCCTCATCACAACTGAATGCTTGGGCTGAA
A better strategy is to start with the biggest overlap first, and gradually reduce it
CTGAATGCTTGGGCTGAAAGGGCGCGAGACGTATTCCCCGGTTGC CCCTCATCACAACTGAATGCTTGGGCTGAA
A better strategy is to start with the biggest overlap first, and gradually reduce it
CTGAATGCTTGGGCTGAAAGGGCGCGAGACGTATTCCCCGGTTGC CCCTCATCACAACTGAATGCTTGGGCTGAA
A better strategy is to start with the biggest overlap first, and gradually reduce it
CTGAATGCTTGGGCTGAAAGGGCGCGAGACGTATTCCCCGGTTGC CCCTCATCACAACTGAATGCTTGGGCTGAA
A better strategy is to start with the biggest overlap first, and gradually reduce it
CTGAATGCTTGGGCTGAAAGGGCGCGAGACGTATTCCCCGGTTGC CCCTCATCACAACTGAATGCTTGGGCTGAA
A better strategy is to start with the biggest overlap first, and gradually reduce it
CTGAATGCTTGGGCTGAAAGGGCGCGAGACGTATTCCCCGGTTGC CCCTCATCACAACTGAATGCTTGGGCTGAA
A better strategy is to start with the biggest overlap first, and gradually reduce it
CTGAATGCTTGGGCTGAAAGGGCGCGAGACGTATTCCCCGGTTGC CCCTCATCACAACTGAATGCTTGGGCTGAA
A better strategy is to start with the biggest overlap first, and gradually reduce it
CTGAATGCTTGGGCTGAAAGGGCGCGAGACGTATTCCCCGGTTGC CCCTCATCACAACTGAATGCTTGGGCTGAA
A better strategy is to start with the biggest overlap first, and gradually reduce it
CTGAATGCTTGGGCTGAAAGGGCGCGAGACGTATTCCCCGGTTGC CCCTCATCACAACTGAATGCTTGGGCTGAA
A better strategy is to start with the biggest overlap first, and gradually reduce it
CTGAATGCTTGGGCTGAAAGGGCGCGAGACGTATTCCCCGGTTGC CCCTCATCACAACTGAATGCTTGGGCTGAA
A better strategy is to start with the biggest overlap first, and gradually reduce it
CTGAATGCTTGGGCTGAAAGGGCGCGAGACGTATTCCCCGGTTGC CCCTCATCACAACTGAATGCTTGGGCTGAA
Now as soon as we find an overlap, it is guaranteed to be the largest
Add a function to compute the overlap between the current sequence and a candidate fragment
Use this function to identify the candidate fragment with the largest overlap with current sequence
Add a function to merge this candidate fragment with the current sequence, given the computed overlap
Use these functions to iteratively merge candidates fragments until no overlapping fragments remain
Check that all unmerged fragments are already contained within the sequence
Add a function to compute the overlap between the current sequence and a candidate fragment
Use this function to identify the candidate fragment with the largest overlap with current sequence
Add a function to merge this candidate fragment with the current sequence, given the computed overlap
Use these functions to iteratively merge candidates fragments until no overlapping fragments remain
Check that all unmerged fragments are already contained within the sequence
Exercise: implement code to do this
We create a header file overlap.h
to hold our function declaration
#pragma once#include <string>int compute_overlap (const std::string& sequence, const std::string& fragment);
We create a header file overlap.h
to hold our function declaration
#pragma once#include <string>int compute_overlap (const std::string& sequence, const std::string& fragment);
We create a header file overlap.h
to hold our function declaration
#pragma once#include <string>int compute_overlap (const std::string& sequence, const std::string& fragment);
We create a header file overlap.h
to hold our function declaration
#pragma once#include <string>int compute_overlap (const std::string& sequence, const std::string& fragment);
We create a header file overlap.h
to hold our function declaration
#pragma once#include <string>int compute_overlap (const std::string& sequence, const std::string& fragment);
#include
the <string>
header since we are using std::string
We create a header file overlap.h
to hold our function declaration
#pragma once#include <string>int compute_overlap (const std::string& sequence, const std::string& fragment);
#include
the <string>
header since we are using std::string
#pragma once
directive to avoid double-inclusion Next, we create the overlap.cpp
file to hold our function definition
#include <iostream>#include <stdexcept>#include <string>#include "overlap.h"int compute_overlap (const std::string& sequence, const std::string& fragment){ // this is where our code will go}
Next, we create the overlap.cpp
file to hold our function definition
#include <iostream>#include <stdexcept>#include <string>#include "overlap.h"int compute_overlap (const std::string& sequence, const std::string& fragment){ // this is where our code will go}
Next, we create the overlap.cpp
file to hold our function definition
#include <iostream>#include <stdexcept>#include <string>#include "overlap.h"int compute_overlap (const std::string& sequence, const std::string& fragment){ // this is where our code will go}
#include
the matching header fileNext, we create the overlap.cpp
file to hold our function definition
#include <iostream>#include <stdexcept>#include <string>#include "overlap.h"int compute_overlap (const std::string& sequence, const std::string& fragment){ // this is where our code will go}
#include
the matching header file#include
the system headers for the functionality we will be
usingNext, we create the overlap.cpp
file to hold our function definition
#include <iostream>#include <stdexcept>#include <string>#include "overlap.h"int compute_overlap (const std::string& sequence, const std::string& fragment){ // this is where our code will go}
#include
the matching header file#include
the system headers for the functionality we will be
usingNow we fill out the body of our compute_overlap()
function
int compute_overlap (const std::string& sequence, const std::string& fragment){ if (fragment.size() > sequence.size()) throw std::runtime_error ("fragment size larger than current sequence!"); std::cerr << "sequence is \"" << sequence << "\"\n"; std::cerr << "trying fragment \"" << fragment << "\"\n"; ... return 0;}
Now we fill out the body of our compute_overlap()
function
int compute_overlap (const std::string& sequence, const std::string& fragment){ if (fragment.size() > sequence.size()) throw std::runtime_error ("fragment size larger than current sequence!"); std::cerr << "sequence is \"" << sequence << "\"\n"; std::cerr << "trying fragment \"" << fragment << "\"\n"; ... return 0;}
Now we fill out the body of our compute_overlap()
function
int compute_overlap (const std::string& sequence, const std::string& fragment){ if (fragment.size() > sequence.size()) throw std::runtime_error ("fragment size larger than current sequence!"); std::cerr << "sequence is \"" << sequence << "\"\n"; std::cerr << "trying fragment \"" << fragment << "\"\n"; ... return 0;}
Now we fill out the body of our compute_overlap()
function
int compute_overlap (const std::string& sequence, const std::string& fragment){ if (fragment.size() > sequence.size()) throw std::runtime_error ("fragment size larger than current sequence!"); std::cerr << "sequence is \"" << sequence << "\"\n"; std::cerr << "trying fragment \"" << fragment << "\"\n"; ... return 0;}
Now we fill out the body of our compute_overlap()
function
int compute_overlap (const std::string& sequence, const std::string& fragment){ if (fragment.size() > sequence.size()) throw std::runtime_error ("fragment size larger than current sequence!"); std::cerr << "sequence is \"" << sequence << "\"\n"; std::cerr << "trying fragment \"" << fragment << "\"\n"; ... return 0;}
Now we can focus on what happens here...
for (int overlap = fragment.size(); overlap > 0; --overlap) { const auto seq_start = sequence.substr(0, overlap); const auto frag_end = fragment.substr(fragment.size()-overlap); if (seq_start == frag_end) return overlap; }
for (int overlap = fragment.size(); overlap > 0; --overlap) { const auto seq_start = sequence.substr(0, overlap); const auto frag_end = fragment.substr(fragment.size()-overlap); if (seq_start == frag_end) return overlap; }
We iterate over the range of valid overlaps:
for (int overlap = fragment.size(); overlap > 0; --overlap) { const auto seq_start = sequence.substr(0, overlap); const auto frag_end = fragment.substr(fragment.size()-overlap); if (seq_start == frag_end) return overlap; }
We iterate over the range of valid overlaps:
We extract the part of the current sequence that corresponds to that overlap
substr()
method for (int overlap = fragment.size(); overlap > 0; --overlap) { const auto seq_start = sequence.substr(0, overlap); const auto frag_end = fragment.substr(fragment.size()-overlap); if (seq_start == frag_end) return overlap; }
We iterate over the range of valid overlaps:
We extract the part of the current sequence that corresponds to that overlap
substr()
method for (int overlap = fragment.size(); overlap > 0; --overlap) { const auto seq_start = sequence.substr(0, overlap); const auto frag_end = fragment.substr(fragment.size()-overlap); if (seq_start == frag_end) return overlap; }
We iterate over the range of valid overlaps:
We extract the part of the current sequence that corresponds to that overlap
substr()
methodFinally, we check whether both substrings match
for (int overlap = fragment.size(); overlap > 0; --overlap) { const auto seq_start = sequence.substr(0, overlap); const auto frag_end = fragment.substr(fragment.size()-overlap); if (seq_start == frag_end) return overlap; }
We iterate over the range of valid overlaps:
We extract the part of the current sequence that corresponds to that overlap
substr()
methodFinally, we check whether both substrings match
Note that in this call to substr()
, we only provided one argument, whereas we
had provided two arguments in the previous call (position and length of the
substring).
This is because the arguments for this call have been given default values:
Here is our function definition in full:
int compute_overlap (const std::string& sequence, const std::string& fragment){ if (fragment.size() > sequence.size()) throw std::runtime_error ("fragment size larger than current sequence!"); std::cerr << "sequence is \"" << sequence << "\"\n"; std::cerr << "trying fragment \"" << fragment << "\"\n"; for (int overlap = fragment.size(); overlap > 0; --overlap) { const auto seq_start = sequence.substr(0, overlap); const auto frag_end = fragment.substr(fragment.size()-overlap); if (seq_start == frag_end) return overlap; } return 0;}
Add a function to compute the overlap between the current sequence and a candidate fragment
Use this function to identify the candidate fragment with the largest overlap with current sequence
Add a function to merge this candidate fragment with the current sequence, given the computed overlap
Use these functions to iteratively merge candidates fragments until no overlapping fragments remain
Check that all unmerged fragments are already contained within the sequence
int compute_overlap (const std::string& sequence, const std::string& fragment){ if (fragment.size() > sequence.size()) throw std::runtime_error ("fragment size larger than current sequence!"); std::cerr << "sequence is \"" << sequence << "\"\n"; std::cerr << "trying fragment \"" << fragment << "\"\n"; for (int overlap = fragment.size(); overlap > 0; --overlap) { const auto seq_start = sequence.substr(0, overlap); const auto frag_end = fragment.substr(fragment.size()-overlap); if (seq_start == frag_end) return overlap; } return 0;}
We need to modify our code to also check for overlap at the other end of the sequence
int compute_overlap (const std::string& sequence, const std::string& fragment){ if (fragment.size() > sequence.size()) throw std::runtime_error ("fragment size larger than current sequence!"); std::cerr << "sequence is \"" << sequence << "\"\n"; std::cerr << "trying fragment \"" << fragment << "\"\n"; for (int overlap = fragment.size(); overlap > 0; --overlap) { const auto seq_start = sequence.substr(0, overlap); const auto frag_end = fragment.substr(fragment.size()-overlap); if (seq_start == frag_end) return overlap; } return 0;}
We can no longer return the overlap here, since there may be a larger overlap at the other end.
We need to check both ends, and return the larger of the two.
int compute_overlap (const std::string& sequence, const std::string& fragment){ if (fragment.size() > sequence.size()) throw std::runtime_error ("fragment size larger than current sequence!"); std::cerr << "sequence is \"" << sequence << "\"\n"; std::cerr << "trying fragment \"" << fragment << "\"\n"; int largest_overlap = 0; for (int overlap = fragment.size(); overlap > 0; --overlap) { const auto seq_start = sequence.substr(0, overlap); const auto frag_end = fragment.substr(fragment.size()-overlap); if (seq_start == frag_end) { largest_overlap = overlap; break; } } ...
To do this, we declare an integer variable to hold the value of the largest overlap detected so far
int compute_overlap (const std::string& sequence, const std::string& fragment){ if (fragment.size() > sequence.size()) throw std::runtime_error ("fragment size larger than current sequence!"); std::cerr << "sequence is \"" << sequence << "\"\n"; std::cerr << "trying fragment \"" << fragment << "\"\n"; int largest_overlap = 0; for (int overlap = fragment.size(); overlap > 0; --overlap) { const auto seq_start = sequence.substr(0, overlap); const auto frag_end = fragment.substr(fragment.size()-overlap); if (seq_start == frag_end) { largest_overlap = overlap; break; } } ...
Now, instead of returning the value of the current overlap, we record this
value, and use the break
statement to stop the enclosing loop and
proceed with the following code.
... if (seq_start == frag_end) { largest_overlap = overlap; break; } } for (int overlap = fragment.size(); overlap > largest_overlap; --overlap) { const auto seq_end = sequence.substr(sequence.size() - overlap); const auto frag_start = fragment.substr(0, overlap); if (seq_end == frag_start) { largest_overlap = -overlap; break; } } return largest_overlap;}
... if (seq_start == frag_end) { largest_overlap = overlap; break; } } for (int overlap = fragment.size(); overlap > largest_overlap; --overlap) { const auto seq_end = sequence.substr(sequence.size() - overlap); const auto frag_start = fragment.substr(0, overlap); if (seq_end == frag_start) { largest_overlap = -overlap; break; } } return largest_overlap;}
We now check the other end of the string, using the same idea as before – with a few differences
... if (seq_start == frag_end) { largest_overlap = overlap; break; } } for (int overlap = fragment.size(); overlap > largest_overlap; --overlap) { const auto seq_end = sequence.substr(sequence.size() - overlap); const auto frag_start = fragment.substr(0, overlap); if (seq_end == frag_start) { largest_overlap = -overlap; break; } } return largest_overlap;}
This time, we extract the substrings from the opposite ends of both the current sequence and the candidate fragment
... if (seq_start == frag_end) { largest_overlap = overlap; break; } } for (int overlap = fragment.size(); overlap > largest_overlap; --overlap) { const auto seq_end = sequence.substr(sequence.size() - overlap); const auto frag_start = fragment.substr(0, overlap); if (seq_end == frag_start) { largest_overlap = -overlap; break; } } return largest_overlap;}
There is no point iterating all the way to zero, since we already know the size of the largest overlap from the previous loop
... if (seq_start == frag_end) { largest_overlap = overlap; break; } } for (int overlap = fragment.size(); overlap > largest_overlap; --overlap) { const auto seq_end = sequence.substr(sequence.size() - overlap); const auto frag_start = fragment.substr(0, overlap); if (seq_end == frag_start) { largest_overlap = -overlap; break; } } return largest_overlap;}
... and we record the overlap as a negative value.
for
loop, we are only
considering overlaps larger than encountered at the other end of the sequence ... if (seq_start == frag_end) { largest_overlap = overlap; break; } } for (int overlap = fragment.size(); overlap > largest_overlap; --overlap) { const auto seq_end = sequence.substr(sequence.size() - overlap); const auto frag_start = fragment.substr(0, overlap); if (seq_end == frag_start) { largest_overlap = -overlap; break; } } return largest_overlap;}
Finally, we end by returning the largest overlap recorded in both loops
Add a function to compute the overlap between the current sequence and a candidate fragment
Use this function to identify the candidate fragment with the largest overlap with current sequence
Add a function to merge this candidate fragment with the current sequence, given the computed overlap
Use these functions to iteratively merge candidates fragments until no overlapping fragments remain
Check that all unmerged fragments are already contained within the sequence
... int biggest_overlap = 0; int fragment_with_biggest_overlap = -1; for (int n = 0; n < fragments.size(); ++n) { const auto overlap = compute_overlap (sequence, fragments[n]); if (std::abs (biggest_overlap) < std::abs (overlap)) { biggest_overlap = overlap; fragment_with_biggest_overlap = n; } } if (fragment_with_biggest_overlap >= 0) std::cerr << "fragment with biggest overlap is at index " << fragment_with_biggest_overlap << ", overlap = " << biggest_overlap << "\n"; write_sequence (args[2], sequence);}
... int biggest_overlap = 0; int fragment_with_biggest_overlap = -1; for (int n = 0; n < fragments.size(); ++n) { const auto overlap = compute_overlap (sequence, fragments[n]); if (std::abs (biggest_overlap) < std::abs (overlap)) { biggest_overlap = overlap; fragment_with_biggest_overlap = n; } } if (fragment_with_biggest_overlap >= 0) std::cerr << "fragment with biggest overlap is at index " << fragment_with_biggest_overlap << ", overlap = " << biggest_overlap << "\n"; write_sequence (args[2], sequence);}
We iterate over all candidate fragments
for
loop (rather than a range-based for
loop) since we
want to keep track of the index of the fragment ... int biggest_overlap = 0; int fragment_with_biggest_overlap = -1; for (int n = 0; n < fragments.size(); ++n) { const auto overlap = compute_overlap (sequence, fragments[n]); if (std::abs (biggest_overlap) < std::abs (overlap)) { biggest_overlap = overlap; fragment_with_biggest_overlap = n; } } if (fragment_with_biggest_overlap >= 0) std::cerr << "fragment with biggest overlap is at index " << fragment_with_biggest_overlap << ", overlap = " << biggest_overlap << "\n"; write_sequence (args[2], sequence);}
We use our new function to compute the overlap for the current candidate fragment
const
since we have no intention of modifying it
any further auto
keywordcompute_overlap()
int
... ... int biggest_overlap = 0; int fragment_with_biggest_overlap = -1; for (int n = 0; n < fragments.size(); ++n) { const auto overlap = compute_overlap (sequence, fragments[n]); if (std::abs (biggest_overlap) < std::abs (overlap)) { biggest_overlap = overlap; fragment_with_biggest_overlap = n; } } if (fragment_with_biggest_overlap >= 0) std::cerr << "fragment with biggest overlap is at index " << fragment_with_biggest_overlap << ", overlap = " << biggest_overlap << "\n"; write_sequence (args[2], sequence);}
We can then compare the extracted portions using the ==
comparison operator
If they match, we can immediately return the value of the overlap
We check whether the overlap for the current fragment exceeds the biggest overlap we have detected so far
... int biggest_overlap = 0; int fragment_with_biggest_overlap = -1; for (int n = 0; n < fragments.size(); ++n) { const auto overlap = compute_overlap (sequence, fragments[n]); if (std::abs (biggest_overlap) < std::abs (overlap)) { biggest_overlap = overlap; fragment_with_biggest_overlap = n; } } if (fragment_with_biggest_overlap >= 0) std::cerr << "fragment with biggest overlap is at index " << fragment_with_biggest_overlap << ", overlap = " << biggest_overlap << "\n"; write_sequence (args[2], sequence);}
If we make through to the last iteration, there is no match, so we simply
return 0;
If the overlap is the biggest so far, record its value, and the index of the corresponding fragment
Add a function to compute the overlap between the current sequence and a candidate fragment
Use this function to identify the candidate fragment with the largest overlap with current sequence
Add a function to merge this candidate fragment with the current sequence, given the computed overlap
Use these functions to iteratively merge candidates fragments until no overlapping fragments remain
Check that all unmerged fragments are already contained within the sequence
Once we've identified the candidate fragment with the largest overlap, we need to merge it with the current sequence to produce a longer, updated sequence:
Once we've identified the candidate fragment with the largest overlap, we need to merge it with the current sequence to produce a longer, updated sequence:
CTGAATGCTTGGGCTGAAAGGGCGCGAGACGTATTCCCCGGTTGC CCCTCATCACAACTGAATGCTTGGGCTGAA
Once we've identified the candidate fragment with the largest overlap, we need to merge it with the current sequence to produce a longer, updated sequence:
CTGAATGCTTGGGCTGAAAGGGCGCGAGACGTATTCCCCGGTTGC CCCTCATCACAACTGAATGCTTGGGCTGAA
⇓
CCCTCATCACAACTGAATGCTTGGGCTGAAAGGGCGCGAGACGTATTCCCCGGTTGC
Once we've identified the candidate fragment with the largest overlap, we need to merge it with the current sequence to produce a longer, updated sequence:
CTGAATGCTTGGGCTGAAAGGGCGCGAGACGTATTCCCCGGTTGC CCCTCATCACAACTGAATGCTTGGGCTGAA
⇓
CCCTCATCACAACTGAATGCTTGGGCTGAAAGGGCGCGAGACGTATTCCCCGGTTGC
Let's implement a function to do this. This is what our function declaration
should look like. We can add it to overlap.h
:
void merge (std::string& sequence, const std::string& fragment, const int overlap);
Next, add our function definition to overlap.cpp
:
void merge (std::string& sequence, const std::string& fragment, const int overlap){ if (overlap < 0) { sequence += fragment.substr (-overlap); } else if (overlap > 0) { sequence = fragment.substr (0, fragment.size()-overlap) + sequence; }}
Next, add our function definition to overlap.cpp
:
void merge (std::string& sequence, const std::string& fragment, const int overlap){ if (overlap < 0) { sequence += fragment.substr (-overlap); } else if (overlap > 0) { sequence = fragment.substr (0, fragment.size()-overlap) + sequence; }}
Use merge()
function in main()
:
... if (fragment_with_biggest_overlap >= 0) { std::cerr << "fragment with biggest overlap is at index " << fragment_with_biggest_overlap << ", overlap = " << biggest_overlap << "\n"; merge (sequence, fragments[fragment_with_biggest_overlap], biggest_overlap); } std::cerr << "final sequence has length " << sequence.size() << "\n"; write_sequence (args[2], sequence);}
Add a function to compute the overlap between the current sequence and a candidate fragment
Use this function to identify the candidate fragment with the largest overlap with current sequence
Add a function to merge this candidate fragment with the current sequence, given the computed overlap
Use these functions to iteratively merge candidates fragments until no overlapping fragments remain
Check that all unmerged fragments are already contained within the sequence
while (fragments.size()) { std::cerr << "---------------------------------------------------\n"; std::cerr << fragments.size() << " fragments left\n"; int biggest_overlap = 0; int fragment_with_biggest_overlap = -1; for (int n = 0; n < static_cast<int> (fragments.size()); ++n) { ... } if (fragment_with_biggest_overlap < 0) break; if (std::abs (biggest_overlap) < 10) break; std::cerr << "fragment with biggest overlap is at index " << fragment_with_biggest_overlap << ", overlap = " << biggest_overlap << "\n"; merge (sequence, fragments[fragment_with_biggest_overlap], biggest_overlap); fragments.erase (fragments.begin() + fragment_with_biggest_overlap); }
Add a function to compute the overlap between the current sequence and a candidate fragment
Use this function to identify the candidate fragment with the largest overlap with current sequence
Add a function to merge this candidate fragment with the current sequence, given the computed overlap
Use these functions to iteratively merge candidates fragments until no overlapping fragments remain
Check that all unmerged fragments are already contained within the sequence
... fragments.erase (fragments.begin() + fragment_with_biggest_overlap); } int num_unmatched = 0; for (const auto& frag : fragments) { if (sequence.find (frag) == std::string::npos) ++num_unmatched; } if (num_unmatched) std::cerr << "WARNING: " << num_unmatched << " fragments remain unmatched!\n"; std::cerr << "final sequence has length " << sequence.size() << "\n"; write_sequence (args[2], sequence);}
We've already seen examples of command-line options in other commands:
$ ls -l$ g++ -std=c++20 shotgun.cpp -o shotgun
We've already seen examples of command-line options in other commands:
$ ls -l$ g++ -std=c++20 shotgun.cpp -o shotgun
The purpose of a command-line option is typically to alter the behaviour of the command in some way
We've already seen examples of command-line options in other commands:
$ ls -l$ g++ -std=c++20 shotgun.cpp -o shotgun
The purpose of a command-line option is typically to alter the behaviour of the command in some way
Can we use that to request additional debugging information?
We've already seen examples of command-line options in other commands:
$ ls -l$ g++ -std=c++20 shotgun.cpp -o shotgun
The purpose of a command-line option is typically to alter the behaviour of the command in some way
Can we use that to request additional debugging information?
A typical option to enable verbose mode is -v
There are many ways to handle command-line options
There are many ways to handle command-line options
Have a go at implementing this simple approach to detect the -v
option:
verbose
variable to true
)There are many ways to handle command-line options
Have a go at implementing this simple approach to detect the -v
option:
verbose
variable to true
)Hint: there are many ways of doing this, but you may find the new C++20
std::erase()
function very useful here (if you can work out how to use it from the
documentation...)
-v
optionPossible solution:
...void run (std::vector<std::string>& args){ bool verbose = std::erase (args, "-v"); if (args.size() < 3) throw std::runtime_error ("expected 2 arguments: input_fragments output_sequence"); ...}
-v
optionPossible solution:
...void run (std::vector<std::string>& args){ bool verbose = std::erase (args, "-v"); if (args.size() < 3) throw std::runtime_error ("expected 2 arguments: input_fragments output_sequence"); ...}
std::erase()
will erase any occurence of the value ("-v"
) from the
container (args
), and return the number of occurences removed.
-v
in the argument list, the return value is zero, which equates to
false
when assigned to a bool
-v
, a non-zero value will be returned,
which equates to true
One problem with the previous solution is that verbose
is a local variable
run()
functionOne problem with the previous solution is that verbose
is a local variable
run()
functionIf we call other functions, the code in these functions will have no knowledge
or access to our verbose
variable
One problem with the previous solution is that verbose
is a local variable
run()
functionIf we call other functions, the code in these functions will have no knowledge
or access to our verbose
variable
We could pass our verbose
variable as an argument to all relevant functions
One problem with the previous solution is that verbose
is a local variable
run()
functionIf we call other functions, the code in these functions will have no knowledge
or access to our verbose
variable
We could pass our verbose
variable as an argument to all relevant functions
A better option in this particular case is to use a global variable
A global variable is declared outside of any function and is accessible from anywhere in the code
A global variable is declared outside of any function and is accessible from anywhere in the code
In general, global variables are strongly discouraged
A global variable is declared outside of any function and is accessible from anywhere in the code
In general, global variables are strongly discouraged
There are however cases where they make sense:
const
)A global variable is declared outside of any function and is accessible from anywhere in the code
In general, global variables are strongly discouraged
There are however cases where they make sense:
const
)std::cout
, std::cerr
: there can only be one of each in
any programWe could declare our global variable like this:
...bool verbose = false;...void run (std::vector<std::string>& args){ verbose = std::erase (args, "-v"); ...
But there are still issues with this:
shotgun.cpp
file – it won't be accessible
in our other cpp
filesWe could declare our global variable like this:
...bool verbose = false;...void run (std::vector<std::string>& args){ verbose = std::erase (args, "-v"); ...
But there are still issues with this:
shotgun.cpp
file – it won't be accessible
in our other cpp
filesverbose
?The scope of a variable determines its lifetime and where it can be accessed from.
Global scope
main()
starts, and destroyed
after main()
returnsThe scope of a variable determines its lifetime and where it can be accessed from.
Global scope
main()
starts, and destroyed
after main()
returnsFunction scope
The scope of a variable determines its lifetime and where it can be accessed from.
Global scope
main()
starts, and destroyed
after main()
returnsFunction scope
Block scope
for
loop or if
statement)Variable shadowing (also called name hiding) occurs when two variables of the same name co-exist in different scopes
int count = 0; // <= global scope...void compute (){ int count = 10; // <= function scope ... for (int count = 0; count < 20; ++count) { // <= block scope std::cout << count << "\n"; ... } std::cout << count << "\n";}
Variable shadowing (also called name hiding) occurs when two variables of the same name co-exist in different scopes
int count = 0; // <= global scope...void compute (){ int count = 10; // <= function scope ... for (int count = 0; count < 20; ++count) { // <= block scope std::cout << count << "\n"; ... } std::cout << count << "\n";}
This is perfectly legal in C++!
Variable shadowing (also called name hiding) occurs when two variables of the same name co-exist in different scopes
int count = 0; // <= global scope...void compute (){ int count = 10; // <= function scope ... for (int count = 0; count < 20; ++count) { // <= block scope std::cout << count << "\n"; ... } std::cout << count << "\n";}
This is perfectly legal in C++!
The variable at function scope hides the variable of the same name at global scope
count
is assumed to refer to the function scope variablecount
still exists, but is no longer accessible
by that nameVariable shadowing (also called name hiding) occurs when two variables of the same name co-exist in different scopes
int count = 0; // <= global scope...void compute (){ int count = 10; // <= function scope ... for (int count = 0; count < 20; ++count) { // <= block scope std::cout << count << "\n"; ... } std::cout << count << "\n";}
This is perfectly legal in C++!
Similarly, the variable at block scope hides the variable of the same name at function scope
count
is assumed to refer to the block scope variablecount
still exist, but are no longer accessible
by that nameVariable shadowing (also called name hiding) occurs when two variables of the same name co-exist in different scopes
int count = 0; // <= global scope...void compute (){ int count = 10; // <= function scope ... for (int count = 0; count < 20; ++count) { // <= block scope std::cout << count << "\n"; ... } std::cout << count << "\n";}
This is perfectly legal in C++!
This line will print the block scope version of count
Variable shadowing (also called name hiding) occurs when two variables of the same name co-exist in different scopes
int count = 0; // <= global scope...void compute (){ int count = 10; // <= function scope ... for (int count = 0; count < 20; ++count) { // <= block scope std::cout << count << "\n"; ... } std::cout << count << "\n";}
This is perfectly legal in C++!
... but this line will print the function scope version of count
, since it
is outside the block
Variable shadowing allows a function to use local variables without worrying about whether some other global variable exists elsewhere with the same name
Variable shadowing allows a function to use local variables without worrying about whether some other global variable exists elsewhere with the same name
... but it can also lead to subtle errors!
Variable shadowing allows a function to use local variables without worrying about whether some other global variable exists elsewhere with the same name
... but it can also lead to subtle errors!
Name collisions are a general problem in computing
Variable shadowing allows a function to use local variables without worrying about whether some other global variable exists elsewhere with the same name
... but it can also lead to subtle errors!
Name collisions are a general problem in computing
Most of these problems can be avoided by:
Variable shadowing allows a function to use local variables without worrying about whether some other global variable exists elsewhere with the same name
... but it can also lead to subtle errors!
Name collisions are a general problem in computing
Most of these problems can be avoided by:
A namespace is essentially a named scope for your function and variable declarations
A namespace is essentially a named scope for your function and variable declarations
We have already been using a namespace from the start: the std
namespace
A namespace is essentially a named scope for your function and variable declarations
We have already been using a namespace from the start: the std
namespace
std::vector
vector
class (a type) declared within the std
namespacestd::cout
std::ostream
) called cout
declared within the std
namespaceA namespace is essentially a named scope for your function and variable declarations
We have been using a namespace from the start: the std
namespace
std::vector
std
namespacestd::cout
std::ostream
) called cout
declared within the std
namespacestd
namespaceA namespace is essentially a named scope for your function and variable declarations
We have been using a namespace from the start: the std
namespace
std::vector
std
namespacestd::cout
std::ostream
) called cout
declared within the std
namespacestd
namespace::
)The main purpose of C++ namespace is:
The main purpose of C++ namespace is:
For example, we may very well want to declare a variable or function called
count
count()
algorithm!Thankfully, it is declared within the std
namespace
count
without the risk of name
collisions with std::count()
!Declaring variables or functions within a namespace is simply a matter of declaring them within the scope of our namespace.
Declaring variables or functions within a namespace is simply a matter of declaring them within the scope of our namespace.
For example:
namespace debug { bool verbose = false;}
This declares our variable verbose
to be within the debug
namespace
debug
namespace if it hadn't already been
encounteredAny code also declared within our debug
namespace can already access our
verbose
variable directly.
Any code also declared within our debug
namespace can already access our
verbose
variable directly.
When accessing our variable outside of the debug
namespace, we can refer to
it using its fully qualified name:
if (debug::verbose) ...
Any code also declared within our debug
namespace can already access our
verbose
variable directly.
When accessing our variable outside of the debug
namespace, we can refer to
it using its fully qualified name:
if (debug::verbose) ...
Alternatively, we can selectively bring one identifier (variable or function)
into the current scope with the using
declaration:
using debug::verbose;...if (verbose) ...
We can also bring all identifiers declared in a namespace into the current scope with the using namespace
directive:
using namespace debug;...if (verbose) ...
We can also bring all identifiers declared in a namespace into the current scope with the using namespace
directive:
using namespace debug;...if (verbose) ...
The using namespace
directive can be problematic
We can also bring all identifiers declared in a namespace into the current scope with the using namespace
directive:
using namespace debug;...if (verbose) ...
The using namespace
directive can be problematic
It is much better to use the fully qualified name, or if it really helps code
readability, use the selective using
declaration for the specific identifiers you
need
using namespace std;
In spite of the general recommendation to avoid blanket using namespace
directives, you will find that many (if not most) C++ tutorials make use of
this directive from the very beginning, starting at 'hello world':
#include <iostream>using namespace std;int main() { cout << "Hello World!\n"; return 0;}
using namespace std;
In spite of the general recommendation to avoid blanket using namespace
directives, you will find that many (if not most) C++ tutorials make use of
this directive from the very beginning, starting at 'hello world':
#include <iostream>using namespace std;int main() { cout << "Hello World!\n"; return 0;}
This brings everything declared within the std
namespace into the global scope
std::
as a prefix for std::cerr
, std::vector
, std::string
, ...using namespace std;
So why do we not follow this convention on this course?
using namespace std;
So why do we not follow this convention on this course?
using namespace std;
So why do we not follow this convention on this course?
In practice, the using namespace
directive is strongly discouraged
using namespace std;
So why do we not follow this convention on this course?
In practice, the using namespace
directive is strongly discouraged
It is particularly bad practice to make use of using namespace
directives
at global scope within header files
using namespace
within your own cpp
filesusing namespace std;
So why do we not follow this convention on this course?
In practice, the using namespace
directive is strongly discouraged
It is particularly bad practice to make use of using namespace
directives
at global scope within header files
using namespace
within your own cpp
files#include
s your header#include
dFor the reasons outlined in the previous slides, we recommend to always use the fully qualified name to access members of a namespace
For the reasons outlined in the previous slides, we recommend to always use the fully qualified name to access members of a namespace
Coming back to our project, we can declare our verbose
variable within a new
debug
namespace to avoid name collisions
namespace debug { bool verbose = false;}
and refer to this variable by its fully qualified name: debug::verbose
For the reasons outlined in the previous slides, we recommend to always use the fully qualified name to access members of a namespace
Coming back to our project, we can declare our verbose
variable within a new
debug
namespace to avoid name collisions
namespace debug { bool verbose = false;}
and refer to this variable by its fully qualified name: debug::verbose
But we still need to work out where to declare this variable
As previously mentioned, our debug::verbose
variable must be declared prior
to its point of use in any translation unit where it is needed
As previously mentioned, our debug::verbose
variable must be declared prior
to its point of use in any translation unit where it is needed
Let's create a header file debug.h
to hold this variable:
#pragma oncenamespace debug { bool verbose = false;}
As previously mentioned, our debug::verbose
variable must be declared prior
to its point of use in any translation unit where it is needed
Let's create a header file debug.h
to hold this variable:
#pragma oncenamespace debug { bool verbose = false;}
We can now #include
it in shotgun.cpp
, fragments.h
and overlap.cpp
As previously mentioned, our debug::verbose
variable must be declared prior
to its point of use in any translation unit where it is needed
Let's create a header file debug.h
to hold this variable:
#pragma oncenamespace debug { bool verbose = false;}
We can now #include
it in shotgun.cpp
, fragments.h
and overlap.cpp
Exercise: implement these changes in your code
When trying to do this, you will most likely have encountered errors of this nature:
/usr/bin/ld: fragments.o:(.bss+0x0): multiple definition of `debug::verbose'; shotgun.o:(.bss+0x0): first defined here/usr/bin/ld: overlap.o:(.bss+0x0): multiple definition of `debug::verbose'; shotgun.o:(.bss+0x0): first defined herecollect2: error: ld returned 1 exit status
When trying to do this, you will most likely have encountered errors of this nature:
/usr/bin/ld: fragments.o:(.bss+0x0): multiple definition of `debug::verbose'; shotgun.o:(.bss+0x0): first defined here/usr/bin/ld: overlap.o:(.bss+0x0): multiple definition of `debug::verbose'; shotgun.o:(.bss+0x0): first defined herecollect2: error: ld returned 1 exit status
The issue is that we have declared and implicitly defined our
debug::verbose
variable once per translation unit
shotgun.o
, fragments.o
and overlap.o
When trying to do this, you will most likely have encountered errors of this nature:
/usr/bin/ld: fragments.o:(.bss+0x0): multiple definition of `debug::verbose'; shotgun.o:(.bss+0x0): first defined here/usr/bin/ld: overlap.o:(.bss+0x0): multiple definition of `debug::verbose'; shotgun.o:(.bss+0x0): first defined herecollect2: error: ld returned 1 exit status
The issue is that we have declared and implicitly defined our
debug::verbose
variable once per translation unit
shotgun.o
, fragments.o
and overlap.o
When trying to do this, you will most likely have encountered errors of this nature:
/usr/bin/ld: fragments.o:(.bss+0x0): multiple definition of `debug::verbose'; shotgun.o:(.bss+0x0): first defined here/usr/bin/ld: overlap.o:(.bss+0x0): multiple definition of `debug::verbose'; shotgun.o:(.bss+0x0): first defined herecollect2: error: ld returned 1 exit status
The issue is that we have declared and implicitly defined our
debug::verbose
variable once per translation unit
shotgun.o
, fragments.o
and overlap.o
There are 2 ways to get around this:
cpp
fileFirst approach:
Use the extern
keyword in
debug.h
to make it clear that we are only declaring our variable at this
point:
#pragma oncenamespace debug { extern bool verbose;}
First approach:
Use the extern
keyword in
debug.h
to make it clear that we are only declaring our variable at this
point:
#pragma oncenamespace debug { extern bool verbose;}
and place the full definition and initialisation in a new file debug.cpp
:
#include "debug.h"namespace debug { bool verbose = false;}
Second approach (much simpler):
Use the inline
keyword in our debug.h
header file to allow multiple definitions
of the same variable to be provided across multiple translation units:
#pragma oncenamespace debug { inline bool verbose = false;}
We do not need an additional cpp
file in this case, and the initial value can
now be specified at the same time
Second approach (much simpler):
Use the inline
keyword in our debug.h
header file to allow multiple definitions
of the same variable to be provided across multiple translation units:
#pragma oncenamespace debug { inline bool verbose = false;}
We do not need an additional cpp
file in this case, and the initial value can
now be specified at the same time
Note that this use of the inline
keyword was introduced in the C++17 version
of the standard
Second approach (much simpler):
Use the inline
keyword in our debug.h
header file to allow multiple definitions
of the same variable to be provided across multiple translation units:
#pragma oncenamespace debug { inline bool verbose = false;}
We do not need an additional cpp
file in this case, and the initial value can
now be specified at the same time
Note that this use of the inline
keyword was introduced in the C++17 version
of the standard
Let's go ahead and use the inline
keyword in our project
We now have all the pieces required to implement our verbose option
debug.h
header file as per the previous slideWe now have all the pieces required to implement our verbose option
debug.h
header file as per the previous slidemain()
:std::erase()
call to detect and handle the -v
optiondebug::verbose
to true
if detectedWe now have all the pieces required to implement our verbose option
debug.h
header file as per the previous slidemain()
:std::erase()
call to detect and handle the -v
optiondebug::verbose
to true
if detected#include "debug.h"
so the debug::verbose
variable is declared at the
point of usedebug::verbose
is true
, and if so print out appropriate information We now have all the pieces required to implement our verbose option
debug.h
header file as per the previous slidemain()
:std::erase()
call to detect and handle the -v
optiondebug::verbose
to true
if detected#include "debug.h"
so the debug::verbose
variable is declared at the
point of usedebug::verbose
is true
, and if so print out appropriate information For example, in fragments.cpp
:
...std::vector<std::string> load_fragments (const std::string& filename){ if (debug::verbose) std::cerr << "reading fragments from file \"" << filename << "\"...\n"; ...
We now have all the pieces required to implement our verbose option
debug.h
header file as per the previous slidemain()
:std::erase()
call to detect and handle the -v
optiondebug::verbose
to true
if detected#include "debug.h"
so the debug::verbose
variable is declared at the
point of usedebug::verbose
is true
, and if so print out appropriate information For example, in fragments.cpp
:
...std::vector<std::string> load_fragments (const std::string& filename){ if (debug::verbose) std::cerr << "reading fragments from file \"" << filename << "\"...\n"; ...
Exercise: implement these changes in your own code
We can now run our code as normal:
$ ./shotgun ../data/fragments-1.txt outinitial sequence has size 1000final sequence has length 20108
which provides a clean output, showing just as much information as we might expect when everything works correctly
... or we can run it with the verbose option to see exactly what is going on:
$ ./shotgun ../data/fragments-1.txt out -vreading fragments from file "../data/fragments-1.txt"...read 190 fragments190 fragments, mean fragment length: 529.158, range [ 51 1000 ]initial sequence has size 1000---------------------------------------------------189 fragments leftfragment with biggest overlap is at index 41, overlap = -824---------------------------------------------------...fragment with biggest overlap is at index 37, overlap = 51---------------------------------------------------104 fragments left104 fragments remaining unmatched - checking whether already contained in sequence...final sequence has length 20108writing sequence to file "out"...
The availability of a verbose option allows you and your users to quickly narrow down problems
the default output can be kept clean and minimal
when issues occur, running in verbose mode provide useful information:
when there are performance issues, verbose mode can provide an indication of which stage is taking longer to complete than expected
...
The availability of a verbose option allows you and your users to quickly narrow down problems
the default output can be kept clean and minimal
when issues occur, running in verbose mode provide useful information:
when there are performance issues, verbose mode can provide an indication of which stage is taking longer to complete than expected
...
In general, it is recommended to provide some mechanism to allow users to get detailed information about the processing, whether using a verbose option, or by way of a separate log file
Now that we have the option to provide debugging output, we can wrap up this
functionality more cleanly to avoid having lots of if (debug::verbose)
statements throughout our code
Let's implement a debug::log()
function, which will take a std::string
as
its argument, and print it to the standard error stream if verbose mode is
enabled
[DEBUG]
)Now that we have the option to provide debugging output, we can wrap up this
functionality more cleanly to avoid having lots of if (debug::verbose)
statements throughout our code
Let's implement a debug::log()
function, which will take a std::string
as
its argument, and print it to the standard error stream if verbose mode is
enabled
[DEBUG]
)Exercise: implement this function and make use of it in your own code
In debug.h
:
#pragma onceinclude <iostream>include <string>namespace debug { inline bool verbose = false; inline void log (const std::string& message) { if (verbose) std::cerr << "[DEBUG] " << message << "\n"; }}
In debug.h
:
#pragma onceinclude <iostream>include <string>namespace debug { inline bool verbose = false; inline void log (const std::string& message) { if (verbose) std::cerr << "[DEBUG] " << message << "\n"; }}
Note the use of the inline
keyword here, this time to allow multiple definitions of the same function
inline
Historically, the inline
keyword was considered more as a performance
optimisation feature
inline
can help avoid the overhead of dedicated
function calls, and so improve performanceinline
mechanism was introduced to allow definitions to be
included in header files without causing the multiple definition probleminline
Historically, the inline
keyword was considered more as a performance
optimisation feature
inline
can help avoid the overhead of dedicated
function calls, and so improve performanceinline
mechanism was introduced to allow definitions to be
included in header files without causing the multiple definition problemIn modern C++ (particularly since C++17), the inline
keyword is used to allow multiple definitions of
functions or variables across multiple translation units
We can now use our convenience function in our code, for example in fragments.cpp
:
...std::vector<std::string> load_fragments (const std::string& filename){ if (debug::verbose) std::cerr << "reading fragments from file \"" << filename << "\"...\n"; ...
becomes simply:
...std::vector<std::string> load_fragments (const std::string& filename){ debug::log ("reading fragments from file \"" + filename + "\"..."); ...
We can now use our convenience function in our code, for example in fragments.cpp
:
...std::vector<std::string> load_fragments (const std::string& filename){ if (debug::verbose) std::cerr << "reading fragments from file \"" << filename << "\"...\n"; ...
becomes simply:
...std::vector<std::string> load_fragments (const std::string& filename){ debug::log ("reading fragments from file \"" + filename + "\"..."); ...
Note the use of the +
string concatenation operator instead of the stream
insertion operator (<<
)
This is because our debug::log()
function expects a single std::string
argument
How do we handle this case?
if (debug::verbose) std::cerr << "read " << fragments.size() << " fragments\n";
How do we handle this case?
if (debug::verbose) std::cerr << "read " << fragments.size() << " fragments\n";
We can't write:
debug::log ("read " + fragments.size() + " fragments");
since fragment.size()
returns an int
, and we can't concatenate a
std::string
with an int
!
How do we handle this case?
if (debug::verbose) std::cerr << "read " << fragments.size() << " fragments\n";
We can't write:
debug::log ("read " + fragments.size() + " fragments");
since fragment.size()
returns an int
, and we can't concatenate a
std::string
with an int
!
Use std::to_string()
, which returns a std::string
representation of a
variable:
debug::log ("read " + std::to_string (fragments.size()) + " fragments");
How do we handle this case?
if (debug::verbose) std::cerr << "read " << fragments.size() << " fragments\n";
We can't write:
debug::log ("read " + fragments.size() + " fragments");
since fragment.size()
returns an int
, and we can't concatenate a
std::string
with an int
!
Use std::to_string()
, which returns a std::string
representation of a
variable:
debug::log ("read " + std::to_string (fragments.size()) + " fragments");
Exercise: implement these changes in your own code
We continue working on our DNA shotgun sequencing project
You can find the most up to date version in the project's solution/
folder
Make sure your code is up to date now!
Keyboard shortcuts
↑, ←, Pg Up, k | Go to previous slide |
↓, →, Pg Dn, Space, j | Go to next slide |
Home | Go to first slide |
End | Go to last slide |
Number + Return | Go to specific slide |
b / m / f | Toggle blackout / mirrored / fullscreen mode |
c | Clone slideshow |
p | Toggle presenter mode |
s | Start & Stop the presentation timer |
t | Reset the presentation timer |
?, h | Toggle this help |
Esc | Back to slideshow |