text:
// Copyright (c) 2018- The Taucoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include "clubleaderdb.h"

#include "leveldb/iterator.h"

#include <boost/lexical_cast.hpp>
#include <boost/signals2/signal.hpp>
#include <boost/thread/exceptions.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/function.hpp>

using namespace std;
using boost::lexical_cast;

const std::string CClubLeaderDB::DB_PATH   = "/clubinfo/leader";
const std::string CClubLeaderDB::ADD_OP    = "A";
const std::string CClubLeaderDB::REMOVE_OP = "R";
const std::string CClubLeaderDB::DB_LEADER = "L";
const std::string CClubLeaderDB::KEY_SPERATOR = "_";

CClubLeaderDB::CClubLeaderDB()
{
    options.create_if_missing = true;

    std::string db_path = GetDataDir(true).string() + std::string(CClubLeaderDB::DB_PATH);
    LogPrintf("Opening LevelDB in %s\n", db_path);

    leveldb::Status status = leveldb::DB::Open(options, db_path, &pdb);
    dbwrapper_private::HandleError(status);
    assert(status.ok());
    LogPrintf("Opened LevelDB successfully\n");

    pdbcache = new CClubLeaderCache();
}

CClubLeaderDB::~CClubLeaderDB()
{
    delete pdb;
    pdb = NULL;

    delete pdbcache;
    pdbcache = NULL;
}

bool CClubLeaderDB::Write(std::string address, std::string height)
{
    leveldb::Status status = pdb->Put(leveldb::WriteOptions(),
            DB_LEADER + KEY_SPERATOR + height + KEY_SPERATOR + address, ADD_OP);
    if(!status.ok())
    {
        LogPrintf("LevelDB write failure in clubleader module: %s\n", status.ToString());
        dbwrapper_private::HandleError(status);
        return false;
    }

    return true;
}

bool CClubLeaderDB::Delete(std::string address, std::string height)
{
    leveldb::Status status = pdb->Put(leveldb::WriteOptions(),
            DB_LEADER + KEY_SPERATOR + height + KEY_SPERATOR + address, REMOVE_OP);
    if(!status.ok() && !status.IsNotFound())
    {
        LogPrintf("LevelDB write failure in clubleader module: %s\n", status.ToString());
        dbwrapper_private::HandleError(status);
        return false;
    }

    return true;

}

bool CClubLeaderDB::Commit()
{
    if (cache.size() == 0)
        return true;

    for(std::map<std::string, std::string>::const_iterator it = cache.begin();
        it != cache.end(); it++)
    {
        string key = it->first;
        vector<string> splitedStr;
        boost::split(splitedStr, key, boost::is_any_of(KEY_SPERATOR));
        if (splitedStr.size() != 2)
        {
            LogPrintf("%s, key string error:%s\n", __func__, key);
            continue;
        }
        string address = splitedStr[0];
        string height  = splitedStr[1];
        string op = it->second;
        if (op.compare(ADD_OP) == 0)
        {
            Write(address, height);
        }
        else if (op.compare(REMOVE_OP) == 0)
        {
            Delete(address, height);
        }
    }

    cache.clear();
    return true;

}

bool CClubLeaderDB::AddClubLeader(std::string address, int height)
{
    if (height < 0)
    {
        return false;
    }

    std::string key;
    std::string strHeight;
    try
    {
        strHeight = lexical_cast<std::string>(height);
    }
    catch(boost::bad_lexical_cast& e)
    {
        LogPrintf("type cast err %s %s\n", __func__, e.what());
        return false;
    }

    key = address + "_" + strHeight;

    std::map<std::string, std::string>::iterator it = cache.find(key);
    if (it != cache.end())
    {
        it->second = ADD_OP;
    }
    else
    {
        cache.insert(std::map<std::string, std::string>::value_type(key, ADD_OP));
    }

    return true;
}

bool CClubLeaderDB::RemoveClubLeader(std::string address, int height)
{
    if (height < 0)
    {
        return false;
    }

    std::string key;
    std::string strHeight;
    try
    {
        strHeight = lexical_cast<std::string>(height);
    }
    catch(boost::bad_lexical_cast& e)
    {
        LogPrintf("type cast err %s %s\n", __func__, e.what());
        return false;
    }

    key = address + "_" + strHeight;

    std::map<std::string, std::string>::iterator it = cache.find(key);
    if (it != cache.end())
    {
        it->second = REMOVE_OP;
    }
    else
    {
        cache.insert(std::map<std::string, std::string>::value_type(key, REMOVE_OP));
    }

    return true;
}

bool CClubLeaderDB::DeleteClubLeader(std::string address, int height)
{
    if (height < 0)
    {
        return false;
    }

    std::string key;
    std::string strHeight;
    try
    {
        strHeight = lexical_cast<std::string>(height);
    }
    catch(boost::bad_lexical_cast& e)
    {
        LogPrintf("type cast err %s %s\n", __func__, e.what());
        return false;
    }

    key = address + "_" + strHeight;
    std::map<std::string, std::string>::iterator it = cache.find(key);
    if (it != cache.end())
    {
        cache.erase(it);
    }

    leveldb::Status status = pdb->Delete(leveldb::WriteOptions(),
            DB_LEADER + KEY_SPERATOR + strHeight + KEY_SPERATOR + address);
    if(!status.ok() && !status.IsNotFound())
    {
        LogPrintf("LevelDB delete failure in clubleader module: %s\n", status.ToString());
        dbwrapper_private::HandleError(status);
        return false;
    }

    return true;

}

void CClubLeaderDB::DumpDBCache()
{
    LogPrintf("dump db cache begin\n");

    LogPrintf("Total size : %d\n", pdbcache->cache.size());

    for (std::map<std::string, OpMap>::iterator it = pdbcache->cache.begin();
        it !=pdbcache->cache.end(); it++)
    {
        LogPrintf("\n%s:\t", it->first);
        OpMap& operations = it->second;

        for (OpMap::iterator itr = operations.begin(); itr != operations.end(); itr++)
        {
            LogPrintf("%d %s ", itr->first, itr->second);
        }
    }

    LogPrintf("\ndump db cache end\n");
}

bool CClubLeaderDB::SyncWithDB()
{
     boost::scoped_ptr<leveldb::Iterator> pcursor(pdb->NewIterator(leveldb::ReadOptions()));
     pcursor->SeekToFirst();

     while (pcursor->Valid())
     {
         boost::this_thread::interruption_point();

         try
         {
             leveldb::Slice slKey = pcursor->key();
             CDataStream ssKey(slKey.data(), slKey.data()+slKey.size(), SER_DISK, CLIENT_VERSION);
             std::string keyStr = ssKey.str();

             if (keyStr.length() > 20 && keyStr[0] == 'L')
             {
                 std::string height_address = keyStr.substr(2, keyStr.length() - 1);
                 vector<string> splitedStr;
                 boost::split(splitedStr, height_address, boost::is_any_of(KEY_SPERATOR));
                 if (splitedStr.size() != 2)
                 {
                     LogPrintf("%s, key string error:%s\n", __func__, height_address);
                     pcursor->Next();
                     continue;
                 }
                 string height  = splitedStr[0];
                 int h = lexical_cast<int>(height);
                 string address = splitedStr[1];

                 leveldb::Slice slValue = pcursor->value();
                 CDataStream ssValue(slValue.data(), slValue.data()+slValue.size(), SER_DISK, CLIENT_VERSION);
                 std::string op = ssValue.str();

                 if (op.compare(ADD_OP) == 0 || op.compare(REMOVE_OP) == 0)
                 {
                     std::map<std::string, OpMap>::iterator it = pdbcache->cache.find(address);

                     if (it != pdbcache->cache.end())
                     {
                         OpMap& operations = it->second;
                         // for one address and a height, operation is "A" or "R"
                         if (operations.find(h) == operations.end())
                         {
                             operations.insert(OpMap::value_type(h, op));
                         }
                     }
                     else
                     {
                         OpMap operations;
                         operations.insert(OpMap::value_type(h, op));
                         pdbcache->cache.insert(std::map<std::string, OpMap>::value_type(
                             address, operations));
                     }
                 }
             }
         }
         catch (const std::exception& e)
         {
             LogPrintf("%s %s\n", __func__, e.what());
             return false;
         }

         pcursor->Next();
     }

     return true;
}

