blob: 957ff3994a27176469b49bb2e9589f6d9a05041c [file]
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// -*- mode: C++ -*-
//
// Copyright 2021-2026 Google LLC
//
// Licensed under the Apache License v2.0 with LLVM Exceptions (the
// "License"); you may not use this file except in compliance with the
// License. You may obtain a copy of the License at
//
// https://llvm.org/LICENSE.txt
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: Giuliano Procida
#ifndef STG_ORDER_H_
#define STG_ORDER_H_
#include <algorithm>
#include <cstddef>
#include <type_traits>
#include <unordered_map>
#include <utility>
#include <vector>
#include "error.h"
namespace stg {
// Match and reorder two collections using a key-extraction lambda.
// Invokes callbacks on-the-fly for unmatched and matched elements in a
// greedy, order-preserving manner.
template <typename T, typename ExtractKey, typename Removed, typename Added,
typename InBoth>
void MatchReorderForEach(const std::vector<T>& items1,
const std::vector<T>& items2, ExtractKey&& extract_key,
Removed&& removed, Added&& added, InBoth&& in_both) {
using Key = std::decay_t<std::invoke_result_t<ExtractKey, const T&>>;
// Copy the key extractor to ensure that if it is stateful (e.g. anonymous
// counter), both phases (building the map and matching) start with the same
// state and thus generate the same keys for matching.
auto extract_key2 = extract_key;
const size_t size1 = items1.size();
const size_t size2 = items2.size();
struct IndexMatch {
size_t index;
bool matched;
};
// map Key to index in items2
std::unordered_map<Key, IndexMatch> key_to_index2;
key_to_index2.reserve(size2);
for (size_t ix2 = 0; ix2 < size2; ++ix2) {
const auto [it, inserted] = key_to_index2.emplace(extract_key2(items2[ix2]),
IndexMatch{ix2, false});
Check(inserted) << "MatchReorderForEach: duplicate key in items2";
}
// map index in items1 to index in items2 (or size2 if unmatched)
std::vector<size_t> ix1_to_ix2(size1, size2);
// map index in items2 to index in items1 (or size1 if unmatched)
std::vector<size_t> ix2_to_ix1(size2, size1);
for (size_t ix1 = 0; ix1 < size1; ++ix1) {
const auto key = extract_key(items1[ix1]);
const auto it = key_to_index2.find(key);
if (it != key_to_index2.end()) {
auto& match = it->second;
Check(!match.matched) << "MatchReorderForEach: duplicate key in items1";
match.matched = true;
ix1_to_ix2[ix1] = match.index;
ix2_to_ix1[match.index] = ix1;
}
}
// keep track of where we are up to in items2
size_t position2 = 0;
for (size_t ix1 = 0; ix1 < size1; ++ix1) {
const auto match_ix2 = ix1_to_ix2[ix1];
if (match_ix2 == size2) {
// ix1 is unmatched (removed)
removed(items1[ix1]);
} else {
// ix1 is matched at match_ix2
// If match_ix2 is already output (because we pulled it forward), do
// nothing.
if (match_ix2 >= position2) {
// output all items in items2 from position2 up to match_ix2 (inclusive)
for (size_t i = position2; i <= match_ix2; ++i) {
const auto match_ix1 = ix2_to_ix1[i];
if (match_ix1 == size1) {
added(items2[i]);
} else {
in_both(items1[match_ix1], items2[i]);
}
}
position2 = match_ix2 + 1;
}
}
}
// output remaining items in items2 (all unmatched)
for (size_t i = position2; i < size2; ++i) {
added(items2[i]);
}
}
} // namespace stg
#endif // STG_ORDER_H_