Merge remote-tracking branch 'origin/bstoeger-range'

Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
This commit is contained in:
Dirk Hohndel 2022-11-08 08:13:16 -08:00
commit 46365b3199
13 changed files with 175 additions and 167 deletions

View file

@ -206,6 +206,7 @@ HEADERS += \
core/pref.h \
core/profile.h \
core/qthelper.h \
core/range.h \
core/save-html.h \
core/statistics.h \
core/units.h \

View file

@ -147,6 +147,7 @@ set(SUBSURFACE_CORE_LIB_SRCS
qt-init.cpp
qthelper.cpp
qthelper.h
range.h
sample.c
sample.h
save-git.c

View file

@ -104,26 +104,11 @@ static const char *negate_description[2] {
QT_TRANSLATE_NOOP("gettextFromC", "is not"),
};
static constexpr size_t type_descriptions_count()
{
return std::size(type_descriptions);
}
static constexpr size_t string_mode_descriptions_count()
{
return std::size(string_mode_descriptions);
}
static constexpr size_t range_mode_descriptions_count()
{
return std::size(range_mode_descriptions);
}
static const type_description *get_type_description(enum filter_constraint_type type)
{
for (size_t i = 0; i < type_descriptions_count(); ++i) {
if (type_descriptions[i].type == type)
return &type_descriptions[i];
for (const auto &desc: type_descriptions) {
if (desc.type == type)
return &desc;
}
report_error("unknown filter constraint type: %d", type);
return nullptr;
@ -131,9 +116,9 @@ static const type_description *get_type_description(enum filter_constraint_type
static const string_mode_description *get_string_mode_description(enum filter_constraint_string_mode mode)
{
for (size_t i = 0; i < string_mode_descriptions_count(); ++i) {
if (string_mode_descriptions[i].mode == mode)
return &string_mode_descriptions[i];
for (const auto &desc: string_mode_descriptions) {
if (desc.mode == mode)
return &desc;
}
report_error("unknown filter constraint string mode: %d", mode);
return nullptr;
@ -141,9 +126,9 @@ static const string_mode_description *get_string_mode_description(enum filter_co
static const range_mode_description *get_range_mode_description(enum filter_constraint_range_mode mode)
{
for (size_t i = 0; i < range_mode_descriptions_count(); ++i) {
if (range_mode_descriptions[i].mode == mode)
return &range_mode_descriptions[i];
for (const auto &desc: range_mode_descriptions) {
if (desc.mode == mode)
return &desc;
}
report_error("unknown filter constraint range mode: %d", mode);
return nullptr;
@ -151,9 +136,9 @@ static const range_mode_description *get_range_mode_description(enum filter_cons
static enum filter_constraint_type filter_constraint_type_from_string(const char *s)
{
for (size_t i = 0; i < type_descriptions_count(); ++i) {
if (same_string(type_descriptions[i].token, s))
return type_descriptions[i].type;
for (const auto &desc: type_descriptions) {
if (same_string(desc.token, s))
return desc.type;
}
report_error("unknown filter constraint type: %s", s);
return FILTER_CONSTRAINT_DATE;
@ -161,9 +146,9 @@ static enum filter_constraint_type filter_constraint_type_from_string(const char
static enum filter_constraint_string_mode filter_constraint_string_mode_from_string(const char *s)
{
for (size_t i = 0; i < string_mode_descriptions_count(); ++i) {
if (same_string(string_mode_descriptions[i].token, s))
return string_mode_descriptions[i].mode;
for (const auto &desc: string_mode_descriptions) {
if (same_string(desc.token, s))
return desc.mode;
}
report_error("unknown filter constraint string mode: %s", s);
return FILTER_CONSTRAINT_EXACT;
@ -171,9 +156,9 @@ static enum filter_constraint_string_mode filter_constraint_string_mode_from_str
static enum filter_constraint_range_mode filter_constraint_range_mode_from_string(const char *s)
{
for (size_t i = 0; i < range_mode_descriptions_count(); ++i) {
if (same_string(range_mode_descriptions[i].token, s))
return range_mode_descriptions[i].mode;
for (const auto &desc: range_mode_descriptions) {
if (same_string(desc.token, s))
return desc.mode;
}
report_error("unknown filter constraint range mode: %s", s);
return FILTER_CONSTRAINT_EQUAL;
@ -217,21 +202,21 @@ extern "C" int filter_constraint_range_mode_to_index(enum filter_constraint_rang
extern "C" enum filter_constraint_type filter_constraint_type_from_index(int index)
{
if (index >= 0 && index < (int)type_descriptions_count())
if (index >= 0 && index < (int)std::size(type_descriptions))
return type_descriptions[index].type;
return (enum filter_constraint_type)-1;
}
extern "C" enum filter_constraint_string_mode filter_constraint_string_mode_from_index(int index)
{
if (index >= 0 && index < (int)string_mode_descriptions_count())
if (index >= 0 && index < (int)std::size(string_mode_descriptions))
return string_mode_descriptions[index].mode;
return (enum filter_constraint_string_mode)-1;
}
extern "C" enum filter_constraint_range_mode filter_constraint_range_mode_from_index(int index)
{
if (index >= 0 && index < (int)range_mode_descriptions_count())
if (index >= 0 && index < (int)std::size(range_mode_descriptions))
return range_mode_descriptions[index].mode;
return (enum filter_constraint_range_mode)-1;
}
@ -347,32 +332,32 @@ static double base_to_display_unit(int i, enum filter_constraint_type type)
QStringList filter_constraint_type_list_translated()
{
QStringList res;
for (size_t i = 0; i < type_descriptions_count(); ++i)
res.push_back(gettextFromC::tr(type_descriptions[i].text_ui));
for (const auto &desc: type_descriptions)
res.push_back(gettextFromC::tr(desc.text_ui));
return res;
}
QStringList filter_constraint_string_mode_list_translated()
{
QStringList res;
for (size_t i = 0; i < string_mode_descriptions_count(); ++i)
res.push_back(gettextFromC::tr(string_mode_descriptions[i].text_ui));
for (const auto &desc: string_mode_descriptions)
res.push_back(gettextFromC::tr(desc.text_ui));
return res;
}
QStringList filter_constraint_range_mode_list_translated()
{
QStringList res;
for (size_t i = 0; i < range_mode_descriptions_count(); ++i)
res.push_back(gettextFromC::tr(range_mode_descriptions[i].text_ui));
for (const auto &desc: range_mode_descriptions)
res.push_back(gettextFromC::tr(desc.text_ui));
return res;
}
QStringList filter_constraint_range_mode_list_translated_date()
{
QStringList res;
for (size_t i = 0; i < range_mode_descriptions_count(); ++i)
res.push_back(gettextFromC::tr(range_mode_descriptions[i].text_ui_date));
for (const auto &desc: range_mode_descriptions)
res.push_back(gettextFromC::tr(desc.text_ui_date));
return res;
}

View file

@ -92,7 +92,7 @@ int get_cylinderid_at_time(struct dive *dive, struct divecomputer *dc, duration_
return cylinder_idx;
}
int get_gasidx(struct dive *dive, struct gasmix mix)
static int get_gasidx(struct dive *dive, struct gasmix mix)
{
return find_best_gasmix_match(mix, &dive->cylinders);
}

View file

@ -42,22 +42,13 @@ extern "C" {
extern int validate_gas(const char *text, struct gasmix *gas);
extern int validate_po2(const char *text, int *mbar_po2);
extern timestamp_t current_time_notz(void);
extern void set_last_stop(bool last_stop_6m);
extern void set_verbatim(bool verbatim);
extern void set_display_runtime(bool display);
extern void set_display_duration(bool display);
extern void set_display_transitions(bool display);
extern int get_cylinderid_at_time(struct dive *dive, struct divecomputer *dc, duration_t time);
extern int get_gasidx(struct dive *dive, struct gasmix mix);
extern bool diveplan_empty(struct diveplan *diveplan);
extern void add_plan_to_notes(struct diveplan *diveplan, struct dive *dive, bool show_disclaimer, int error);
extern const char *get_planner_disclaimer();
extern char *get_planner_disclaimer_formatted();
extern void free_dps(struct diveplan *diveplan);
extern struct dive *planned_dive;
extern char *cache_data;
struct divedatapoint *plan_add_segment(struct diveplan *diveplan, int duration, int depth, int cylinderid, int po2, bool entered, enum divemode_t divemode);
struct divedatapoint *create_dp(int time_incr, int depth, int cylinderid, int po2);

View file

@ -36,7 +36,6 @@
#include <QApplication>
#include <QTextDocument>
#include <QPainter>
#include <QProgressDialog> // TODO: remove with convertThumbnails()
#include <QSvgRenderer>
#include <cstdarg>
#include <cstdint>
@ -676,7 +675,7 @@ QString get_water_type_string(int salinity)
QStringList getWaterTypesAsString()
{
QStringList res;
res.reserve(std::end(waterTypes) - std::begin(waterTypes)); // Waiting for C++17's std::size()
res.reserve(std::size(waterTypes));
for (const char *t: waterTypes)
res.push_back(gettextFromC::tr(t));
return res;
@ -719,17 +718,17 @@ static const char *printing_templates = "printing_templates";
QString getPrintingTemplatePathUser()
{
static QString path = QString();
if (path.isEmpty())
path = QString(system_default_directory()) + QDir::separator() + QString(printing_templates);
// Function-local statics are initialized on first invocation
static QString path(QString(system_default_directory()) +
QDir::separator() +
QString(printing_templates));
return path;
}
QString getPrintingTemplatePathBundle()
{
static QString path = QString();
if (path.isEmpty())
path = getSubsurfaceDataPath(printing_templates);
// Function-local statics are initialized on first invocation
static QString path(getSubsurfaceDataPath(printing_templates));
return path;
}
@ -1072,45 +1071,6 @@ extern "C" char *hashfile_name_string()
return copy_qstring(hashfile_name());
}
// During a transition period, convert old thumbnail-hashes to individual files
// TODO: remove this code in due course
static void convertThumbnails(const QHash <QString, QImage> &thumbnails)
{
if (thumbnails.empty())
return;
// This is a singular occurrence, therefore translating the strings seems not worth it
QProgressDialog progress("Convert thumbnails...", "Abort", 0, thumbnails.size());
progress.setWindowModality(Qt::WindowModal);
int count = 0;
for (const QString &name: thumbnails.keys()) {
const QImage thumbnail = thumbnails[name];
if (thumbnail.isNull())
continue;
// This is duplicate code (see qt-models/divepicturemodel.cpp)
// Not a problem, since this routine will be removed in due course.
QString filename = thumbnailFileName(name);
if (filename.isEmpty())
continue;
QSaveFile file(filename);
if (!file.open(QIODevice::WriteOnly))
return;
QDataStream stream(&file);
quint32 type = MEDIATYPE_PICTURE;
stream << type;
stream << thumbnail;
file.commit();
progress.setValue(++count);
if (progress.wasCanceled())
break;
}
}
// TODO: This is a temporary helper struct. Remove in due course with convertLocalFilename().
struct HashToFile {
QByteArray hash;
@ -1129,7 +1089,7 @@ static void convertLocalFilename(const QHash<QString, QByteArray> &hashOf, const
return;
// Create a vector of hash/filename pairs and sort by hash.
// Elements can than be accessed with binary search.
// Elements can then be accessed with binary search.
QHash<QByteArray, QString> canonicalFilenameByHash;
QVector<HashToFile> h2f;
h2f.reserve(hashOf.size());
@ -1166,7 +1126,6 @@ void read_hashes()
stream >> localFilenameOf;
locker.unlock();
hashfile.close();
convertThumbnails(thumbnailCache);
convertLocalFilename(hashOf, localFilenameByHash);
}
QMutexLocker locker(&hashOfMutex);

View file

@ -108,31 +108,6 @@ void uiNotification(const QString &msg);
#define TITLE_OR_TEXT(_t, _m) _t, _m
#endif
// Move a range in a vector to a different position.
// The parameters are given according to the usual STL-semantics:
// v: a container with STL-like random access iterator via std::begin(...)
// rangeBegin: index of first element
// rangeEnd: index one *past* last element
// destination: index to element before which the range will be moved
// Owing to std::begin() magic, this function works with STL-like containers:
// QVector<int> v{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
// moveInVector(v, 1, 4, 6);
// as well as with C-style arrays:
// int array[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
// moveInVector(array, 1, 4, 6);
// Both calls will have the following effect:
// Before: 0 1 2 3 4 5 6 7 8 9
// After: 0 4 5 1 2 3 6 7 8 9
// No sanitizing of the input arguments is performed.
template <typename Vector>
void moveInVector(Vector &v, int rangeBegin, int rangeEnd, int destination)
{
auto it = std::begin(v);
if (destination > rangeEnd)
std::rotate(it + rangeBegin, it + rangeEnd, it + destination);
else if (destination < rangeBegin)
std::rotate(it + destination, it + rangeBegin, it + rangeEnd);
}
#endif
// 3) Functions visible to C and C++

101
core/range.h Normal file
View file

@ -0,0 +1,101 @@
// SPDX-License-Identifier: GPL-2.0
// Helper functions for range manipulations
#ifndef RANGE_H
#define RANGE_H
#include <utility> // for std::pair
#include <vector> // we need a declaration of std::begin() and std::end()
// Move a range in a vector to a different position.
// The parameters are given according to the usual STL-semantics:
// v: a container with STL-like random access iterator via std::begin(...)
// rangeBegin: index of first element
// rangeEnd: index one *past* last element
// destination: index to element before which the range will be moved
// Owing to std::begin() magic, this function works with STL-like containers:
// QVector<int> v{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
// move_in_range(v, 1, 4, 6);
// as well as with C-style arrays:
// int array[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
// move_in_range(array, 1, 4, 6);
// Both calls will have the following effect:
// Before: 0 1 2 3 4 5 6 7 8 9
// After: 0 4 5 1 2 3 6 7 8 9
// No sanitizing of the input arguments is performed.
template <typename Range>
void move_in_range(Range &v, int rangeBegin, int rangeEnd, int destination)
{
auto it = std::begin(v);
if (destination > rangeEnd)
std::rotate(it + rangeBegin, it + rangeEnd, it + destination);
else if (destination < rangeBegin)
std::rotate(it + destination, it + rangeBegin, it + rangeEnd);
}
// A rudimentary adaptor for looping over ranges with an index:
// for (auto [idx, item]: enumerated_range(v)) ...
// The index is a signed integer, since this is what we use more often.
template <typename Range>
class enumerated_range
{
Range &base;
public:
using base_iterator = decltype(std::begin(std::declval<Range>()));
class iterator {
int idx;
base_iterator it;
public:
std::pair<int, decltype(*it)> operator*() const
{
return { idx, *it };
}
iterator &operator++()
{
++idx;
++it;
return *this;
}
iterator(int idx, base_iterator it) : idx(idx), it(it)
{
}
bool operator==(const iterator &it2) const
{
return it == it2.it;
}
bool operator!=(const iterator &it2) const
{
return it != it2.it;
}
};
iterator begin()
{
return iterator(0, std::begin(base));
}
iterator end()
{
return iterator(-1, std::end(base));
}
enumerated_range(Range &base) : base(base)
{
}
};
// Find the index of an element in a range. Return -1 if not found
// Range must have a random access iterator.
template <typename Range, typename Element>
int index_of(const Range &range, const Element &e)
{
auto it = std::find(std::begin(range), std::end(range), e);
return it == std::end(range) ? -1 : it - std::begin(range);
}
template <typename Range, typename Func>
int index_of_if(const Range &range, Func f)
{
auto it = std::find_if(std::begin(range), std::end(range), f);
return it == std::end(range) ? -1 : it - std::begin(range);
}
#endif

View file

@ -3,6 +3,7 @@
#include "templateedit.h"
#include "templatelayout.h"
#include "core/qthelper.h"
#include "core/range.h"
#include <QDebug>
#include <QFileDialog>
@ -63,10 +64,10 @@ void PrintOptions::setupTemplates()
currList.sort();
int current_index = 0;
ui.printTemplate->clear();
Q_FOREACH(const QString& theme, currList) {
for (auto [idx, theme]: enumerated_range(currList)) {
// find the stored template in the list
if (theme == storedTemplate || theme == lastImportExportTemplate)
current_index = currList.indexOf(theme);
current_index = idx;
ui.printTemplate->addItem(theme.split('.')[0], theme);
}
ui.printTemplate->setCurrentIndex(current_index);

View file

@ -5,6 +5,7 @@
#include "core/event.h"
#include "core/subsurface-string.h"
#include "core/qthelper.h"
#include "core/range.h"
#include "core/settings/qPrefTechnicalDetails.h"
#include "core/settings/qPrefPartialPressureGas.h"
#include "profile-widget/diveeventitem.h"
@ -870,8 +871,8 @@ void ProfileWidget2::pointsRemoved(const QModelIndex &, int start, int end)
void ProfileWidget2::pointsMoved(const QModelIndex &, int start, int end, const QModelIndex &, int row)
{
moveInVector(handles, start, end + 1, row);
moveInVector(gases, start, end + 1, row);
move_in_range(handles, start, end + 1, row);
move_in_range(gases, start, end + 1, row);
}
void ProfileWidget2::repositionDiveHandlers()
@ -1319,7 +1320,7 @@ void ProfileWidget2::pictureOffsetChanged(dive *dIn, QString filename, offset_t
// Move image from old to new position
int oldIndex = oldPos - pictures.begin();
int newIndex = newPos - pictures.begin();
moveInVector(pictures, oldIndex, oldIndex + 1, newIndex);
move_in_range(pictures, oldIndex, oldIndex + 1, newIndex);
} else {
// Case 1b): remove picture
pictures.erase(oldPos);

View file

@ -5,6 +5,7 @@
#include "core/imagedownloader.h"
#include "core/picture.h"
#include "core/qthelper.h"
#include "core/range.h"
#include "core/selection.h"
#include "core/subsurface-qt/divelistnotifier.h"
#include "commands/command.h"
@ -205,8 +206,8 @@ void DivePictureModel::picturesAdded(dive *d, QVector<PictureObj> picsIn)
// Convert the picture-data into our own format
std::vector<PictureEntry> pics;
pics.reserve(picsIn.size());
for (int i = 0; i < picsIn.size(); ++i)
pics.push_back(PictureEntry(d, picsIn[i]));
for (const PictureObj &pic: picsIn)
pics.push_back(PictureEntry(d, pic));
// Insert batch-wise to avoid too many reloads
pictures.reserve(pictures.size() + pics.size());
@ -241,10 +242,8 @@ int DivePictureModel::rowCount(const QModelIndex&) const
int DivePictureModel::findPictureId(const std::string &filename)
{
for (int i = 0; i < (int)pictures.size(); ++i)
if (pictures[i].filename == filename)
return i;
return -1;
return index_of_if(pictures, [&filename](const PictureEntry &p)
{ return p.filename == filename; });
}
static void addDurationToThumbnail(QImage &img, duration_t duration)
@ -312,6 +311,6 @@ void DivePictureModel::pictureOffsetChanged(dive *d, const QString filenameIn, o
if (oldIndex == newIndex || oldIndex + 1 == newIndex)
return;
beginMoveRows(QModelIndex(), oldIndex, oldIndex, QModelIndex(), newIndex);
moveInVector(pictures, oldIndex, oldIndex + 1, newIndex);
move_in_range(pictures, oldIndex, oldIndex + 1, newIndex);
endMoveRows();
}

View file

@ -7,6 +7,7 @@
#include "qt-models/models.h"
#include "core/device.h"
#include "core/qthelper.h"
#include "core/range.h"
#include "core/sample.h"
#include "core/settings/qPrefDivePlanner.h"
#include "core/settings/qPrefUnit.h"
@ -877,7 +878,7 @@ void DivePlannerPointsModel::editStop(int row, divedatapoint newData)
if (newRow != row && newRow != row + 1) {
beginMoveRows(QModelIndex(), row, row, QModelIndex(), newRow);
moveInVector(divepoints, row, row + 1, newRow);
move_in_range(divepoints, row, row + 1, newRow);
endMoveRows();
// Account for moving the row backwards in the array.

View file

@ -10,6 +10,7 @@
#include "core/string-format.h"
#include "core/trip.h"
#include "core/qthelper.h"
#include "core/range.h"
#include "core/divesite.h"
#include "core/picture.h"
#include "core/subsurface-string.h"
@ -205,8 +206,8 @@ static QPixmap &getPhotoIcon(int idx)
if (!icons) {
const IconMetrics &im = defaultIconMetrics();
icons = std::make_unique<QPixmap[]>(std::size(icon_names));
for (size_t i = 0; i < std::size(icon_names); ++i)
icons[i] = QIcon(icon_names[i]).pixmap(im.sz_small, im.sz_small);
for (auto [i, name]: enumerated_range(icon_names))
icons[i] = QIcon(name).pixmap(im.sz_small, im.sz_small);
}
return icons[idx];
}
@ -974,7 +975,7 @@ void DiveTripModelTree::topLevelChanged(int idx)
// If index changed, move items
if (newIdx != idx && newIdx != idx + 1) {
beginMoveRows(QModelIndex(), idx, idx, QModelIndex(), newIdx);
moveInVector(items, idx, idx + 1, newIdx);
move_in_range(items, idx, idx + 1, newIdx);
endMoveRows();
}
@ -1007,34 +1008,26 @@ void DiveTripModelTree::addDivesToTrip(int trip, const QVector<dive *> &dives)
int DiveTripModelTree::findTripIdx(const dive_trip *trip) const
{
for (int i = 0; i < (int)items.size(); ++i)
if (items[i].d_or_t.trip == trip)
return i;
return -1;
return index_of_if(items, [trip] (const Item &item)
{ return item.d_or_t.trip == trip; });
}
int DiveTripModelTree::findDiveIdx(const dive *d) const
{
for (int i = 0; i < (int)items.size(); ++i)
if (items[i].isDive(d))
return i;
return -1;
return index_of_if(items, [d] (const Item &item)
{ return item.isDive(d); });
}
int DiveTripModelTree::findDiveInTrip(int tripIdx, const dive *d) const
{
const Item &item = items[tripIdx];
for (int i = 0; i < (int)item.dives.size(); ++i)
if (item.dives[i] == d)
return i;
return -1;
return index_of(items[tripIdx].dives, d);
}
int DiveTripModelTree::findInsertionIndex(const dive_trip *trip) const
{
dive_or_trip d_or_t{ nullptr, (dive_trip *)trip };
for (int i = 0; i < (int)items.size(); ++i) {
if (dive_or_trip_less_than(d_or_t, items[i].d_or_t))
for (auto [i, item]: enumerated_range(items)) {
if (dive_or_trip_less_than(d_or_t, item.d_or_t))
return i;
}
return items.size();
@ -1121,8 +1114,8 @@ static QVector<dive *> visibleDives(const QVector<dive *> &dives)
#ifdef SUBSURFACE_MOBILE
int DiveTripModelTree::tripInDirection(const struct dive *d, int direction) const
{
for (int i = 0; i < (int)items.size(); ++i) {
if (items[i].d_or_t.dive == d || (items[i].d_or_t.trip && findDiveInTrip(i, d) != -1)) {
for (auto [i, item]: enumerated_range(items)) {
if (item.d_or_t.dive == d || (item.d_or_t.trip && findDiveInTrip(i, d) != -1)) {
// now walk in the direction given to find a trip
int offset = direction;
while (i + offset >= 0 && i + offset < (int)items.size()) {
@ -1405,8 +1398,8 @@ void DiveTripModelTree::divesSelectedTrip(dive_trip *trip, const QVector<dive *>
// Since both lists are sorted, we can do this linearly. Perhaps a binary search
// would be better?
int j = 0; // Index in items array
for (int i = 0; i < dives.size(); ++i) {
while (j < (int)items.size() && !items[j].isDive(dives[i]))
for (struct dive *dive: dives) {
while (j < (int)items.size() && !items[j].isDive(dive))
++j;
if (j >= (int)items.size())
break;
@ -1426,8 +1419,8 @@ void DiveTripModelTree::divesSelectedTrip(dive_trip *trip, const QVector<dive *>
// would be better?
int j = 0; // Index in items array
const Item &entry = items[idx];
for (int i = 0; i < dives.size(); ++i) {
while (j < (int)entry.dives.size() && entry.dives[j] != dives[i])
for (struct dive *dive: dives) {
while (j < (int)entry.dives.size() && entry.dives[j] != dive)
++j;
if (j >= (int)entry.dives.size())
break;
@ -1662,8 +1655,8 @@ void DiveTripModelList::divesSelected(const QVector<dive *> &divesIn)
// Since both lists are sorted, we can do this linearly. Perhaps a binary search
// would be better?
int j = 0; // Index in items array
for (int i = 0; i < dives.size(); ++i) {
while (j < (int)items.size() && items[j] != dives[i])
for (struct dive *dive: dives) {
while (j < (int)items.size() && items[j] != dive)
++j;
if (j >= (int)items.size())
break;