bool CClubLeaderDB::GetAllClubLeaders(std::vector<std::string>& leaders, int height)
{
    if (height < 0)
        return false;

    leaders.clear();

    if (height > pdbcache->height)
    {
        if (SyncWithDB())
        {
            pdbcache->height = height;
            DumpDBCache();
        }
        else
        {
            return false;
        }
    }

    for (std::map<std::string, OpMap>::iterator it = pdbcache->cache.begin();
        it != pdbcache->cache.end(); it++)
    {
        if (SeletedOrNot(it->second, height))
        {
            leaders.push_back(it->first);
        }
    }

    return true;
}

bool CClubLeaderDB::SeletedOrNot(OpMap& operations, int height)
{
    for (OpMap::reverse_iterator it = operations.rbegin(); it != operations.rend(); it++)
    {
        if (height >= it->first)
        {
            LogPrintf("hit height:%d\n", it->first);
            return (it->second).compare(ADD_OP) == 0 ? true : false;
        }
    }

    return false;
}

max_stars_repo_name: dardekasti/taucoin


text:
#define BOOST_TEST_MODULE homework1

#include <boost/test/unit_test.hpp>
#include <boost/regex.hpp>
#include "version.hpp"

BOOST_AUTO_TEST_SUITE(test_version)

BOOST_AUTO_TEST_CASE(test_valid_number_bild)
{
    // Arrange

    // Act

    // Assert
    BOOST_CHECK(GetNumberBuild() > 0);
}

// Вот мне непонятно, как можно будет данный тест лаконично написать без использования регулярных выражений
BOOST_AUTO_TEST_CASE(test_valid_version)
{
    // Arrange
    boost::regex regEx("^\\d+\\.\\d+\\.(\\d+)$");
    boost::smatch result;

    // Act
    std::string version = GetVersion();
    bool IsFormat = boost::regex_match(version, result, regEx);

    // Assert
    BOOST_CHECK(IsFormat);
    BOOST_CHECK(atoi(static_cast<std::string>(result[1]).c_str()) == GetNumberBuild());
}

}

ext: cpp

max_stars_repo_name: AlexanderSorvin/otus

max_issues_repo_name: AlexanderSorvin/otus

max_forks_repo_name: AlexanderSorvin/otus


text:
// Software License for MTL
//
// Copyright (c) 2007 The Trustees of Indiana University.
//               2008 Dresden University of Technology and the Trustees of Indiana University.
//               2010 SimuNova UG (haftungsbeschränkt), www.simunova.com.
// All rights reserved.
// Authors: Peter Gottschling and Andrew Lumsdaine
//
// This file is part of the Matrix Template Library
//
// See also license.mtl.txt in the distribution.

#ifndef ITL_QUASI_NEWTON_INCLUDE
#define ITL_QUASI_NEWTON_INCLUDE

#include <boost/numeric/mtl/concept/collection.hpp>
#include <boost/numeric/mtl/matrix/dense2D.hpp>
#include <boost/numeric/mtl/matrix/operators.hpp>
#include <boost/numeric/mtl/operation/operators.hpp>
#include <boost/numeric/mtl/operation/trans.hpp>
#include <boost/numeric/mtl/utility/gradient.hpp>

// #include <iostream>

namespace itl {

/// Quasi-Newton method
template <typename Matrix, typename Vector, typename F, typename Grad,
	  typename Step, typename Update, typename Iter>
Vector quasi_newton(Vector& x, F f, Grad grad_f, Step step, Update update, Iter& iter)
{
    typedef typename mtl::Collection<Vector>::value_type value_type;
    Vector         d, y, x_k, s;
    Matrix         H(size(x), size(x));

    H= 1;
    for (; !iter.finished(two_norm(grad_f(x))); ++iter) {
	d= H * -grad_f(x);                                                  // std::cout << "d is " << d << '\n';
	value_type alpha= step(x, d, f, grad_f); assert(alpha == alpha);
	x_k= x + alpha * d;                                                 // std::cout << "x_k is " << x_k << '\n';
	s= alpha * d;                                                       // std::cout << "alpha is " << alpha << '\n';
	y= grad_f(x_k) - grad_f(x);
	update(H, y, s);
	x= x_k;
    }
    return x;
}

/// Quasi-Newton method
template <typename Vector, typename F, typename Grad, typename Step, typename Update, typename Iter>
Vector inline quasi_newton(Vector& x, F f, Grad grad_f, Step step, Update update, Iter& iter)
{
    typedef typename mtl::traits::gradient<Vector>::type hessian_type;
    // typedef typename mtl::Collection<Vector>::value_type value_type;
    return quasi_newton<hessian_type>(x, f, grad_f, step, update, iter);
}

} // namespace itl

#endif // ITL_QUASI_NEWTON_INCLUDE

ext: hpp


text:
// Copyright (C) 2020 T. Zachary Laine
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

// Warning! This file is autogenerated.
#include <boost/text/grapheme_break.hpp>

#include <gtest/gtest.h>

#include <algorithm>

