间隔映射实现

interval map implementation

本文关键字:实现 映射      更新时间:2023-10-16

我在一家IT公司的C++职位上发送了一份申请。他们给我发了一份测试作业。

任务是实现间隔映射分配操作。我向他们发送了我的解决方案,但它没有通过第二个要求(正确的行为(。他们没有给出更多的反馈,只是说我的代码没有通过他们的所有测试。现在我想知道我能做错什么。当然,在发送解决方案之前,我做了一些测试,我能想到的每个测试都通过了。

现在我无法入睡,不知道我在哪里可以把它搞砸。

这是我的代码:

void assign (const K & keyBegin, const K & keyEnd, const V & val )
{
if (!(keyBegin < keyEnd))
return;
auto nextInterval = --m_map.upper_bound(keyEnd);
auto inserted1 = m_map.end();
auto inserted2 = m_map.end();
if (nextInterval->second == val)
++nextInterval;
else if (nextInterval->first < keyEnd)
{
const V & nextValue = nextInterval->second;
++nextInterval;
inserted1 = nextInterval = m_map.emplace_hint(nextInterval, keyEnd, nextValue);
}
try
{
auto prevInterval = nextInterval;
--prevInterval;
if (keyBegin < prevInterval->first)
prevInterval = --m_map.upper_bound(keyBegin);
if (!(prevInterval->second == val))
{
if (prevInterval->first < keyBegin)
{
++prevInterval;
inserted2 = prevInterval = m_map.emplace_hint(prevInterval, keyBegin, val);
}
else
{
auto beforePrev = prevInterval;
--beforePrev;
if (beforePrev != m_map.end() && beforePrev->second == val)
prevInterval = beforePrev;
else
{
auto hint = m_map.erase(prevInterval);
inserted2 = prevInterval = m_map.emplace_hint(hint, keyBegin, val);
}
}
}
m_map.erase(++prevInterval, nextInterval);
}
catch (...)
{
if (inserted1 != m_map.end())
m_map.erase(inserted1);
if (inserted2 != m_map.end())
m_map.erase(inserted2);
throw;
}
}

你能帮我找到一个错误吗?

通过递减地图的开头来获得 UB:

auto beforePrev = prevInterval;
--beforePrev;

演示

你下面的测试也很奇怪:

if (beforePrev != m_map.end()

之前Prev不能在你递减它时end()

似乎您可以将该块替换为

prevInterval->second = val;
if (prevInterval != m_map.begin() && !((--prevInterval)->second == val)){
++prevInterval;
} 

演示

首先写测试代码:这是测试类型要求

class Value {
char a;
public:
Value(char _a){ a = _a; }
bool operator==(const Value& _val) const;
friend ostream& operator<<(ostream& os, const Value& val); //  ONLY FOR TESTING, NOT RELATED TO SOLUTION
};
bool Value::operator==( const Value& _val ) const{
return ( a == _val.a ) ;
}
// operator<< is implemented ONLY FOR TESTING PURPOSE
ostream& operator<<(ostream& os, const Value& val)
{
os << val.a;
return os;
}
class Key {
int a;
public:
Key(int _a){ a = _a; }
Key(){ a = 0; }
bool operator<(const Key& _key) const;
friend ostream& operator<<(ostream& os, const Key& _key); //  ONLY FOR TESTING, NOT RELATED TO SOLUTION

};

bool Key::operator<( const Key& _key ) const{
return ( a < _key.a ) ;
}

// operator<< is implemented ONLY FOR TESTING PURPOSE
ostream& operator<<(ostream& os, const Key& _key)
{
os <<  _key.a;
return os;
}

现在实施

void assign( K const& keyBegin, K const& keyEnd, V const& val ) {
K last = m_map.rbegin()->first;
K first = m_map.begin()->first;

if (!(keyBegin < keyEnd) ) return;
if ( keyBegin < first ) return;
if( last < keyEnd) return ;
for (auto it = m_map.begin(); it!=m_map.end(); ++it)
{
if((*it).first < keyBegin) continue;
else
{
(*it).second=val;
it++;
auto tmp=it;
while((*it).first < keyEnd){
it++;
}
m_map.erase(tmp, it);
break;
}
}
}

另一种解决方案

void assign( K const& keyBegin, K const& keyEnd, V const& val ) {
K last = m_map.rbegin()->first;
K first = m_map.begin()->first;

if (!(keyBegin < keyEnd) ) return;
if ( keyBegin < first ) return;
if( last < keyEnd) return ;
auto itr1 = m_map.find(keyBegin);
auto itr2 = m_map.find(keyEnd);
(*itr1).second=val;
itr1++;
m_map.erase(itr1,itr2);
cout << endl << endl;
}

上面来自 Vivek 的两个解决方案不起作用。这是我能想到的最简单和最短的实现:

if (not (keyBegin < keyEnd) )
return;
auto beginLowerBound = m_map.lower_bound(keyBegin);
auto endLowerBound = m_map.lower_bound(keyEnd);
std::optional<std::pair<K,V>> additionalElement;
if (endLowerBound == m_map.end() || keyEnd < endLowerBound->first)
additionalElement = std::pair(keyEnd, operator[]( keyEnd ));
else if (beginLowerBound == m_map.end() || keyBegin < beginLowerBound->first)
additionalElement = std::pair(keyEnd, operator[]( keyBegin ));
if (beginLowerBound != m_map.end())
m_map.erase(beginLowerBound, endLowerBound);
m_map.insert(std::pair(keyBegin, val));
if (additionalElement)
m_map.insert(additionalElement.value());

我刚刚接受了同样的采访。我的方法与上述所有方法都不同 - 但有效。我在测试场景上花了很多精力。不幸的是,我正在使用 [] 运算符来分配映射值。这需要使用默认构造函数 - 这显然是不允许的(虽然没有明确说明(。尽管他们建议使用 <int、char> 进行测试 - 当然两者都有默认构造函数。

if (!(keyBegin < keyEnd))
return;
auto upper_bound_end = m_map.upper_bound(keyEnd);
auto upper_bound_end2 = upper_bound_end;
upper_bound_end2--;     
auto uper_bound_begin = m_map.upper_bound(keyBegin);        
auto uper_bound_begin2 = uper_bound_begin;
uper_bound_begin2--;
bool flag = false;
bool flag2 = false;
if (!((uper_bound_begin2)->second == val))
flag = true;
V tmp;
if (!((upper_bound_end2)->second == val))
{
flag2 = true;
tmp = (upper_bound_end2)->second;
}   
for (auto it = uper_bound_begin; it != upper_bound_end;)
{
it = m_map.erase(it);
}
if (flag == true)
m_map[keyBegin] = val;
if (flag2 == true)
m_map[keyEnd] = tmp;

这是我的解决方案,可以替换上面问题中的assign函数。最初的问题是要求将其实现为类成员函数,但这里的问题省略了类部分。因此,您可以忽略templateinterval_map<K, V>,以便能够匹配问题中的解决方案,就像本页中的其他答案一样。

在实现时,我将assign函数想象为在条带中的现有颜色上绘制新颜色。此处的map仅包含起点和颜色(即value(中每个涂漆区域的条带。

template <typename K, typename V>
void interval_map<K, V>::assign(const K& keyBegin, const K& keyEnd, const V& val)
{
using iterator = typename std::map<K, V>::iterator;
if ( !(keyBegin < keyEnd) )
return;
// Handle keyEnd first to not impact the handling of keyBegin.
iterator it1 = m_map.upper_bound(keyEnd);
--it1;  // it1 points to the greatest interval whose key <= keyEnd. It exists always.
V& old_val = it1->second;
++it1;
if ( old_val != val )
// If old_val from the greatest interval does not equal the desired val, we cut
// off the interval at keyEnd unless the interval already starts with keyEnd, in
// which case we do nothing.
// If old_val equals val we set up to erase up to that interval.
it1 = m_map.try_emplace(it1, keyEnd, std::move(old_val));
// Now it1 points to the first interval starting from keyEnd that has a value
// different than `val`, or m_map.end() if no such interval exits.
iterator it0 = m_map.lower_bound(keyBegin);
--it0;  // it0 points to the greatest interval whose key < keyBegin. It exists always.
if ( it0->second != val )
{
// If the greatest interval has a value different than `val`, we create a new
// interval at keyBegin, or if there already exists an interval starting at
// keyBegin we change the value.
// If the greatest interval has `val` as the value we make it our interval to
// cover [keyBegin, keyEnd).
++it0;
it0 = m_map.insert_or_assign(it0, keyBegin, val);
}
// Now it0 points to the first interval that covers keyBegin and has the value `val`.
// Extend it0 until it1, that is, erase all iterators {it | it0 < it < it1}.
++it0;
m_map.erase(it0, it1);
}

仅供参考,我还编写了一个验证函数,可以在每次assign后调用该函数来验证整个地图的有效性。希望这有助于了解地图的属性,以满足assign操作。

template <typename K, typename V>
void interval_map<K, V>::_verify(const K& keyBegin, const K& keyEnd, const V& val) const
{
// 1. m_map should be non-empty.
assert( m_map.begin() != m_map.end() );
auto it = m_map.begin();
V prev_val = it->second;
while ( ++it != m_map.end() )
{
// 2. Adjacent intervals should have different values.
assert( prev_val != it->second );
prev_val = it->second;
}
auto it0 = --m_map.upper_bound(keyBegin);
auto it1 = --m_map.lower_bound(keyEnd);
// 3. There should be only one interval that covers [keyBegin, keyEnd).
assert( it0 == it1 );
// 4. The interval should have the disired value.
assert( val == it0->second );
}

试试这段代码:

#include <iostream>
#include <map>
using namespace std;
template < typename K, typename V >
class interval_map {
std::map < K, V > m_map;
public:
// constructor
interval_map(V
const & val) {
m_map.insert(m_map.begin(), std::make_pair(std::numeric_limits < K > ::lowest(), val));
}
// insert an interval and value
void insert(K
const & keyBegin, K
const & keyEnd, V
const & val) {
// check if the interval is valid
if (!(keyBegin < keyEnd))
return;
// find the entry for the interval start
auto lb = m_map.lower_bound(keyBegin);
if (lb != m_map.begin() && (--lb) -> second == val) {
++lb;
}
// remove all entries in the interval
auto up = m_map.upper_bound(keyEnd);
while (lb != up) {
lb = m_map.erase(lb);
}
// insert the new interval
m_map.insert(lb, std::make_pair(keyBegin, val));
m_map.insert(up, std::make_pair(keyEnd,
m_map.find(keyBegin) -> second));
}
};

下面的完整工作代码,在主((中进行测试(但雇主说模板类型的使用不正确(

#define _ITERATOR_DEBUG_LEVEL 2
#define _SECURE_SCL 1
#define _HAS_ITERATOR_DEBUGGING 1
#include <iostream>
#include <iomanip>
#include <cassert>
#include <map>
template<typename K, typename V>
class interval_map {    
V m_valBegin;
std::map<K, V> m_map;
public:
// constructor associates whole range of K with val
interval_map(V const& val)
: m_valBegin(val)
{}
void assign(std::map<K, V> const& testMap) {
m_map = testMap;
}
// Assign value val to interval [keyBegin, keyEnd).
// Overwrite previous values in this interval.
// Conforming to the C++ Standard Library conventions, the interval
// includes keyBegin, but excludes keyEnd.
// If !( keyBegin < keyEnd ), this designates an empty interval,
// and assign must do nothing.

// look-up of the value associated with key
V const& operator[](K const& key) const {
auto it = m_map.upper_bound(key);
if (it == m_map.begin()) {
return m_valBegin;
}
else {
return (--it)->second;
}
}
void print() {
std::cout << 'n' << m_valBegin << 'n';
for (auto it = m_map.begin(); it != m_map.end(); ++it) {
std::cout << it->first << ", " << it->second << 'n';
}
}
void assign(K const& keyBegin, K const& keyEnd, V const& val) {

if (!(keyBegin < keyEnd)) return;

if (m_map.empty()) {
if (m_valBegin == val)
return;
m_map.insert({ keyBegin, val });
m_map.insert({ keyEnd, m_valBegin});
return;
}
auto startIt = m_map.lower_bound(keyBegin);
bool doErase = true;
if (startIt == m_map.end())
doErase = false;
auto endIt = m_map.upper_bound(keyEnd);
auto upperVal{ m_valBegin };
if (endIt == m_map.begin())
doErase = false;                    
else
upperVal = (--endIt)->second;

if (endIt != m_map.end())
endIt++;
if(doErase)
m_map.erase(startIt, endIt);

m_map.insert({ keyBegin, val });
m_map.insert({ keyEnd, upperVal });
// ensure canonical - further down
startIt = m_map.lower_bound(keyBegin);

assert(startIt->second == val);         

// go back to prev interval (it can have value = val)
if (startIt != m_map.begin()) 
{
startIt--;
// then our inserted value is the first equal to val
if (startIt->second != val) 
startIt++;
}

// skip first repeating value (first one should be left - others deleted)
if(!(startIt == m_map.begin() && val == m_valBegin))
startIt++;           

while(startIt != m_map.end())
{           
auto tmpKey = startIt->first;
if ((startIt++)->second == val)
m_map.erase(tmpKey);
else 
break;
}

}
};
int main() {
interval_map<int, char> mymap{ 'a' };
//mymap.assign({ {10,'c'},{20,'a'},{30,'c'},{40,'i'},{50,'c'},{60,'i'} });  
//mymap.assign(65, 68, 'z');    mymap.print();
while (1) 
{       
mymap.print();

int start, end;
char ch;
std::cout << "nn Enter start, end, char: ";
std::cin >> start >> end >> ch;

mymap.assign(start, end, ch);
}

}