TEST(grapheme, breaks_7)
{
    // ÷ 1160 ÷ 000D ÷
    // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
    {
        std::array<uint32_t, 2> cps = {{ 0x1160, 0xd }};
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 0, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 1);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 1, cps.end()) - cps.begin(), 1);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 1, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 2, cps.end()) - cps.begin(), 1);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 1, cps.end()) - cps.begin(), 2);
    }

    // ÷ 1160 × 0308 ÷ 000D ÷
    // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
    {
        std::array<uint32_t, 3> cps = {{ 0x1160, 0x308, 0xd }};
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 0, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 1, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 2, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 2, cps.end()) - cps.begin(), 3);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 3, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 2, cps.end()) - cps.begin(), 3);
    }

    // ÷ 1160 ÷ 000A ÷
    // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
    {
        std::array<uint32_t, 2> cps = {{ 0x1160, 0xa }};
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 0, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 1);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 1, cps.end()) - cps.begin(), 1);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 1, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 2, cps.end()) - cps.begin(), 1);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 1, cps.end()) - cps.begin(), 2);
    }

    // ÷ 1160 × 0308 ÷ 000A ÷
    // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
    {
        std::array<uint32_t, 3> cps = {{ 0x1160, 0x308, 0xa }};
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 0, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 1, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 2, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 2, cps.end()) - cps.begin(), 3);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 3, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 2, cps.end()) - cps.begin(), 3);
    }

    // ÷ 1160 ÷ 0001 ÷
    // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
    {
        std::array<uint32_t, 2> cps = {{ 0x1160, 0x1 }};
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 0, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 1);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 1, cps.end()) - cps.begin(), 1);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 1, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 2, cps.end()) - cps.begin(), 1);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 1, cps.end()) - cps.begin(), 2);
    }

    // ÷ 1160 × 0308 ÷ 0001 ÷
    // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
    {
        std::array<uint32_t, 3> cps = {{ 0x1160, 0x308, 0x1 }};
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 0, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 1, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 2, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 2, cps.end()) - cps.begin(), 3);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 3, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 2, cps.end()) - cps.begin(), 3);
    }

    // ÷ 1160 × 034F ÷
    // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
    {
        std::array<uint32_t, 2> cps = {{ 0x1160, 0x34f }};
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 0, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 1, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 2, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
    }

    // ÷ 1160 × 0308 × 034F ÷
    // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
    {
        std::array<uint32_t, 3> cps = {{ 0x1160, 0x308, 0x34f }};
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 0, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 3);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 1, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 3);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 2, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 3);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 3, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 3);
    }

    // ÷ 1160 ÷ 1F1E6 ÷
    // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
    {
        std::array<uint32_t, 2> cps = {{ 0x1160, 0x1f1e6 }};
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 0, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 1);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 1, cps.end()) - cps.begin(), 1);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 1, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 2, cps.end()) - cps.begin(), 1);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 1, cps.end()) - cps.begin(), 2);
    }

    // ÷ 1160 × 0308 ÷ 1F1E6 ÷
    // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
    {
        std::array<uint32_t, 3> cps = {{ 0x1160, 0x308, 0x1f1e6 }};
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 0, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 1, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 2, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 2, cps.end()) - cps.begin(), 3);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 3, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 2, cps.end()) - cps.begin(), 3);
    }

    // ÷ 1160 ÷ 0600 ÷
    // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
    {
        std::array<uint32_t, 2> cps = {{ 0x1160, 0x600 }};
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 0, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 1);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 1, cps.end()) - cps.begin(), 1);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 1, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 2, cps.end()) - cps.begin(), 1);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 1, cps.end()) - cps.begin(), 2);
    }

    // ÷ 1160 × 0308 ÷ 0600 ÷
    // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
    {
        std::array<uint32_t, 3> cps = {{ 0x1160, 0x308, 0x600 }};
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 0, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 1, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 2, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 2, cps.end()) - cps.begin(), 3);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 3, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 2, cps.end()) - cps.begin(), 3);
    }

    // ÷ 1160 × 0903 ÷
    // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
    {
        std::array<uint32_t, 2> cps = {{ 0x1160, 0x903 }};
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 0, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 1, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 2, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
    }

    // ÷ 1160 × 0308 × 0903 ÷
    // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
    {
        std::array<uint32_t, 3> cps = {{ 0x1160, 0x308, 0x903 }};
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 0, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 3);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 1, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 3);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 2, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 3);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 3, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 3);
    }

    // ÷ 1160 ÷ 1100 ÷
    // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
    {
        std::array<uint32_t, 2> cps = {{ 0x1160, 0x1100 }};
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 0, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 1);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 1, cps.end()) - cps.begin(), 1);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 1, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 2, cps.end()) - cps.begin(), 1);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 1, cps.end()) - cps.begin(), 2);
    }

    // ÷ 1160 × 0308 ÷ 1100 ÷
    // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
    {
        std::array<uint32_t, 3> cps = {{ 0x1160, 0x308, 0x1100 }};
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 0, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 1, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 2, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 2, cps.end()) - cps.begin(), 3);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 3, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 2, cps.end()) - cps.begin(), 3);
    }

    // ÷ 1160 × 1160 ÷
    // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [7.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
    {
        std::array<uint32_t, 2> cps = {{ 0x1160, 0x1160 }};
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 0, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 1, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 2, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
    }

    // ÷ 1160 × 0308 ÷ 1160 ÷
    // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
    {
        std::array<uint32_t, 3> cps = {{ 0x1160, 0x308, 0x1160 }};
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 0, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 1, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 2, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 2, cps.end()) - cps.begin(), 3);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 3, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 2, cps.end()) - cps.begin(), 3);
    }

    // ÷ 1160 × 11A8 ÷
    // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [7.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
    {
        std::array<uint32_t, 2> cps = {{ 0x1160, 0x11a8 }};
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 0, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 1, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 2, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
    }

    // ÷ 1160 × 0308 ÷ 11A8 ÷
    // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
    {
        std::array<uint32_t, 3> cps = {{ 0x1160, 0x308, 0x11a8 }};
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 0, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 1, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 2, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 2, cps.end()) - cps.begin(), 3);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 3, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 2, cps.end()) - cps.begin(), 3);
    }

    // ÷ 1160 ÷ AC00 ÷
    // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
    {
        std::array<uint32_t, 2> cps = {{ 0x1160, 0xac00 }};
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 0, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 1);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 1, cps.end()) - cps.begin(), 1);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 1, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 2, cps.end()) - cps.begin(), 1);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 1, cps.end()) - cps.begin(), 2);
    }

    // ÷ 1160 × 0308 ÷ AC00 ÷
    // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
    {
        std::array<uint32_t, 3> cps = {{ 0x1160, 0x308, 0xac00 }};
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 0, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 1, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 2, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 2, cps.end()) - cps.begin(), 3);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 3, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 2, cps.end()) - cps.begin(), 3);
    }

    // ÷ 1160 ÷ AC01 ÷
    // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
    {
        std::array<uint32_t, 2> cps = {{ 0x1160, 0xac01 }};
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 0, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 1);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 1, cps.end()) - cps.begin(), 1);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 1, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 2, cps.end()) - cps.begin(), 1);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 1, cps.end()) - cps.begin(), 2);
    }

    // ÷ 1160 × 0308 ÷ AC01 ÷
    // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
    {
        std::array<uint32_t, 3> cps = {{ 0x1160, 0x308, 0xac01 }};
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 0, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 1, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 2, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 2, cps.end()) - cps.begin(), 3);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 3, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 2, cps.end()) - cps.begin(), 3);
    }

    // ÷ 1160 ÷ 231A ÷
    // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
    {
        std::array<uint32_t, 2> cps = {{ 0x1160, 0x231a }};
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 0, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 1);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 1, cps.end()) - cps.begin(), 1);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 1, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 2, cps.end()) - cps.begin(), 1);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 1, cps.end()) - cps.begin(), 2);
    }

    // ÷ 1160 × 0308 ÷ 231A ÷
    // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
    {
        std::array<uint32_t, 3> cps = {{ 0x1160, 0x308, 0x231a }};
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 0, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 1, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 2, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 2, cps.end()) - cps.begin(), 3);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 3, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 2, cps.end()) - cps.begin(), 3);
    }

    // ÷ 1160 × 0300 ÷
    // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
    {
        std::array<uint32_t, 2> cps = {{ 0x1160, 0x300 }};
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 0, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 1, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 2, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
    }

    // ÷ 1160 × 0308 × 0300 ÷
    // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
    {
        std::array<uint32_t, 3> cps = {{ 0x1160, 0x308, 0x300 }};
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 0, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 3);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 1, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 3);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 2, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 3);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 3, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 3);
    }

    // ÷ 1160 × 200D ÷
    // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
    {
        std::array<uint32_t, 2> cps = {{ 0x1160, 0x200d }};
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 0, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 1, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 2, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
    }

    // ÷ 1160 × 0308 × 200D ÷
    // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
    {
        std::array<uint32_t, 3> cps = {{ 0x1160, 0x308, 0x200d }};
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 0, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 3);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 1, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 3);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 2, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 3);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 3, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 3);
    }

    // ÷ 1160 ÷ 0378 ÷
    // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
    {
        std::array<uint32_t, 2> cps = {{ 0x1160, 0x378 }};
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 0, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 1);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 1, cps.end()) - cps.begin(), 1);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 1, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 2, cps.end()) - cps.begin(), 1);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 1, cps.end()) - cps.begin(), 2);
    }

    // ÷ 1160 × 0308 ÷ 0378 ÷
    // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
    {
        std::array<uint32_t, 3> cps = {{ 0x1160, 0x308, 0x378 }};
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 0, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 1, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 2, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 2, cps.end()) - cps.begin(), 3);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 3, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 2, cps.end()) - cps.begin(), 3);
    }

    // ÷ 1160 ÷ D800 ÷
    // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
    {
        std::array<uint32_t, 2> cps = {{ 0x1160, 0xd800 }};
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 0, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 1);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 1, cps.end()) - cps.begin(), 1);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 1, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 2, cps.end()) - cps.begin(), 1);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 1, cps.end()) - cps.begin(), 2);
    }

    // ÷ 1160 × 0308 ÷ D800 ÷
    // ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
    {
        std::array<uint32_t, 3> cps = {{ 0x1160, 0x308, 0xd800 }};
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 0, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 1, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 2, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 2, cps.end()) - cps.begin(), 3);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 3, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 2, cps.end()) - cps.begin(), 3);
    }

    // ÷ 11A8 ÷ 0020 ÷
    // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] SPACE (Other) ÷ [0.3]
    {
        std::array<uint32_t, 2> cps = {{ 0x11a8, 0x20 }};
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 0, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 1);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 1, cps.end()) - cps.begin(), 1);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 1, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 2, cps.end()) - cps.begin(), 1);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 1, cps.end()) - cps.begin(), 2);
    }

    // ÷ 11A8 × 0308 ÷ 0020 ÷
    // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
    {
        std::array<uint32_t, 3> cps = {{ 0x11a8, 0x308, 0x20 }};
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 0, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 1, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 2, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 2, cps.end()) - cps.begin(), 3);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 3, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 2, cps.end()) - cps.begin(), 3);
    }

    // ÷ 11A8 ÷ 000D ÷
    // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
    {
        std::array<uint32_t, 2> cps = {{ 0x11a8, 0xd }};
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 0, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 1);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 1, cps.end()) - cps.begin(), 1);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 1, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 2, cps.end()) - cps.begin(), 1);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 1, cps.end()) - cps.begin(), 2);
    }

    // ÷ 11A8 × 0308 ÷ 000D ÷
    // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
    {
        std::array<uint32_t, 3> cps = {{ 0x11a8, 0x308, 0xd }};
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 0, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 1, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 2, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 2, cps.end()) - cps.begin(), 3);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 3, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 2, cps.end()) - cps.begin(), 3);
    }

    // ÷ 11A8 ÷ 000A ÷
    // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
    {
        std::array<uint32_t, 2> cps = {{ 0x11a8, 0xa }};
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 0, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 1);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 1, cps.end()) - cps.begin(), 1);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 1, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 2, cps.end()) - cps.begin(), 1);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 1, cps.end()) - cps.begin(), 2);
    }

    // ÷ 11A8 × 0308 ÷ 000A ÷
    // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
    {
        std::array<uint32_t, 3> cps = {{ 0x11a8, 0x308, 0xa }};
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 0, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 1, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 2, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 2, cps.end()) - cps.begin(), 3);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 3, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 2, cps.end()) - cps.begin(), 3);
    }

    // ÷ 11A8 ÷ 0001 ÷
    // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
    {
        std::array<uint32_t, 2> cps = {{ 0x11a8, 0x1 }};
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 0, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 1);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 1, cps.end()) - cps.begin(), 1);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 1, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 2, cps.end()) - cps.begin(), 1);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 1, cps.end()) - cps.begin(), 2);
    }

    // ÷ 11A8 × 0308 ÷ 0001 ÷
    // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
    {
        std::array<uint32_t, 3> cps = {{ 0x11a8, 0x308, 0x1 }};
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 0, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 1, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 2, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 2, cps.end()) - cps.begin(), 3);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 3, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 2, cps.end()) - cps.begin(), 3);
    }

    // ÷ 11A8 × 034F ÷
    // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
    {
        std::array<uint32_t, 2> cps = {{ 0x11a8, 0x34f }};
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 0, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 1, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 2, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
    }

    // ÷ 11A8 × 0308 × 034F ÷
    // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
    {
        std::array<uint32_t, 3> cps = {{ 0x11a8, 0x308, 0x34f }};
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 0, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 3);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 1, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 3);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 2, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 3);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 3, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 3);
    }

    // ÷ 11A8 ÷ 1F1E6 ÷
    // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
    {
        std::array<uint32_t, 2> cps = {{ 0x11a8, 0x1f1e6 }};
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 0, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 1);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 1, cps.end()) - cps.begin(), 1);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 1, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 2, cps.end()) - cps.begin(), 1);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 1, cps.end()) - cps.begin(), 2);
    }

    // ÷ 11A8 × 0308 ÷ 1F1E6 ÷
    // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
    {
        std::array<uint32_t, 3> cps = {{ 0x11a8, 0x308, 0x1f1e6 }};
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 0, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 1, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 2, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 2, cps.end()) - cps.begin(), 3);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 3, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 2, cps.end()) - cps.begin(), 3);
    }

    // ÷ 11A8 ÷ 0600 ÷
    // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
    {
        std::array<uint32_t, 2> cps = {{ 0x11a8, 0x600 }};
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 0, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 1);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 1, cps.end()) - cps.begin(), 1);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 1, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 2, cps.end()) - cps.begin(), 1);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 1, cps.end()) - cps.begin(), 2);
    }

    // ÷ 11A8 × 0308 ÷ 0600 ÷
    // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
    {
        std::array<uint32_t, 3> cps = {{ 0x11a8, 0x308, 0x600 }};
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 0, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 1, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 2, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 2, cps.end()) - cps.begin(), 3);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 3, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 2, cps.end()) - cps.begin(), 3);
    }

    // ÷ 11A8 × 0903 ÷
    // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
    {
        std::array<uint32_t, 2> cps = {{ 0x11a8, 0x903 }};
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 0, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 1, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 2, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 2);
    }

    // ÷ 11A8 × 0308 × 0903 ÷
    // ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
    {
        std::array<uint32_t, 3> cps = {{ 0x11a8, 0x308, 0x903 }};
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 0, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 3);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 1, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 3);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 2, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 3);
        EXPECT_EQ(boost::text::prev_grapheme_break(cps.begin(), cps.begin() + 3, cps.end()) - cps.begin(), 0);
        EXPECT_EQ(boost::text::next_grapheme_break(cps.begin() + 0, cps.end()) - cps.begin(), 3);
    }

}

max_stars_repo_name: Ryan-rsm-McKenzie/text

max_issues_repo_name: Ryan-rsm-McKenzie/text

max_forks_repo_name: Ryan-rsm-McKenzie/text


text:
#pragma once
#ifndef CANNON_RAY_TEXTURE_H
#define CANNON_RAY_TEXTURE_H

/*!
 * \file cannon/ray/texture.hpp
 * \brief File containing Texture and subclasses class definitions.
 */

#include <memory>

#include <Eigen/Dense>
#include <stb_image/stb_image.h>

#include <cannon/math/perlin.hpp>

using namespace Eigen;

using namespace cannon::math;

namespace cannon {
  namespace ray {

    /*!
     * \brief Abstract class representing a texture on some geometry.
     */
    class Texture {
      public:
        /*!
         * Method to get the value of this texture at U,V surface coordinates.
         *
         * \param u Horizontal surface coordinate
         * \param v Vertical surface coordinate
         * \param p Point being textured
         *
         * \returns Color of texture at point with surface coords
         */
        virtual Vector3d value(double u, double v, const Vector3d& p) const = 0;
    };

    /*!
     * \brief Class representing a texture of a single, fixed color.
     */
    class SolidColor : public Texture {
      public:
        /*!
         * Default constructor.
         */
        SolidColor() {}

        /*!
         * Constructor taking the color to make this texture.
         */
        SolidColor(const Vector3d& c) : color_(c) {}

        /*!
         * Constructor taking RGB components for the color of this texture.
         */
        SolidColor(double red, double green, double blue) : color_(red, green, blue) {}

        /*!
         * Destructor.
         */
        virtual ~SolidColor() {}

        /*!
         * Inherited from Texture.
         */
        virtual Vector3d value(double /*u*/, double /*v*/, const Vector3d& /*p*/) const override {
          return color_;
        }

      private:
        Vector3d color_; //!< Color for this texture
    };

    /*!
     * \brief Class representing a procedural checker texture.
     */
    class CheckerTexture : public Texture {
      public:

        /*!
         * Default constructor.
         */
        CheckerTexture() {}

        /*!
         * Constructor taking textures to alternate in checkerboard pattern.
         */
        CheckerTexture(std::shared_ptr<Texture> even, std::shared_ptr<Texture>
            odd) : even_(even), odd_(odd){}

        /*!
         * Constructor taking colors to alternate in checkerboard pattern.
         */
        CheckerTexture(const Vector3d& c1, const Vector3d& c2) :
          even_(std::make_shared<SolidColor>(c1)),
          odd_(std::make_shared<SolidColor>(c2)) {}

        /*!
         * Destructor.
         */
        virtual ~CheckerTexture() {}

        /*!
         * Inherited from Texture.
         */
        virtual Vector3d value(double u, double v, const Vector3d& p) const override;

      public:
        std::shared_ptr<Texture> even_;
        std::shared_ptr<Texture> odd_;
    };

    /*!
     * \brief Class representing a Perlin noise texture.
     */
    class NoiseTexture : public Texture {
      public:

        /*!
         * Default constructor.
         */
        NoiseTexture() {}

        /*!
         * Constructor taking a noise scale.
         */
        NoiseTexture(double scale) : scale_(scale) {}

        /*!
         * Destructor.
         */
        virtual ~NoiseTexture() {}

        /*!
         * Inherited from Texture.
         */
        virtual Vector3d value(double /*u*/, double /*v*/, const Vector3d& p) const override {
          return Vector3d(1, 1, 1) * 0.5 * (1.0 +
              std::sin(scale_ * p.z() + 10*noise_.turbulence(p)));
        }

      public:
        Perlin noise_; //!< Perlin noise generator
        double scale_; //!< Noise scale
    };

    /*!
     * \brief Class representing a texture derived from an image.
     */
    class ImageTexture : public Texture {
      public:

        /*!
         * Default constructor.
         */
        ImageTexture() : data_(nullptr), width_(0), height_(0), bytes_per_scanline_(0) {}

        /*!
         * Constructor taking filename of image to load.
         */
        ImageTexture(const std::string& filename) {
          auto components_per_pixel = bytes_per_pixel;

          data_ = stbi_load(filename.c_str(), &width_, &height_,
              &components_per_pixel, components_per_pixel);

          if (!data_) {
            throw std::runtime_error("Could not open image file for texture.");
          }

          bytes_per_scanline_ = bytes_per_pixel * width_;
        }

        /*!
         * Destructor.
         */
        virtual ~ImageTexture () {}

        /*!
         * Inherited from Texture.
         */
        virtual Vector3d value(double u, double v, const Vector3d& p) const override;

        const static int bytes_per_pixel = 3; //!< Bytes per pixel in this texture

      private:
        unsigned char *data_; //!< Image data
        int width_, height_; //!< Image width, height
        int bytes_per_scanline_; //!< Number of bytes per scanline in image

    };

  } // namespace ray
} // namespace cannon

#endif /* ifndef CANNON_RAY_TEXTURE_H */

max_stars_repo_name: cannontwo/cannon

max_issues_repo_name: cannontwo/cannon

max_forks_repo_name: cannontwo/cannon


text:
#include "cpprest/json_utils.h"

#include <boost/algorithm/string/find.hpp>
#include <boost/algorithm/string/predicate.hpp>

// json query helpers
namespace web
{
    namespace json
    {
        namespace details
        {
            inline utility::string_t::size_type count(utility::string_t::size_type first, utility::string_t::size_type last)
            {
                return utility::string_t::npos == last ? utility::string_t::npos : last - first;
            }
        }

        // insert a field into the specified object at the specified key path (splitting it on '.' and inserting sub-objects as necessary)
        // only if the value doesn't already contain a field matching that key path (except for the required sub-objects or null values)
        bool insert(web::json::object& object, const utility::string_t& key_path, const web::json::value& field_value)
        {
            web::json::object* pobject = &object;
            utility::string_t::size_type key_first = 0;
            do
            {
                const utility::string_t::size_type key_last = key_path.find_first_of(U("."), key_first);
                const utility::string_t key = key_path.substr(key_first, details::count(key_first, key_last));
                if (utility::string_t::npos != key_last)
                {
                    auto& field = (*pobject)[key];
                    // do not replace (non-null) values for duplicate keys; other policies (replace, promote to array, or throw) would be possible...
                    if (field.is_null()) field = web::json::value::object();
                    else if (!field.is_object()) return false;
                    pobject = &field.as_object();
                }
                else
                {
                    auto& field = (*pobject)[key];
                    // do not replace (non-null) values for duplicate keys; other policies (replace, promote to array, or throw) would be possible...
                    if (field.is_null()) field = field_value;
                    else return false;
                }
                key_first = utility::string_t::npos != key_last ? key_last + 1 : key_last;
            } while (utility::string_t::npos != key_first);
            return true;
        }

        // find the value of a field or fields from the specified object, splitting the key path on '.' and searching arrays as necessary
        // returns true if the value has at least one field matching the key path
        // if any arrays are encountered on the key path, results is an array, otherwise it's a non-array value
        bool extract(const web::json::object& object, web::json::value& results, const utility::string_t& key_path)
        {
            bool match = false;
            results = web::json::value::null();

            std::list<const web::json::object*> pobjects(1, &object);
            utility::string_t::size_type key_first = 0;
            do
            {
                const utility::string_t::size_type key_last = key_path.find_first_of(U("."), key_first);
                const utility::string_t key = key_path.substr(key_first, details::count(key_first, key_last));
                if (utility::string_t::npos != key_last)
                {
                    // not the leaf key, so map each object to the specified field, searching arrays and filtering out other types
                    for (auto it = pobjects.begin(); pobjects.end() != it; it = pobjects.erase(it))
                    {
                        auto& object = **it;
                        auto found = object.find(key);
                        if (object.end() != found)
                        {
                            auto& field = found->second;
                            if (field.is_array())
                            {
                                // encountered an array
                                if (!results.is_array())
                                {
                                    results = web::json::value::array();
                                }

                                for (auto& element : field.as_array())
                                {
                                    if (element.is_object())
                                    {
                                        pobjects.insert(it, &element.as_object());
                                    }
                                }
                            }
                            else if (field.is_object())
                            {
                                pobjects.insert(it, &field.as_object());
                            }
                        }
                    }
                }
                else
                {
                    // leaf key, so map each object to the specified field, merging arrays into results
                    for (auto& pobject : pobjects)
                    {
                        auto& object = *pobject;
                        auto found = object.find(key);
                        if (object.end() != found)
                        {
                            auto& field = found->second;
                            if (!results.is_array())
                            {
                                // field must be the only result(s); i.e. match currently false
                                // field may be any type of value though, including null or an array
                                results = field;
                            }
                            else
                            {
                                if (field.is_array())
                                {
                                    for (auto& element : field.as_array())
                                    {
                                        web::json::push_back(results, element);
                                    }
                                }
                                else
                                {
                                    web::json::push_back(results, field);
                                }
                            }

                            // found a match!
                            if (!match) match = true;
                        }
                    }
                }
                key_first = utility::string_t::npos != key_last ? key_last + 1 : key_last;
            } while (utility::string_t::npos != key_first);

            return match;
        }

        // construct an ordered parameters object from a URI-encoded query string of '&' or ';' separated terms expected to be field=value pairs
        // field names will be URI-decoded, but values will be left as-is!
        // cf. web::uri::split_query
        web::json::value value_from_query(const utility::string_t& query)
        {
            web::json::value result = web::json::value::object(true); // keep order
            utility::string_t::size_type field_first = 0;
            do
            {
                const utility::string_t::size_type field_last = query.find_first_of(U("=&;"), field_first);
                const utility::string_t::size_type value_first = utility::string_t::npos != field_last && U('=') == query[field_last] ? field_last + 1 : field_last;
                const utility::string_t::size_type value_last = query.find_first_of(U("&;"), value_first);
                const utility::string_t field = uri::decode(query.substr(field_first, details::count(field_first, field_last)));
                // could distinguish the two cases (no '=' vs. from empty value) but at the moment, map both to empty string
                const utility::string_t value = utility::string_t::npos == value_first ? utility::string_t() : query.substr(value_first, details::count(value_first, value_last));
                if (!field.empty() || !value.empty())
                {
                    result[field] = web::json::value::string(value);
                }
                field_first = utility::string_t::npos != value_last ? value_last + 1 : value_last;
            } while (utility::string_t::npos != field_first);
            return result;
        }

        // construct a query string of '&' separated terms from a parameters object
        // field names will be URI-encoded, but values will be left as-is!
        utility::string_t query_from_value(const web::json::value& params)
        {
            utility::ostringstream_t query;
            bool first = true;
            for (auto& param : params.as_object())
            {
                if (first) first = false; else query << U('&');
                query << uri::encode_uri(param.first, uri::components::query) << U('=') << param.second.as_string();
            }
            return query.str();
        }

        // construct a query/exemplar object from a parameters object, by constructing nested sub-objects from '.'-separated field names
        web::json::value unflatten(const web::json::value& params)
        {
            web::json::value result = web::json::value::object();
            for (auto& param : params.as_object())
            {
                // later duplicate query terms are ignored, as a result of current insert policy above
                insert(result.as_object(), param.first, param.second);
            }
            return result;
        }

        // compare a value against a query/exemplar
        bool match_query(const web::json::value& value, const web::json::value& query, match_flag_type match_flags)
        {
            if (value.is_array())
            {
                // one of the value's elements must match the query
                for (auto& element : value.as_array())
                {
                    if (match_query(element, query, match_flags))
                    {
                        return true;
                    }
                }
                return false;
            }
            else if (value.is_object() && query.is_object())
            {
                // value must have fields matching all of the query fields, but other value fields are ignored
                for (auto& query_field : query.as_object())
                {
                    if (!value.has_field(query_field.first) || !match_query(value.at(query_field.first), query_field.second, match_flags))
                    {
                        return false;
                    }
                }
                return true;
            }
            else if (value.type() == query.type())
            {
                if (query.is_string())
                {
                    return 0 != (match_substr & match_flags)
                        // value must contain the query as a substring (optionally case-insensitive)
                        ? 0 != (match_icase & match_flags)
                            ? (bool)boost::algorithm::ifind_first(value.as_string(), query.as_string())
                            : (bool)boost::algorithm::find_first(value.as_string(), query.as_string())
                        // value must be an exact match (optionally case-insensitive)
                        : 0 != (match_icase & match_flags)
                            ? boost::algorithm::iequals(value.as_string(), query.as_string())
                            : boost::algorithm::equals(value.as_string(), query.as_string());
                }
                else
                {
                    // value must be an exact match
                    return value == query;
                }
            }
            else if (query.is_string()) // && !value.is_object() could be a good idea
            {
                // last resort; treat the query string as serialized json...
                // it'd feel less risky to compare value.serialize() to the query string but
                // that wouldn't handle different string representations, hmm.
                std::error_code ec;
                web::json::value parsed_query = web::json::value::parse(query.as_string(), ec);
                return !ec && match_query(value, parsed_query, match_flags);
            }
            else
            {
                return false;
            }
        }
    }
}

max_stars_repo_name: cylonbrain/nmos-cpp

max_issues_repo_name: cylonbrain/nmos-cpp

max_forks_repo_name: cylonbrain/nmos-cpp


text:
/*=========================================================================
 *
 *  Copyright David Doria 2012 daviddoria@gmail.com
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0.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.
 *
 *=========================================================================*/

#ifndef InpaintingAlgorithm_hpp
#define InpaintingAlgorithm_hpp

// Concepts
#include "Concepts/InpaintingVisitorConcept.hpp"

// Boost
#include <boost/graph/properties.hpp>

// STL
#include <stdexcept>
#include <memory>

// Custom
#include <BoostHelpers/BoostHelpers.h>

/** When this function is called, the priority-queue must already be filled with
  * all the boundary nodes (which should also have their boundaryStatusMap set appropriately).
  * The only thing this function does is run the inpainting loop. All of the
  * actual code is externalized in the functors and visitors (vis, find_inpainting_source,
  * inpaint_patch, etc.).
  */
template <typename TVertexListGraph, typename TInpaintingVisitor,
          typename TPriorityQueue, typename TBestPatchFinder,
          typename TPatchInpainter>
inline void
InpaintingAlgorithm(std::shared_ptr<TVertexListGraph> graph,
                    std::shared_ptr<TInpaintingVisitor> visitor,
                    std::shared_ptr<TPriorityQueue> boundaryNodeQueue,
                    std::shared_ptr<TBestPatchFinder> bestPatchFinder,
                    std::shared_ptr<TPatchInpainter> patchInpainter)
{
  BOOST_CONCEPT_ASSERT((InpaintingVisitorConcept<TInpaintingVisitor, TVertexListGraph>));

  typedef typename boost::graph_traits<TVertexListGraph>::vertex_descriptor VertexDescriptorType;

//   std::cout << "At the beginning of the algorithm there are " << (*boundaryNodeQueue).size() << " nodes in the queue." << std::endl;
//   std::cout << "At the beginning of the algorithm there are "
//             << BoostHelpers::CountValidQueueNodes(*boundaryNodeQueue, *boundaryStatusMap)
//             << " valid nodes in the queue." << std::endl;

  unsigned int iteration = 0;

  while(!boundaryNodeQueue->empty())
  {
    VertexDescriptorType targetNode = boundaryNodeQueue->top(); // This also pops the node

    // Notify the visitor that we have a hole target center.
    visitor->DiscoverVertex(targetNode);

    // Create a list of the source patches to search (all of them)
    typename boost::graph_traits<TVertexListGraph>::vertex_iterator graphBeginIterator;
    typename boost::graph_traits<TVertexListGraph>::vertex_iterator graphEndIterator;
    tie(graphBeginIterator, graphEndIterator) = vertices(*graph);

    // Find the source node that matches best to the target node
    VertexDescriptorType sourceNode = (*bestPatchFinder)(graphBeginIterator, graphEndIterator, targetNode);
    visitor->PotentialMatchMade(targetNode, sourceNode);

    // Inpaint the target patch from the source patch.
    itk::Index<2> targetIndex = ITKHelpers::CreateIndex(targetNode);
    itk::Index<2> sourceIndex = ITKHelpers::CreateIndex(sourceNode);

    patchInpainter->PaintPatch(targetIndex, sourceIndex);
    // Ideally we would like to do this, but PatchInpainter::PaintPatch
    // cannot be a template because it is virtual (c++ rules say so)
    // patchInpainter->PaintPatch(targetNode, sourceNode);

    visitor->FinishVertex(targetNode, sourceNode);

    iteration++;
  } // end main iteration loop

  std::cout << "Inpainting complete after " << iteration
            << " iterations." << std::endl;
  visitor->InpaintingComplete();
}

#endif

max_stars_repo_name: jingtangliao/ff

max_issues_repo_name: jingtangliao/ff

max_forks_repo_name: jingtangliao/ff


text:
//
// Expansion Hunter
// Copyright 2016-2019 Illumina, Inc.
// All rights reserved.
//
// Author: Egor Dolzhenko <edolzhenko@illumina.com>,
//         Chris Saunders <csaunders@illumina.com>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// 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.
//
//

#include "sample/HtsStreamingSampleAnalysis.hh"

#include <memory>

#include "absl/container/flat_hash_set.h"
#include "spdlog/spdlog.h"
#include <boost/optional.hpp>

#include "core/HtsHelpers.hh"
#include "core/ThreadPool.hh"
#include "locus/LocusAnalyzer.hh"
#include "locus/LocusAnalyzerUtil.hh"
#include "sample/GenomeQueryCollection.hh"
#include "sample/HtsFileStreamer.hh"
#include "sample/HtsStreamingReadPairQueue.hh"

using ehunter::locus::initializeLocusAnalyzers;
using ehunter::locus::LocusAnalyzer;
using graphtools::AlignmentWriter;
using std::string;
using std::vector;

namespace ehunter
{

namespace
{

/// \brief Mutable data shared by all LocusAnalyzer-processing threads
///
class LocusAnalyzerThreadSharedData
{
public:
    LocusAnalyzerThreadSharedData(const unsigned maxActiveLocusAnalyzerQueues, const unsigned locusAnalyzerCount)
        : isWorkerThreadException(false)
        , readPairQueue(maxActiveLocusAnalyzerQueues, locusAnalyzerCount)
    {
    }

    std::atomic<bool> isWorkerThreadException;
    HtsStreamingReadPairQueue readPairQueue;
    vector<std::unique_ptr<LocusAnalyzer>> locusAnalyzers;
};

/// \brief Data isolated to each LocusAnalyzer-processing thread
///
struct LocusAnalyzerThreadLocalData
{
    std::exception_ptr threadExceptionPtr = nullptr;
    std::shared_ptr<graphtools::AlignerSelector> alignerSelectorPtr;
};

/// Process queue for a single LocusAnalyzer on one thread
void processLocusAnalyzerQueue(
    const int threadIndex, LocusAnalyzerThreadSharedData& locusAnalyzerThreadSharedData,
    std::vector<LocusAnalyzerThreadLocalData>& locusAnalyzerThreadLocalDataPool, const unsigned locusIndex)
{
    if (locusAnalyzerThreadSharedData.isWorkerThreadException.load())
    {
        return;
    }

    LocusAnalyzerThreadLocalData& locusAnalyzerThreadData(locusAnalyzerThreadLocalDataPool[threadIndex]);
    auto& locusAnalyzer(*locusAnalyzerThreadSharedData.locusAnalyzers[locusIndex]);

    boost::optional<HtsStreamingReadPairQueue::ReadPair> readPair;

    try
    {
        while (true)
        {
            locusAnalyzerThreadSharedData.readPairQueue.getNextReadPair(locusIndex, readPair);
            if (not readPair)
            {
                break;
            }
            processAnalyzerBundleReadPair(
                locusAnalyzer, readPair->regionType, readPair->inputType, readPair->read, readPair->mate,
                *locusAnalyzerThreadData.alignerSelectorPtr);
        }
    }
    catch (const std::exception& e)
    {
        locusAnalyzerThreadSharedData.isWorkerThreadException.store(true);
        locusAnalyzerThreadData.threadExceptionPtr = std::current_exception();

        std::ostringstream oss;
        oss << "Exception caught in thread " << threadIndex << " while processing read pair queue for locus: `"
            << locusAnalyzer.locusId() << "`";
        if (readPair)
        {
            oss << " current readPair: `" << readPair->read.fragmentId() << "`";
        }
        oss << ": " << e.what();
        spdlog::error(oss.str());
        throw;
    }
    catch (...)
    {
        locusAnalyzerThreadSharedData.isWorkerThreadException.store(true);
        locusAnalyzerThreadData.threadExceptionPtr = std::current_exception();

        std::ostringstream oss;
        oss << "Exception caught in thread " << threadIndex << " while processing read pair queue for locus: `"
            << locusAnalyzer.locusId() << "`";
        if (readPair)
        {
            oss << " current readPair: `" << readPair->read.fragmentId() << "`";
        }
        spdlog::error(oss.str());
        throw;
    }
}

/// \brief Mutable data shared by all SampleFindings-processing threads
///
class SampleFindingsThreadSharedData
{
public:
    SampleFindingsThreadSharedData()
        : isWorkerThreadException(false)
        , locusIndex(0)
    {
    }

    std::atomic<bool> isWorkerThreadException;
    std::atomic<unsigned> locusIndex;
};

/// \brief Data isolated to each SampleFindings-processing thread
///
struct SampleFindingsThreadLocalData
{
    std::exception_ptr threadExceptionPtr = nullptr;
};

/// \brief Analyze a series of loci on one thread
///
void analyzeLocus(
    const int threadIndex, const Sex sampleSex, vector<std::unique_ptr<LocusAnalyzer>>& locusAnalyzers,
    SampleFindings& sampleFindings, SampleFindingsThreadSharedData& sampleFindingsThreadSharedData,
    std::vector<SampleFindingsThreadLocalData>& sampleFindingsThreadLocalData)
{
    SampleFindingsThreadLocalData& sampleFindingsThreadData(sampleFindingsThreadLocalData[threadIndex]);
    std::string locusId = "Unknown";

    try
    {

        const unsigned size(locusAnalyzers.size());
        while (true)
        {
            if (sampleFindingsThreadSharedData.isWorkerThreadException.load())
            {
                return;
            }
            const auto locusIndex(sampleFindingsThreadSharedData.locusIndex.fetch_add(1));
            if (locusIndex >= size)
            {
                return;
            }

            auto& locusAnalyzer(*locusAnalyzers[locusIndex]);
            locusId = locusAnalyzer.locusId();
            sampleFindings[locusIndex] = locusAnalyzer.analyze(sampleSex, boost::none);
        }
    }
    catch (const std::exception& e)
    {
        sampleFindingsThreadSharedData.isWorkerThreadException = true;
        sampleFindingsThreadData.threadExceptionPtr = std::current_exception();

        spdlog::error("Exception caught in thread {} while analyzing locus: {} : {}", threadIndex, locusId, e.what());
        throw;
    }
    catch (...)
    {
        sampleFindingsThreadSharedData.isWorkerThreadException = true;
        sampleFindingsThreadData.threadExceptionPtr = std::current_exception();

        spdlog::error("Unknown exception caught in thread {} while analyzing locus: {}", threadIndex, locusId);
        throw;
    }
}

}

SampleFindings htsStreamingSampleAnalysis(
    const ProgramParameters& programParams, const HeuristicParameters& heuristicParams,
    const RegionCatalog& regionCatalog, locus::AlignWriterPtr bamletWriter)
{
    // Setup thread-specific data structures and thread pool
    const InputPaths& inputPaths = programParams.inputPaths();
    const Sex sampleSex = programParams.sample().sex();

    const int threadCount = programParams.threadCount;
    const unsigned maxActiveLocusAnalyzerQueues(threadCount + 5);
    const unsigned locusAnalyzerCount(regionCatalog.size());
    LocusAnalyzerThreadSharedData locusAnalyzerThreadSharedData(maxActiveLocusAnalyzerQueues, locusAnalyzerCount);
    std::vector<LocusAnalyzerThreadLocalData> locusAnalyzerThreadLocalDataPool(threadCount);
    for (int threadIndex(0); threadIndex < threadCount; ++threadIndex)
    {
        auto& locusAnalyzerThreadData(locusAnalyzerThreadLocalDataPool[threadIndex]);
        locusAnalyzerThreadData.alignerSelectorPtr.reset(
            new graphtools::AlignerSelector(heuristicParams.alignerType()));
    }
    ctpl::thread_pool pool(threadCount);

    spdlog::info("Initializing all loci");
    graphtools::AlignerSelector alignerSelector(heuristicParams.alignerType());
    locusAnalyzerThreadSharedData.locusAnalyzers
        = initializeLocusAnalyzers(regionCatalog, heuristicParams, bamletWriter, threadCount);
    GenomeQueryCollection genomeQuery(locusAnalyzerThreadSharedData.locusAnalyzers);

    spdlog::info("Streaming reads");

    auto ReadHash = [](const Read& read) { return std::hash<std::string>()(read.fragmentId()); };
    auto ReadEq = [](const Read& read1, const Read& read2) { return (read1.fragmentId() == read2.fragmentId()); };
    using ReadCatalog = absl::flat_hash_set<Read, decltype(ReadHash), decltype(ReadEq)>;
    ReadCatalog unpairedReads(1000, ReadHash, ReadEq);

    const unsigned htsDecompressionThreads(std::min(threadCount, 12));
    htshelpers::HtsFileStreamer readStreamer(inputPaths.htsFile(), inputPaths.reference(), htsDecompressionThreads);
    while (readStreamer.trySeekingToNextPrimaryAlignment() && readStreamer.isStreamingAlignedReads())
    {
        // Stop processing reads if an exception is thrown in the worker pool:
        if (locusAnalyzerThreadSharedData.isWorkerThreadException.load())
        {
            break;
        }

        const bool isReadNearTargetRegion = genomeQuery.targetRegionMask.query(
            readStreamer.currentReadContigId(), readStreamer.currentReadPosition());
        const bool isMateNearTargetRegion = genomeQuery.targetRegionMask.query(
            readStreamer.currentMateContigId(), readStreamer.currentMatePosition());
        if (!isReadNearTargetRegion && !isMateNearTargetRegion)
        {
            continue;
        }

        if (not readStreamer.currentIsPaired())
        {
            continue;
        }

        Read read = readStreamer.decodeRead();
        const auto mateIterator = unpairedReads.find(read);
        if (mateIterator == unpairedReads.end())
        {
            unpairedReads.emplace(std::move(read));
            continue;
        }
        Read mate = std::move(*mateIterator);
        unpairedReads.erase(mateIterator);

        const int64_t readEnd = readStreamer.currentReadPosition() + read.sequence().length();
        const int64_t mateEnd = readStreamer.currentMatePosition() + mate.sequence().length();

        vector<AnalyzerBundle> analyzerBundles = genomeQuery.analyzerFinder.query(
            readStreamer.currentReadContigId(), readStreamer.currentReadPosition(), readEnd,
            readStreamer.currentMateContigId(), readStreamer.currentMatePosition(), mateEnd);

        const unsigned bundleCount(analyzerBundles.size());
        for (unsigned bundleIndex(0); bundleIndex < bundleCount; ++bundleIndex)
        {
            auto& bundle(analyzerBundles[bundleIndex]);
            auto sendReadPair = [&](HtsStreamingReadPairQueue::ReadPair readPair)
            {
                if (locusAnalyzerThreadSharedData.readPairQueue.insertReadPair(bundle.locusIndex, std::move(readPair)))
                {
                    pool.push(
                        processLocusAnalyzerQueue, std::ref(locusAnalyzerThreadSharedData),
                        std::ref(locusAnalyzerThreadLocalDataPool), bundle.locusIndex);
                }
            };

            if ((bundleIndex + 1) < bundleCount)
            {
                sendReadPair({ bundle.regionType, bundle.inputType, read, mate });
            }
            else
            {
                sendReadPair({ bundle.regionType, bundle.inputType, std::move(read), std::move(mate) });
            }
        }
    }

    pool.stop(true);

    // Rethrow exceptions from the pool in thread order:
    if (locusAnalyzerThreadSharedData.isWorkerThreadException.load())
    {
        for (int threadIndex(0); threadIndex < threadCount; ++threadIndex)
        {
            const LocusAnalyzerThreadLocalData& locusAnalyzerThreadData(locusAnalyzerThreadLocalDataPool[threadIndex]);
            if (locusAnalyzerThreadData.threadExceptionPtr)
            {
                std::rethrow_exception(locusAnalyzerThreadData.threadExceptionPtr);
            }
        }
    }

    spdlog::info("Analyzing read evidence");

    SampleFindingsThreadSharedData sampleFindingsThreadSharedData;
    std::vector<SampleFindingsThreadLocalData> sampleFindingsThreadLocalDataPool(threadCount);

    const unsigned locusCount(locusAnalyzerThreadSharedData.locusAnalyzers.size());
    SampleFindings sampleFindings(locusCount);

    // Start all sampleFindings worker threads
    std::vector<std::thread> sampleFindingsThreads;
    for (int threadIndex(0); threadIndex < threadCount; ++threadIndex)
    {
        sampleFindingsThreads.emplace_back(
            analyzeLocus, threadIndex, sampleSex, std::ref(locusAnalyzerThreadSharedData.locusAnalyzers),
            std::ref(sampleFindings), std::ref(sampleFindingsThreadSharedData),
            std::ref(sampleFindingsThreadLocalDataPool));
    }

    // Rethrow exceptions from worker pool in thread order:
    if (sampleFindingsThreadSharedData.isWorkerThreadException.load())
    {
        for (int threadIndex(0); threadIndex < threadCount; ++threadIndex)
        {
            const auto& sampleFindingsThreadData(sampleFindingsThreadLocalDataPool[threadIndex]);
            if (sampleFindingsThreadData.threadExceptionPtr)
            {
                std::rethrow_exception(sampleFindingsThreadData.threadExceptionPtr);
            }
        }
    }

    for (int threadIndex(0); threadIndex < threadCount; ++threadIndex)
    {
        sampleFindingsThreads[threadIndex].join();
    }

    return sampleFindings;
}

}