postgresql – cursors: atomic MOVE then FETCH?

the intent

Basically, pagination: have a CURSOR (created using DECLARE outside any function here, but that can be changed if need be) concurrently addressed to retrieve batches of rows, implying moving the cursor position in order to fetch more than one line (FETCH count seems to be the only way to fetch more than one line).

the context

During a more global transaction (i.e. using one connection), I want to retrieve a range of rows through a cursor. To do so, I:

  • MOVE the cursor to the desired position (e.g. MOVE 42 FROM "mycursor")
  • then FETCH the amount of rows (e.g. FETCH FORWARD 10 FROM "mycursor")

However, this transaction is used by many workers (horizontally scaled), each receiving a set of “coordinates” for the cursor, like LIMIT and OFFSET: the index to MOVE to, and the amount of rows to FETCH. These workers use the DB connection through HTTP calls to a single DB API which handles the pool of connections and the transactions’ liveliness.

Because of this concurrent access to the transaction/connection, I need to ensure atomic execution of the couple “MOVE then FETCH“.

the setup

  • NodeJS workers consuming ranges of rows through a DB API
  • NodeJS DB API based on pg (latest)
  • PostgreSQL v10 (can be upgraded if required, all documentation links here are from v12 – latest)

the tries

  • WITH (MOVE 42 FROM "mycursor") FETCH 10 FROM "mycursor" produces a syntax error, apparently WITH doesn’t handle MOVE
  • MOVE 42 FROM "mycursor" ; FETCH 10 FROM "mycursor" as I’m inside a transaction I suppose this could work, but anyway I’m using Node’s pg which apparently doesn’t handle several statements in the same call to query() (no error, but no result yielded, I didn’t dig into this too much as it looks like a hack)
  • I’m not confident a function would guarantee atomicity, it doesn’t seem to be what PARALLEL UNSAFE does, and as I’m going to have high concurrency, I’d really love some explicitly written assurances about atomicity…

the reason

I’d prefer not to rely on LIMIT/OFFSET as it would require an ORDER BY clause to ensure pagination consistency (as per the docs, ctrl-f for “unpredictable”), unless (scrollable, without hold) cursors prove to be way more resource-consuming. “Way more” because it has to be weighed with the INSENSITIVE behavior of cursors that would allow me not to acquire a lock on the underlying table during the whole process. If it’s proven that pagination in this context is not feasible using cursors, I’ll fall back to this solution, unless you have something better to suggest!

the human side

Hello, and thanks in advance for the help! 🙂

Magento2: Issue with move product tabs

In catalog_product.view.xml I try move product tabs to short description area:

<move element="reviews.tab" destination="product.info.overview" after="-" />
<move element="product.attributes" destination="product.info.overview" after="-"/>

When I use this code, tabs disappear. Does anyone have any solution?

https://prnt.sc/tc42v2

Magento2: How to move category description to bottom

Try this

app/design/frontend/[Company]/[theme_name]/Magento_Catalog/layout/catalog_category_view.xml

Code

<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <move element="category.description" destination="content.bottom" />
    </body>
</page>

Hope this help you

Thanks …

c++ program to help find move sequences to solve Rubiks cubes

This program takes a text file and parses the initial cube start position, the pattern that the result should have and an optional start sequence. The cube is using numbers instead of colors for more exact positioning. Each face is represented by 9 numbers 3 rows of 3. The top face would be 1 2 3 4 5 6 7 8 9. The left face would be 10 11 12 13 14 15 16 17 18. etc. Speed is the most important thing to consider. On my computer with 16 cores it can solve a 7 move sequence in a couple of seconds. The time however grows by a factor of 12 with each move. So sequence of 10 moves would be hours. It will create multiple threads to find a sequence.

I can add the header files if needed.

Here is the code:

sequencefinder.cpp

#include <string>
#include <vector>
#include <iostream>
#include <fstream>
#include <thread>

#include "cube.h"
#include "str.h"
#include "findsequence.h"


using namespace std;

int find_sequence(string& file_name, bool use_slice);
void read_cube_start(istream& inputstream, vector<face_val<face_val_type>>& cubestart);
void read_pattern(istream& inputstream, vector<face_val<face_val_type>>& pattern);
void read_sequence(istream& inputstream, string& sequence);
void readstring(istream& inputstream, string& str);
void readfaceval(istream& inputstream, face_val<face_val_type>& val);
int parse_args(int argc, char** argv);
int check_parameter(vector<string>& args, string searcharg, string& var);
static const vector<face_val<face_val_type>> init = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54};

static int max_depth = 8;


//************************************
// Method:    main
// FullName:  main
// Access:    public 
// Returns:   int
// Qualifier:
// Parameter: const int argc
// Parameter: char * * argv
//************************************
int main(const int argc, char** argv)
{   
    try
    {
        return parse_args(argc, argv);
    }
    catch (...)
    {
        cout << "Exception thrown." << endl;
        return  -1;
    }
}

//************************************
// Method:    parse_args
// FullName:  parse_args
// Access:    public 
// Returns:   int
// Qualifier:
// Parameter: const int argc
// Parameter: char * * argv
//
// Parse the arguments
//************************************
int parse_args(const int argc, char** argv)
{
    if (argc < 2)
    {
        cout << "No options specified." << endl;
        return -1;
    }

    string name;
    string matchpattern;
    string cubestring;
    string sequence;
    string reversesequence;
    string filename;
    string executesequence;
    string executereversesequence;
    string slice;
    string maxdepth;
    
    bool use_slice;
    
    vector<string> args;
    for (auto argindex = 1; argindex < argc; argindex++)
        args.emplace_back(argv(argindex));

    auto result = check_parameter(args, "-c", cubestring);
    if (result < 0) return result;

    result = check_parameter(args, "-slice", slice);
    if (result < 0) return result;

    result = check_parameter(args, "-f", filename);
    if (result < 0) return result;

    result = check_parameter(args, "-depth", maxdepth);
    if (result < 0) return result;

    if (!maxdepth.empty())
    {
        max_depth = stoi(maxdepth);
    }
    
    use_slice = !slice.empty();
    
    if (!args.empty())
    {
        cout << "Unknown argument(s) ";
        for (auto& arg : args)
            cout << arg << " ";
        cout << endl;
        return -1;
    }

    if (!filename.empty())
    {
        result = find_sequence(filename, use_slice);
    }
    
    return result;
}

//************************************
// Method:    check_parameter
// FullName:  check_parameter
// Access:    public 
// Returns:   int
// Qualifier: // NOLINT(performance-unnecessary-value-param)
// Parameter: vector<string> & args
// Parameter: const string searcharg
// Parameter: string & var
//
// check a single parameter
//************************************
int check_parameter(vector<string>& args, const string searcharg, string& var)  // NOLINT(performance-unnecessary-value-param)
{
    auto argindex = 0;
    const auto argc = int(args.size());
    while (argindex < argc)
    {
        if (args(argindex++) == searcharg)
        {
            if (argindex >= argc)
            {
                cout << "No sequence specified with " << args(argindex) << "." << endl;
                return -1;
            }
            var = args(argindex);
            args.erase(args.begin() + (argindex - 1ll));
            args.erase(args.begin() + (argindex - 1ll));
            return 1;
        }
    }
    return 0;
}

//************************************
// Method:    read_cube_start
// FullName:  read_cube_start
// Access:    public 
// Returns:   void
// Qualifier:
// Parameter: istream & inputstream
// Parameter: vector<face_val<face_val_type>>& cubestart
//************************************
void read_cube_start(istream& inputstream, vector<face_val<face_val_type>>& cubestart)
{
    cubestart.clear();

    while (!inputstream.eof() && cubestart.size() < 54)
    {
        face_val<face_val_type> val;
        readfaceval(inputstream, val);
        cubestart.push_back(val);
    }   
}

//************************************
// Method:    read_pattern
// FullName:  read_pattern
// Access:    public 
// Returns:   void
// Qualifier:
// Parameter: istream & inputstream
// Parameter: vector<face_val<face_val_type>> & pattern
//************************************
void read_pattern(istream& inputstream, vector<face_val<face_val_type>>& pattern)
{
    pattern.clear();

    while (!inputstream.eof() && pattern.size() < 54)
    {
        face_val<face_val_type> val;
        readfaceval(inputstream, val);
        pattern.push_back(val);
    }
}

//************************************
// Method:    read_sequence
// FullName:  read_sequence
// Access:    public 
// Returns:   void
// Qualifier:
// Parameter: istream & inputstream
// Parameter: string & sequence
//************************************
void read_sequence(istream& inputstream, string& sequence)
{
    sequence.clear();
    string temp;
    readstring(inputstream, temp);
    if (temp == "**" || temp != "*") return;

    temp.clear();
    do
    {
        readstring(inputstream, temp);
        if (temp != "*")
        {
            if (sequence.length() > 0) sequence.append(" ");
            sequence.append(temp);
        }
    } while (temp != "*");
}

//************************************
// Method:    find_sequence
// FullName:  find_sequence
// Access:    public 
// Returns:   int
// Qualifier:
// Parameter: string & file_name
// Parameter: const bool use_slice
//************************************
int find_sequence(string& file_name, const bool use_slice)
{
    ifstream inputfile;
    try
    {
        inputfile.open(file_name);
        if (!inputfile.is_open())
            throw;
    }
    catch (...)
    {
        cout << "Error opening input file." << endl;
        return -1;
    }
    
    while (!inputfile.eof())
    {
        auto cube_values = init;
        vector<face_val<face_val_type>> pattern;
        string start_seq;
        string case_name;
        
        readstring(inputfile, case_name);

        if (inputfile.eof())
            continue;
        
        read_cube_start(inputfile, cube_values);
        read_pattern(inputfile, pattern);
        read_sequence(inputfile, start_seq);

        std::cout << "<Entry> " << endl;
        std::cout << "    <Key>" << case_name << "</Key>" << endl;

        string found_sequence;
        findsequence f;
        
        const auto found = f.find_sequence(cube_values, pattern, start_seq, found_sequence, 0, max_depth, use_slice);
        if (found)
        {           
            auto seq = start_seq.append(" ") + found_sequence;
            seq = trim(seq);
            if (!seq.empty())
            {
                std::cout << "    <Value>" << seq << "</Value>" << endl;
            }
            else
            {
                std::cout << "    <Value />" << endl;
            }
        }
        else
        {
            cout << "    <!-- Not Found! -->" << endl << "    <Value />" << endl;
        }
        cout << "</Entry>" << endl << endl;
    }
    inputfile.close();
    return 0;
}

//************************************
// Method:    readstring
// FullName:  readstring
// Access:    public 
// Returns:   void
// Qualifier:
// Parameter: istream & inputstream
// Parameter: string & str
//************************************
void readstring(istream& inputstream, string& str)
{
    string temp;
    while (!inputstream.eof() && temp.empty())
    {
        inputstream >> temp;
        str = trim(temp);

        if (str.length() > 1 && str(0) == "https://codereview.stackexchange.com/" && str(1) == str(0))
        {
            cout << str << " ";
            getline(inputstream, str);
            cout << str << endl;
            temp.erase();
        }
    }
}

//************************************
// Method:    readfaceval
// FullName:  readfaceval
// Access:    public 
// Returns:   void
// Qualifier:
// Parameter: istream & inputstream
// Parameter: face_val<face_val_type> val
//************************************
void readfaceval(istream& inputstream, face_val<face_val_type>& val)
{
    string temp;
    readstring(inputstream, temp);
    if (temp == "-")
    {
        val = 0;
        return;
    }
    const auto n = stoi(temp);
    val = n;
}

findsequence.cpp

#include <string>
#include <vector>
#include <iostream>
#include <thread>
#include <memory>

#include "cube.h"
#include "findsequence.h"
#include "str.h"

#ifndef __countof
#define _countof(array) (sizeof(array) / sizeof(array(0)))
#endif

//************************************
// Method:    find_sequence
// FullName:  findsequence::find_sequence
// Access:    public 
// Returns:   bool
// Qualifier:
// Parameter: cube * cube_original
// Parameter: std::vector<face_val<face_val_type>> & pattern
// Parameter: int n
// Parameter: std::vector<std::string> & data
// Parameter: std::string & out
//
// Find the moves from a given cube that
// makes the cube match the pattern. The number of moves
// will be exactly n or 0 if the cube already has the pattern.
// On exit out will be the sequence of moves or empty string.
// This will return true if the pattern is matched.
//
// This will start a thread for each possible move and wait for all to complete.
// This is the second layer of the findsequence API
//************************************
bool findsequence::find_sequence(cube* cube_original, std::vector<face_val<face_val_type>>& pattern, int n,
    std::vector<std::string>& data, std::string& out)
{
    out = "";

    // Check for initial match
    if (*cube_original == pattern)
    {
        return true;
    }

    if (n > -1)
    {
        constexpr auto buff_sz = 26;
        auto timer = time(nullptr);
        char buffer(buff_sz);
        const auto tm_info = localtime(&timer);

        strftime(buffer, _countof(buffer), "%Y-%m-%d %H:%M:%S", tm_info);
        std::cout << "    <!-- Level " << n << " " << buffer << " -->" << std::endl;
    }

    auto result = false;

    // create the thread start data
    auto threaddata = std::vector<std::unique_ptr<thread_start_data>>(data.size());
    for (auto i = 0lu; i < data.size(); i++)
    {
        threaddata(i) = std::make_unique<thread_start_data>(cube_original, pattern, n, data, data(i), this);
    }

    // Create the threads
    auto threads = std::vector<std::thread>(threaddata.size());
    for (auto i = 0lu; i < threads.size(); i++)
    {       
        threads(i) = std::thread(&find_sequence_async, threaddata(i).get());
    }

    // wait for threads to terminate
    for (auto& t : threads)
    {
        t.join();
    }

    // Check result
    for (auto& td : threaddata)
    {
        if (!result && td->result)
        {
            // save result
            out = td->out;
            result = true;
            break;
        }
    }

    return result;
}

//************************************
// Method:    find_sequence
// FullName:  findsequence::find_sequence
// Access:    public 
// Returns:   bool
// Qualifier:
// Parameter: std::vector<face_val<face_val_type>> & cube_initial_values
// Parameter: std::vector<face_val<face_val_type>> & pattern
// Parameter: std::string & start_seq
// Parameter: std::string & out
// Parameter: const int start
// Parameter: const int max_moves
// Parameter: const bool use_slice
//
// Find the moves from a given cube string that
// makes the cube match the pattern (pattern). The number of moves will up to n.
// On exit out will be the sequence of moves or empty string.
// This will return true if the pattern is matched.
// This will start a thread for each possible move and wait for all to complete.
//
// This is the topmost layer of the find_sequence API
//************************************
bool findsequence::find_sequence(std::vector<face_val<face_val_type>>& cube_initial_values, std::vector<face_val<face_val_type>>& pattern, std::string& start_seq,
                                 std::string& out, const int start, const int max_moves, const bool use_slice)
{
    static std::vector<std::string> cube_directions =
    {
        "U", "Ui", "D", "Di", "L", "Li", "R", "Ri", "B", "Bi", "F", "Fi",
        "Us", "Ds", "Ls", "Rs", "Fs", "Bs"
    };

    static std::vector<std::string> cube_directions_no_slice =
    {
        "U", "Ui", "D", "Di", "L", "Li", "R", "Ri", "B", "Bi", "F", "Fi"
    };

    auto data = use_slice ? cube_directions : cube_directions_no_slice; 

    const auto cube_original = std::make_unique<cube>();
    cube_original->set_cube(cube_initial_values);

    // if there is a start sequence execute it here
    if (!start_seq.empty())
    {
        cube_original->execute_sequence(start_seq);
    }
    
    auto found = false;
    for (auto n = start; !found && n <= max_moves; n++)
    {
        // call second layer of API
        found = find_sequence(cube_original.get(), pattern, n, data, out);
    }
    return found;
}

//************************************
// Method:    find_sequence
// FullName:  findsequence::find_sequence
// Access:    public 
// Returns:   void
// Qualifier: const
// Parameter: cube * cube_original
// Parameter: std::vector<face_val<face_val_type>> & pattern
// Parameter: const int n
// Parameter: std::vector<std::string> & data
// Parameter: std::string & out
// Parameter: std::string & start
// Parameter: bool & result
//************************************
void findsequence::find_sequence(cube* cube_original, std::vector<face_val<face_val_type>>& pattern, const int n,
                                 std::vector<std::string>& data, std::string& out, std::string& start, bool& result) const
{
    std::vector<face_val<face_val_type>> cube_initial_values;

    cube_original->get_cube(cube_initial_values);
    find_sequence(cube_initial_values, pattern, n, data, out, start, result);
}

//************************************
// Method:    find_sequence
// FullName:  findsequence::find_sequence
// Access:    public 
// Returns:   void
// Qualifier: const
// Parameter: std::string & cube_initial_values
// Parameter: std::vector<std::string> & pattern
// Parameter: const int n
// Parameter: std::vector<std::string> & data
// Parameter: std::string & out
// Parameter: std::string & start
// Parameter: bool & result
//************************************
void findsequence::find_sequence(std::vector<face_val<face_val_type>>& cube_initial_values, std::vector<face_val<face_val_type>>& pattern, 
    const int n, std::vector<std::string>& data, std::string& out, std::string& start, bool& result) const
{
    result = false;

    auto indexlist = std::vector<int>(n, 0);
    auto done = false;
    const auto end = data.size();

    std::vector<std::string> start_moves;
    std::string chars = "tnrvf ";
    split(start, chars, start_moves);
    
    auto c = std::make_unique<cube>();
    while (!done)
    {
        c->set_cube(cube_initial_values);
        auto tokens = start_moves;
        
        for (auto i = 1; i < n; i++)
        {
            tokens.push_back(data(indexlist(i)));
        }

        c->execute_sequence(tokens);
        done = true;
        if (*c == pattern)
        {
            join(tokens, out, " ");
            result = true;
        }
        else
        {
            for (auto index = 1; index < n; index++)
            {
                if (indexlist(index) + 1 < int(end))
                {
                    indexlist(index)++;
                    done = false;
                    break;
                }
                indexlist(index) = 0;
            }
        }
    }
}

//************************************
// Method:    find_sequence_async
// FullName:  findsequence::find_sequence_async
// Access:    private static 
// Returns:   void
// Qualifier:
// Parameter: thread_start_data * threadstart
//************************************
void findsequence::find_sequence_async(thread_start_data* threadstart)
{
    const auto sz = threadstart->n;
    auto data = threadstart->data;  
    auto pattern = threadstart->pattern;

    std::vector<face_val<face_val_type>> cube_initial_values;
    threadstart->cube_original->get_cube(cube_initial_values);
    
    auto c = std::make_unique<cube>();
    c->set_cube(cube_initial_values);

    std::string out;
    bool result;
    auto start = threadstart->start;
    threadstart->instance->find_sequence(cube_initial_values, pattern, sz, data, out, start, result);
    threadstart->result = result;
    threadstart->out = out;
}

cube.cpp

#include <cstdlib>
#include <cstring>
#include <ctime>
#include <string>
#include <vector>

#include "cube.h"
#include "str.h"

//************************************
// Method:    cube
// FullName:  cube::cube
// Access:    public 
// Returns:   
// Qualifier:
//
// Build a default cube.
//************************************
cube::cube()
{
    const auto time_ui = unsigned (time(nullptr));
    srand(time_ui);

    whitechars = "tnrvf ";
    record_ = true;
    init_cube();
}

//************************************
// Method:    init_cube
// FullName:  cube::init_cube
// Access:    public 
// Returns:   void
// Qualifier:
//
// Initialize the cube.
// This clears _moves and _scramble_sequence.
//************************************
void cube::init_cube()
{
    face *faces() = { &up, &left, &front, &right, &back, &down };
    auto n = 1;
    for (auto& f : faces)
        for (auto& row : f->square)
            for (auto& col : row)
                col = n++;

    moves_.clear();
    scramble_sequence_.clear();
}

//************************************
// Method:    set_cube
// FullName:  cube::set_cube
// Access:    public 
// Returns:   void
// Qualifier:
// Parameter: const char * cube_values
//
// Set cube values.
// This clears _moves and _scramble_sequence.
//************************************
void cube::set_cube(std::vector<face_val<face_val_type>>& cube_values)
{
    face *faces() = { &up, &left, &front, &right, &back, &down };
    auto n = 0;
    for (auto& f : faces)
        for (auto& row : f->square)
            for (auto& col : row)
                col = cube_values(n++);

    moves_.clear();
    scramble_sequence_.clear();
}

//************************************
// Method:    get_cube
// FullName:  cube::get_cube
// Access:    private 
// Returns:   void
// Qualifier: const
// Parameter: std::vector<face_val<face_val_type>> & pattern
//************************************
void cube::get_cube(std::vector<face_val<face_val_type>>& pattern) const
{
    pattern.clear();

    const face *faces() = { &up, &left, &front, &right, &back, &down };
    for (auto& f : faces)
        for (auto& row : f->square)
            for (auto& col : row)
                pattern.push_back(col);
}

//************************************
// Method:    clone
// FullName:  cube::clone
// Access:    public 
// Returns:   cube::cube *
// Qualifier:
// 
// This clones the cube instance
//************************************
cube * cube::clone() const
{
    auto clone_cube = std::make_unique<cube>();
    for (auto row = 0; row < cube_size; row++)
        for (auto col = 0; col < cube_size; col++)
        {
            clone_cube->up.square(row)(col) = up.square(row)(col);
            clone_cube->left.square(row)(col) = left.square(row)(col);
            clone_cube->front.square(row)(col) = front.square(row)(col);
            clone_cube->right.square(row)(col) = right.square(row)(col);
            clone_cube->back.square(row)(col) = back.square(row)(col);
            clone_cube->down.square(row)(col) = down.square(row)(col);
        }
    return clone_cube.get();
}

// ReSharper disable once CppMemberFunctionMayBeStatic
//************************************
// Method:    simple_solve
// FullName:  cube::simple_solve
// Access:    public 
// Returns:   bool
// Qualifier:
// Parameter: char * buffer
// Parameter: size_t * sz
// 
// This performs a simple solve
//************************************
bool cube::simple_solve(char* buffer, size_t* sz)
{
    // const c_simple_solver solver(this);
    // return solver.solve(buffer, sz);
    return true;
}

//************************************
// Method:    issolved
// FullName:  cube::issolved
// Access:    public 
// Returns:   bool
// Qualifier:
//
// This returns true if the cube is solved
//************************************
bool cube::issolved()
{
    return up.issolved() && left.issolved() && front.issolved() && right.issolved() && back.issolved() && down.issolved();
}

//************************************
// Method:    f
// FullName:  cube::f
// Access:    public 
// Returns:   void
// Qualifier:
//
// Rotate the front face clockwise
//************************************
void cube::f()
{
    front.rotate_clockwise();
    for (auto index = 0; index < cube_size; index++)
    {
        const auto temp = up.square(cube_size - 1)(cube_size - 1 - index);
        up.square(cube_size - 1)(cube_size - 1 - index) = left.square(index)(cube_size - 1);
        left.square(index)(cube_size - 1) = down.square(0)(index);
        down.square(0)(index) = right.square(cube_size - 1 - index)(0);
        right.square(cube_size - 1 - index)(0) = temp;
    }
    if (record_)
        moves_ += "F ";
}

//************************************
// Method:    fi
// FullName:  cube::fi
// Access:    public 
// Returns:   void
// Qualifier:
//
// Rotate the front face counter clockwise
//************************************
void cube::fi()
{
    front.rotate_counter_clockwise();
    for (auto index = 0; index < cube_size; index++)
    {
        const auto temp = up.square(cube_size -1)(cube_size -1 - index);
        up.square(cube_size - 1)(cube_size - 1 - index) = right.square(cube_size - 1 - index)(0);
        right.square(cube_size - 1 - index)(0) = down.square(0)(index);
        down.square(0)(index) = left.square(index)(cube_size -1);
        left.square(index)(cube_size - 1) = temp;
    }
    if (record_)
        moves_ += "Fi ";
}

//************************************
// Method:    u
// FullName:  cube::u
// Access:    public 
// Returns:   void
// Qualifier:
//
// Rotate the up face clockwise
//************************************
void cube::u()
{
    up.rotate_clockwise();
    for (auto index = 0; index < cube_size; index++)
    {
        const auto temp = front.square(0)(index);
        front.square(0)(index) = right.square(0)(index);
        right.square(0)(index) = back.square(0)(index);
        back.square(0)(index) = left.square(0)(index);
        left.square(0)(index) = temp;
    }
    if (record_)
        moves_ += "U ";
}

//************************************
// Method:    ui
// FullName:  cube::ui
// Access:    public 
// Returns:   void 
// Qualifier:
//
// Rotate the up face counter clockwise
//************************************
void cube::ui()
{
    up.rotate_counter_clockwise();
    for (auto index = 0; index < cube_size; index++)
    {
        const auto temp = front.square(0)(index);
        front.square(0)(index) = left.square(0)(index);
        left.square(0)(index) = back.square(0)(index);
        back.square(0)(index) = right.square(0)(index);
        right.square(0)(index) = temp;
    }
    if (record_)
        moves_ += "Ui ";
}

//************************************
// Method:    b
// FullName:  cube::b
// Access:    public 
// Returns:   void
// Qualifier:
//
// Rotate the back face clockwise
//************************************
void cube::b()
{
    back.rotate_clockwise();
    for (auto index = 0; index < cube_size; index++)
    {
        const auto temp = up.square(0)(index);
        up.square(0)(index) = right.square(index)(cube_size - 1);
        right.square(index)(cube_size - 1) = down.square(cube_size - 1)(cube_size - 1 - index);
        down.square(cube_size - 1)(cube_size - 1 - index) = left.square(cube_size - 1 - index)(0);
        left.square(cube_size - 1 - index)(0) = temp;
    }
    if (record_)
        moves_ += "B ";
}

//************************************
// Method:    bi
// FullName:  cube::bi
// Access:    public 
// Returns:   void
// Qualifier:
//
// Rotate the back face counter clockwise
//************************************
void cube::bi()
{
    back.rotate_counter_clockwise();
    for (auto index = 0; index < cube_size; index++)
    {
        const auto temp = up.square(0)(index);
        up.square(0)(index) = left.square(cube_size -1 - index)(0);
        left.square(cube_size - 1 - index)(0) = down.square(cube_size - 1)(cube_size - 1 - index);
        down.square(cube_size - 1)(cube_size - 1 - index) = right.square(index)(cube_size - 1);
        right.square(index)(cube_size - 1) = temp;
    }
    if (record_)
        moves_ += "Bi ";
}

//************************************
// Method:    l
// FullName:  cube::l
// Access:    public 
// Returns:   void
// Qualifier:
//
// Rotate the left face clockwise
//************************************
void cube::l()
{
    left.rotate_clockwise();
    for (auto index = 0; index < cube_size; index++)
    {
        const auto temp = up.square(index)(0);
        up.square(index)(0) = back.square(cube_size - 1 - index)(cube_size - 1);
        back.square(cube_size - 1 - index)(cube_size - 1) = down.square(index)(0);
        down.square(index)(0) = front.square(index)(0);
        front.square(index)(0) = temp;
    }
    if (record_)
        moves_ += "L ";
}

//************************************
// Method:    li
// FullName:  cube::li
// Access:    public 
// Returns:   void
// Qualifier:
//
// Rotate the left face counter clockwise
//************************************
void cube::li()
{
    left.rotate_counter_clockwise();
    for (auto index = 0; index < cube_size; index++)
    {
        const auto temp = up.square(index)(0);
        up.square(index)(0) = front.square(index)(0);
        front.square(index)(0) = down.square(index)(0);
        down.square(index)(0) = back.square(cube_size - 1 - index)(cube_size - 1);
        back.square(cube_size - 1 - index)(cube_size - 1) = temp;
    }
    if (record_)
        moves_ += "Li ";
}

//************************************
// Method:    r
// FullName:  cube::r
// Access:    public 
// Returns:   void
// Qualifier:
//
// Rotate the right face clockwise
//************************************
void cube::r()
{
    right.rotate_clockwise();
    for (auto index = 0; index < cube_size; index++)
    {
        const auto temp = up.square(index)(cube_size - 1);
        up.square(index)(cube_size - 1) = front.square(index)(cube_size - 1);
        front.square(index)(cube_size - 1) = down.square(index)(cube_size - 1);
        down.square(index)(cube_size - 1) = back.square(cube_size - 1 - index)(0);
        back.square(cube_size - 1 - index)(0) = temp;
    }
    if (record_)
        moves_ += "R ";
}

//************************************
// Method:    ri
// FullName:  cube::ri
// Access:    public 
// Returns:   void
// Qualifier:
//
// Rotate the right face counter clockwise
//************************************
void cube::ri()
{
    right.rotate_counter_clockwise();
    for (auto index = 0; index < cube_size; index++)
    {
        const auto temp = up.square(cube_size - 1 - index)(cube_size - 1);
        up.square(cube_size - 1 - index)(cube_size - 1) = back.square(index)(0);
        back.square(index)(0) = down.square(cube_size - 1 - index)(cube_size - 1);
        down.square(cube_size - 1 - index)(cube_size - 1) = front.square(cube_size - 1 - index)(cube_size - 1);
        front.square(cube_size - 1 - index)(cube_size - 1) = temp;
    }
    if (record_)
        moves_ += "Ri ";
}

//************************************
// Method:    d
// FullName:  cube::d
// Access:    public 
// Returns:   void
// Qualifier:
//
// Rotate the down face clockwise
//************************************
void cube::d()
{
    down.rotate_clockwise();
    for (auto index = 0; index < cube_size; index++)
    {
        const auto temp = front.square(cube_size -1)(index);
        front.square(cube_size - 1)(index) = left.square(cube_size - 1)(index);
        left.square(cube_size - 1)(index) = back.square(cube_size - 1)(index);
        back.square(cube_size - 1)(index) = right.square(cube_size - 1)(index);
        right.square(cube_size - 1)(index) = temp;
    }
    if (record_)
        moves_ += "D ";
}

//************************************
// Method:    di
// FullName:  cube::di
// Access:    public 
// Returns:   void
// Qualifier:
//
// Rotate the down face counter clockwise
//************************************
void cube::di()
{
    down.rotate_counter_clockwise();
    for (auto index = 0; index < cube_size; index++)
    {
        const auto temp = front.square(cube_size - 1)(index);
        front.square(cube_size - 1)(index) = right.square(cube_size - 1)(index);
        right.square(cube_size - 1)(index) = back.square(cube_size - 1)(index);
        back.square(cube_size - 1)(index) = left.square(cube_size - 1)(index);
        left.square(cube_size - 1)(index) = temp;
    }
    if (record_)
        moves_ += "Di ";
}

//************************************
// Method:    us
// FullName:  cube::us
// Access:    public 
// Returns:   void
// Qualifier:
//
// Rotate the cube up slice clockwise
//************************************
void cube::us()
{
    for (auto col = 0; col < cube_size; col++)
    {
        const auto temp = front.square(1)(col);
        front.square(1)(col) = right.square(1)(col);
        right.square(1)(col) = back.square(1)(col);
        back.square(1)(col) = left.square(1)(col);
        left.square(1)(col) = temp;
    }
    if (record_)
        moves_ += "Us ";
}

//************************************
// Method:    ds
// FullName:  cube::ds
// Access:    public 
// Returns:   void
// Qualifier:
//
// Rotate the cube down slice clockwise
//************************************
void cube::ds()
{
    for (auto col = 0; col < cube_size; col++)
    {
        const auto temp = front.square(1)(col);
        front.square(1)(col) = left.square(1)(col);
        left.square(1)(col) = back.square(1)(col);
        back.square(1)(col) = right.square(1)(col);
        right.square(1)(col) = temp;
    }
    if (record_)
        moves_ += "Ds ";
}

//************************************
// Method:    ls
// FullName:  cube::ls
// Access:    public 
// Returns:   void
// Qualifier:
//
// Rotate the cube left slice clockwise
//************************************
void cube::ls()
{
    for (auto row = 0; row < cube_size; row++)
    {
        const auto temp = up.square(row)(1);
        up.square(row)(1) = back.square(cube_size - row - 1)(1);
        back.square(cube_size - row - 1)(1) = down.square(row)(1);
        down.square(row)(1) = front.square(row)(1);
        front.square(row)(1) = temp;
    }
    if (record_)
        moves_ += "Ls ";
}

//************************************
// Method:    rs
// FullName:  cube::rs
// Access:    public 
// Returns:   void
// Qualifier:
//
// Rotate the cube right slice clockwise
//************************************
void cube::rs()
{
    for (auto row = 0; row < cube_size; row++)
    {
        const auto temp = up.square(row)(1);
        up.square(row)(1) = front.square(row)(1);
        front.square(row)(1) = down.square(row)(1);
        down.square(row)(1) = back.square(cube_size - row - 1)(1);
        back.square(cube_size - row - 1)(1) = temp;
    }
    if (record_)
        moves_ += "Rs ";
}

//************************************
// Method:    fs
// FullName:  cube::fs
// Access:    public 
// Returns:   void
// Qualifier:
//
// Rotate the cube front slice clockwise
//************************************
void cube::fs()
{
    for (auto index = 0; index < cube_size; index++)
    {
        const auto temp = up.square(1)(index);
        up.square(1)(index) = left.square(cube_size - index - 1)(1);
        left.square(cube_size - index - 1)(1) = down.square(1)(cube_size - index -1);
        down.square(1)(cube_size - index -1) = right.square(index)(1);
        right.square(index)(1) = temp;
    }
    if (record_)
        moves_ += "Fs ";
}

//************************************
// Method:    bs
// FullName:  cube::bs
// Access:    public 
// Returns:   void
// Qualifier:
//
// Rotate the cube back slice clockwise
//************************************
void cube::bs()
{
    for (auto index = 0; index < cube_size; index++)
    {
        const auto temp = up.square(1)(index);
        up.square(1)(index) = right.square(index)(1);
        right.square(index)(1) = down.square(1)(cube_size - index - 1);
        down.square(1)(cube_size - index - 1) = left.square(cube_size - index - 1)(1);
        left.square(cube_size - index - 1)(1) = temp;
    }
    if (record_)
        moves_ += "Bs ";
}

//************************************
// Method:    cu
// FullName:  cube::cu
// Access:    public 
// Returns:   void
// Qualifier:
//
// Rotate the cube up
//************************************
void cube::cu()
{
    left.rotate_counter_clockwise();
    right.rotate_clockwise();
    for (auto row = 0; row < cube_size; row++)
        for (auto col = 0; col < cube_size; col++)
        {
            const auto temp = up.square(row)(col);
            up.square(row)(col) = front.square(row)(col);
            front.square(row)(col) = down.square(row)(col);
            down.square(row)(col) = back.square(cube_size - 1 - row)(cube_size - 1 - col);
            back.square(cube_size - 1 - row)(cube_size - 1 - col) = temp;
        }
    if (record_)
        moves_ += "Cu ";
}

//************************************
// Method:    cd
// FullName:  cube::cd
// Access:    public 
// Returns:   void
// Qualifier:
//
// Rotate the cube down
//************************************
void cube::cd()
{
    left.rotate_clockwise();
    right.rotate_counter_clockwise();
    for (auto row = 0; row < cube_size; row++)
        for (auto col = 0; col < cube_size; col++)
        {
            const auto temp = down.square(row)(col);
            down.square(row)(col) = front.square(row)(col);
            front.square(row)(col) = up.square(row)(col);
            up.square(row)(col) = back.square(cube_size - 1 - row)(cube_size - 1 - col);
            back.square(cube_size - 1 - row)(cube_size - 1 - col) = temp;
        }
    if (record_)
        moves_ += "Cd ";
}

//************************************
// Method:    cl
// FullName:  cube::cl
// Access:    public 
// Returns:   void
// Qualifier:
//
// Rotate the cube left
//************************************
void cube::cl()
{
    down.rotate_counter_clockwise();
    up.rotate_clockwise();
    for (auto row = 0; row < cube_size; row++)
        for (auto col = 0; col < cube_size; col++)
        {
            const auto temp = front.square(row)(col);
            front.square(row)(col) = right.square(row)(col);
            right.square(row)(col) = back.square(row)(col);
            back.square(row)(col) = left.square(row)(col);
            left.square(row)(col) = temp;
        }
    if (record_)
        moves_ += "Cl ";
}

//************************************
// Method:    cr
// FullName:  cube::cr
// Access:    public 
// Returns:   void
// Qualifier:
//
// Rotate the cube face_right
//************************************
void cube::cr()
{
    down.rotate_clockwise();
    up.rotate_counter_clockwise();
    for (auto row = 0; row < cube_size; row++)
        for (auto col = 0; col < cube_size; col++)
        {
            const auto temp = front.square(row)(col);
            front.square(row)(col) = left.square(row)(col);
            left.square(row)(col) = back.square(row)(col);
            back.square(row)(col) = right.square(row)(col);
            right.square(row)(col) = temp;
        }
    if (record_)
        moves_ += "Cr ";
}

//************************************
// Method:    reverse_sequence
// FullName:  cube::reverse_sequence
// Access:    public static 
// Returns:   void
// Qualifier: const
// Parameter: const char * sequence
// Parameter: char * buffer
// Parameter: size_t * sz
//
// Create an inverse to a sequence
//************************************
void cube::reverse_sequence(const char* sequence, char* buffer, size_t* sz) const
{
    std::string result;
    reverse_sequence(sequence, result);
    if (buffer != nullptr && *sz >= result.length())
        strcpy(buffer, result.c_str());
    if (sz != nullptr) *sz = result.length();
}

//************************************
// Method:    reverse_sequence
// FullName:  cube::reverse_sequence
// Access:    public static 
// Returns:   void
// Qualifier:
// Parameter: const std::string & sequence
// Parameter: std::string & out
//
// Create an inverse to a sequence
//************************************
void cube::reverse_sequence(const std::string& sequence, std::string& out)
{
    std::string buffer;
    const std::string white = "tnrvf ";
    
    std::vector<std::string> strs;
    split(sequence, white, strs);

    for (auto str : strs)
    {
        auto lowerstr = lower(str);
        if (buffer.length() > 0)
            buffer.insert(buffer.begin(), ' ');

        if (lowerstr == "u")
        {
            buffer.insert(buffer.begin(), 'i');
            buffer.insert(buffer.begin(), str(0));
        }
        else if (lowerstr == "ui")
        {
            buffer.insert(buffer.begin(), str(0));
        }
        else if (lowerstr == "l")
        {
            buffer.insert(buffer.begin(), 'i');
            buffer.insert(buffer.begin(), str(0));
        }
        else if (lowerstr == "li")
        {
            buffer.insert(buffer.begin(), str(0));
        }
        else if (lowerstr == "f")
        {
            buffer.insert(buffer.begin(), 'i');
            buffer.insert(buffer.begin(), str(0));
        }
        else if (lowerstr == "fi")
        {
            buffer.insert(buffer.begin(), str(0));
        }
        else if (lowerstr == "r")
        {
            buffer.insert(buffer.begin(), 'i');
            buffer.insert(buffer.begin(), str(0));
        }
        else if (lowerstr == "ri")
        {
            buffer.insert(buffer.begin(), str(0));
        }
        else if (lowerstr == "b")
        {
            buffer.insert(buffer.begin(), 'i');
            buffer.insert(buffer.begin(), str(0));
        }
        else if (lowerstr == "bi")
        {
            buffer.insert(buffer.begin(), str(0));
        }
        else if (lowerstr == "d")
        {
            buffer.insert(buffer.begin(), 'i');
            buffer.insert(buffer.begin(), str(0));
        }
        else if (lowerstr == "di")
        {
            buffer.insert(buffer.begin(), 'd');
        }
        else if (lowerstr == "us")
        {
            buffer.insert(buffer.begin(), str(1));
            buffer.insert(buffer.begin(), 'D');
        }
        else if (lowerstr == "ds")
        {
            buffer.insert(buffer.begin(), str(1));
            buffer.insert(buffer.begin(), 'U');
        }
        else if (lowerstr == "ls")
        {
            buffer.insert(buffer.begin(), str(1));
            buffer.insert(buffer.begin(), 'R');
        }
        else if (lowerstr == "rs")
        {
            buffer.insert(buffer.begin(), str(1));
            buffer.insert(buffer.begin(), 'L');
        }
        else if (lowerstr == "fs")
        {
            buffer.insert(buffer.begin(), str(1));
            buffer.insert(buffer.begin(), 'B');
        }
        else if (lowerstr == "bs")
        {
            buffer.insert(buffer.begin(), str(1));
            buffer.insert(buffer.begin(), 'F');
        }
        else if (lowerstr == "cu")
        {
            buffer.insert(buffer.begin(), 'd');
            buffer.insert(buffer.begin(), str(0));
        }
        else if (lowerstr == "cd")
        {
            buffer.insert(buffer.begin(), 'u');
            buffer.insert(buffer.begin(), str(0));
        }
        else if (lowerstr == "cl")
        {
            buffer.insert(buffer.begin(), 'r');
            buffer.insert(buffer.begin(), str(0));
        }
        else if (lowerstr == "cr")
        {
            buffer.insert(buffer.begin(), 'l');
            buffer.insert(buffer.begin(), str(0));
        }
    }
    optimize_sequence(buffer, out); 
}

//************************************
// Method:    execute_move
// FullName:  cube::execute_move
// Access:    public 
// Returns:   void
// Qualifier:
// Parameter: const char * move
//
// Perform a single move
//************************************
void cube::execute_move(const char* move)
{
    if (iequals(move, "u"))         u();
    else if (iequals(move, "ui"))   ui();
    else if (iequals(move, "d"))    d();
    else if (iequals(move, "di"))   di();
    else if (iequals(move, "l"))    l();
    else if (iequals(move, "li"))   li();
    else if (iequals(move, "r"))    r();
    else if (iequals(move, "ri"))   ri();
    else if (iequals(move, "f"))    f();
    else if (iequals(move, "fi"))   fi();
    else if (iequals(move, "b"))    b();
    else if (iequals(move, "bi"))   bi();
    else if (iequals(move, "us"))   us();
    else if (iequals(move, "ds"))   ds();
    else if (iequals(move, "rs"))   rs();
    else if (iequals(move, "ls"))   ls();
    else if (iequals(move, "fs"))   fs();
    else if (iequals(move, "bs"))   bs();
    else if (iequals(move, "cu"))   cu();
    else if (iequals(move, "cd"))   cd();
    else if (iequals(move, "cl"))   cl();
    else if (iequals(move, "cr"))   cr();
}

//************************************
// Method:    execute_move
// FullName:  cube::execute_move
// Access:    public 
// Returns:   void
// Qualifier:
// Parameter: const std::string & move
//
// Perform a single move
//************************************
void cube::execute_move(const std::string& move)
{
    execute_move(move.c_str());
}

//************************************
// Method:    execute_sequence
// FullName:  cube::execute_sequence
// Access:    public 
// Returns:   void
// Qualifier:
// Parameter: const char * sequence
//
// Execute a move sequence
//************************************
void cube::execute_sequence(const char* sequence)
{
    const std::string seq = sequence;
    execute_sequence(seq);
}
 
//************************************
// Method:    execute_sequence
// FullName:  cube::execute_sequence
// Access:    public 
// Returns:   void
// Qualifier:
// Parameter: const std::string & sequence
//
// Execute a move sequence
//************************************
void cube::execute_sequence(const std::string& sequence)
{
    if (sequence.length() == 0) return;
    std::vector<std::string> tokens;
    split(sequence, whitechars, tokens);
    execute_sequence(tokens);
}

//************************************
// Method:    execute_sequence
// FullName:  cube::execute_sequence
// Access:    public 
// Returns:   void
// Qualifier:
// Parameter: const std::vector<std::string> & tokens
//************************************
void cube::execute_sequence(const std::vector<std::string>& tokens)
{
    for (const auto &token : tokens)
        execute_move(token);
}
//************************************
// Method:    scramble_cube
// FullName:  cube::scramble_cube
// Access:    public 
// Returns:   void
// Qualifier:
// Parameter: const int scramble_count
//
// Scramble the cube
//************************************
void cube::scramble_cube(const int scramble_count = 1000)
{
    const auto temp = record_;
    record_ = false;

    const auto count = scramble_count;
    const auto num_moves = 18; 
    static const std::string directions(num_moves) =
    {
        "U", "Ui", "D", "Di", "L", "Li", "R", "Ri", "B", "Bi", "F", "Fi", 
        "Us", "Ds", "Ls", "Rs", "Fs", "Bs"
    };

    std::string sequence;
    for (auto scrambleloop = 0; scrambleloop < count; scrambleloop++)
    {
        const auto rnd = rand() % num_moves;
        sequence += directions(rnd) + ' ';
    }
    optimize_sequence(sequence, scramble_sequence_);
    execute_sequence(scramble_sequence_);
    record_ = temp;
}

//************************************
// Method:    get_optimized_moves
// FullName:  cube::get_optimized_moves
// Access:    public 
// Returns:   void
// Qualifier: const
// Parameter: std::string & moves
//
// get the optimized moves as a string
//************************************
void cube::get_optimized_moves(std::string& moves) const
{
    optimize_sequence(moves_, moves);
}

//************************************
// Method:    get_moves
// FullName:  cube::get_moves
// Access:    public 
// Returns:   void
// Qualifier: const
// Parameter: std::string & moves
//
// get the moves as a string
//************************************
void cube::get_moves(std::string& moves) const
{
    moves = moves_;
    trim(moves);
}

//************************************
// Method:    get_scramble_sequence
// FullName:  cube::get_scramble_sequence
// Access:    public 
// Returns:   void
// Qualifier: const
// Parameter: std::string & scramble_sequence
//
// get the scramble sequence as a string
//************************************
void cube::get_scramble_sequence(std::string& scramble_sequence) const
{
    scramble_sequence = scramble_sequence_;
}

//************************************
// Method:    get_moves
// FullName:  cube::get_moves
// Access:    public 
// Returns:   void
// Qualifier: const
// Parameter: char * buffer
// Parameter: size_t * sz
//
// get the moves as a c string
//************************************
void cube::get_moves(char* buffer, size_t* sz) const
{
    std::string moves;
    get_moves(moves);
    if (buffer != nullptr && *sz > moves.length())
        strcpy(buffer, moves.c_str());
    if (sz != nullptr)
        *sz = moves_.length() + 1;
}

//************************************
// Method:    get_optimized_moves
// FullName:  cube::get_optimized_moves
// Access:    public 
// Returns:   void
// Qualifier: const
// Parameter: char * buffer
// Parameter: size_t * sz
//
// get the moves made as a string that are optimized
//************************************
void cube::get_optimized_moves(char* buffer, size_t* sz) const
{
    std::string moves;
    get_optimized_moves(moves);
    if (buffer != nullptr && *sz > moves.length())
        strcpy(buffer, moves.c_str());
    if (sz != nullptr)
        *sz = moves_.length() + 1;
}

//************************************
// Method:    get_scramble_sequence
// FullName:  cube::get_scramble_sequence
// Access:    public 
// Returns:   void
// Qualifier: const
// Parameter: char * buffer
// Parameter: size_t * sz
//
// get the scramble sequence
//************************************
void cube::get_scramble_sequence(char* buffer, size_t* sz) const
{
    std::string scramble_sequence;
    get_scramble_sequence(scramble_sequence);
    if (buffer != nullptr && *sz > scramble_sequence.length())
        strcpy(buffer, scramble_sequence.c_str());
    if (sz != nullptr)
        *sz = scramble_sequence_.length() + 1;
}

//************************************
// Method:    optimize_sequence
// FullName:  cube::optimize_sequence
// Access:    public static 
// Returns:   void
// Qualifier:
// Parameter: const std::string & sequence
// Parameter: std::string & out
//
// optimize a sequence
//************************************
void cube::optimize_sequence(const std::string& sequence, std::string& out)
{
    auto temp = sequence;
    size_t len1;
    size_t len2;
    do
    {
        optimize_sequence_recursion(temp, out);
        len1 = temp.length();
        len2 = out.length();
        temp = out;
    } while (len2 < len1);
}

//************************************
// Method:    optimize_sequence_recursion
// FullName:  cube::optimize_sequence_recursion
// Access:    private static 
// Returns:   void
// Qualifier:
// Parameter: const std::string & sequence
// Parameter: std::string & out
//
// optimize a sequence
//
// The basic algorithm is to track 
// how many turns there are in a given direction
// and opposite direction while keeping track of which moves can be ignored.
// Optimize the ignored moves via recursion
//************************************
void cube::optimize_sequence_recursion(const std::string& sequence, std::string& out)
{
    out.clear();
    const std::string end_marker = "****";
    std::vector<std::string>tokens;

    const std::string white = "tnrvf ";
    split(sequence, white, tokens);
    tokens.emplace_back(end_marker);

    auto index = 0u;
    auto count = 0;

    std::vector<std::string> ignore;
    std::vector<std::string> add;
    std::vector<std::string> subtract;
    std::string search;
    std::string ig_string;
    std::string add_string;
    std::string subtract_string;

    // loop through all tokens
    while (index < tokens.size())
    {
        // get the current token
        auto tok = lower(tokens(index++));

        // new sequence
        // set add subtract and ignore vectors
        if (search.length() == 0)
        {
            count = 0;
            search = tok;
            ignore.clear();
            add.clear();
            subtract.clear();
            ig_string.clear();
            search = tok;
            add_string.clear();
            subtract_string.clear();

            // left and left inverse
            if (tok == "l" || tok == "li")
            {
                add.emplace_back("l");
                subtract.emplace_back("li");
                ignore.emplace_back("r");
                ignore.emplace_back("ri");
                ignore.emplace_back("ls");
                ignore.emplace_back("rs");
                add_string = "L";
                subtract_string = "Li";
            }
            // right and right inverse
            else if (tok == "r" || tok == "ri")
            {
                add.emplace_back("r");
                subtract.emplace_back("ri");
                ignore.emplace_back("l");
                ignore.emplace_back("li");
                ignore.emplace_back("ls");
                ignore.emplace_back("rs");
                add_string = "R";
                subtract_string = "Ri";
            }
            // front and front inverse
            else if (tok == "f" || tok == "fi")
            {
                add.emplace_back("f");
                subtract.emplace_back("fi");
                ignore.emplace_back("b");
                ignore.emplace_back("bi");
                ignore.emplace_back("fs");
                ignore.emplace_back("bs");
                add_string = "F";
                subtract_string = "Fi";
            }
            // back and back inverse
            else if (tok == "b" || tok == "bi")
            {
                add.emplace_back("b");
                subtract.emplace_back("bi");
                ignore.emplace_back("f");
                ignore.emplace_back("fi");
                ignore.emplace_back("fs");
                ignore.emplace_back("bs");
                add_string = "B";
                subtract_string = "Bi";
            }
            // up and up inverse
            else if (tok == "u" || tok == "ui")
            {
                add.emplace_back("u");
                subtract.emplace_back("ui");
                ignore.emplace_back("d");
                ignore.emplace_back("di");
                ignore.emplace_back("us");
                ignore.emplace_back("ds");
                add_string = "U";
                subtract_string = "Ui";
            }
            // down and down inverse
            else if (tok == "d" || tok == "di")
            {
                add.emplace_back("d");
                subtract.emplace_back("di");
                ignore.emplace_back("u");
                ignore.emplace_back("ui");
                ignore.emplace_back("us");
                ignore.emplace_back("ds");
                add_string = "D";
                subtract_string = "Di";
            }
            // cube up and cube down
            else if (tok == "cu" || tok == "cd")
            {
                add.emplace_back("cu");
                subtract.emplace_back("cd");
                add_string = "Cu";
                subtract_string = "Cd";
            }
            // cube left and cube right
            else if (tok == "cl" || tok == "cr")
            {
                add.emplace_back("cl");
                subtract.emplace_back("cr");
                add_string = "Cl";
                subtract_string = "Cr";
            }
            // up slice, up slice inverse, down slice and down slice inverse
            else if (tok == "us" || tok == "ds")
            {
                add.emplace_back("us");
                subtract.emplace_back("ds");
                ignore.emplace_back("u");
                ignore.emplace_back("ui");
                ignore.emplace_back("d");
                ignore.emplace_back("di");
                add_string = "Us";
                subtract_string = "Ds";
            }
            // left slice, left slice inverse, right slice and right slice inverse
            else if (tok == "ls" || tok == "rs")
            {
                add.emplace_back("ls");
                subtract.emplace_back("rs");
                ignore.emplace_back("l");
                ignore.emplace_back("li");
                ignore.emplace_back("r");
                ignore.emplace_back("ri");
                add_string = "Ls";
                subtract_string = "Rs";
            }
            // front slice, front slice inverse, back slice and back slice inverse
            else if (tok == "fs" || tok == "bs")
            {
                add.emplace_back("fs");
                subtract.emplace_back("bs");
                ignore.emplace_back("f");
                ignore.emplace_back("fi");
                ignore.emplace_back("b");
                ignore.emplace_back("bi");
                add_string = "Fs";
                subtract_string = "Bs";
            }
            else  // if (tok == end_marker)
            {
                add.emplace_back(tok);
            }
        }

        // At this point add, subtract and ignore vectors are set

        // add
        auto found = false;
        for (const auto& a : add)
        {
            if (tok == a)
            {
                count++;
                found = true;
                break;
            }
        }
        // subtract
        if (!found)
            for (const auto& s : subtract)
            {
                if (tok == s)
                {
                    count--;
                    found = true;
                    break;
                }
            }
        // ignore
        if (!found)
            for (const auto& i : ignore)
            {
                if (tok == i)
                {
                    ig_string += ' ' + tok;
                    found = true;
                    break;
                }
            }

        // check for end of sequence
        if (!found)
        {
            // recurse over ignore string
            if (ig_string.length() > 0)
            {
                std::string opt;
                optimize_sequence_recursion(ig_string, opt);
                if (opt.length() > 0)
                    out += ' ' + opt;
            }

            // the numbers of moves in any direction must be mod 4
            count %= 4;
            if (count > 0)
            {
                switch (count)
                {
                case 1:
                    out += ' ' + add_string;
                    break;
                case 2:
                    out += ' ' + add_string;
                    out += ' ' + add_string;
                    break;
                case 3:  // 3 add == 1 substract
                    out += ' ' + subtract_string;
                    break;
                default:
                    break;
                }
            }
            else if (count < 0)
            {
                switch (count)
                {
                case -1:
                    out += ' ' + subtract_string;
                    break;
                case -2: // 2 subtracts == 2 adds for simplicity
                    out += ' ' + add_string;
                    out += ' ' + add_string;
                    break;
                case -3: // 3 subtracts == 1 add
                    out += ' ' + add_string;
                    break;
                default:
                    break;
                }
            }
            // trigger a new sequence by clearing it
            search.clear();

            // move 1 token backwards
            index--;
        }
    }
    trim(out);
}

//************************************
// Method:    optimize_sequence
// FullName:  cube::optimize_sequence
// Access:    public static 
// Returns:   void
// Qualifier: const
// Parameter: const char * sequence
// Parameter: char * buffer
// Parameter: size_t * sz
//
// optimize a sequence
//************************************
void cube::optimize_sequence(const char* sequence, char* buffer, size_t* sz) const
{
    std::string out;
    optimize_sequence(sequence, out);
    if (buffer != nullptr && *sz > out.length())
        strcpy(buffer, out.c_str());
    if (sz != nullptr)
        *sz = out.length() + 1;
}

Unable to move / copy files from parent to child site

We have a site and a subsite and with the copy / move functions I can see the parent site from the subsite in the “Choose a destination” window, but not the other way around. From the parent site, I can’t see the child site. I am “following” both sites. I was unable to see the parent site from the child site until I changed the privacy setting for the site to public.

I know this has to be a simple setting somewhere but I can’t figure it out.

Thanks in advance for any help you can offer.

dnd 5e – Do opportunity attacks have disadvantage if I use my action to Dodge and then move out of melee range?

Dodge:

Until the start of your next turn, any attack roll made against you has disadvantage if you can see the attacker, and you make Dexterity saving throws with advantage.

Opportunity attacks require attack rolls, so they would all have disadvantage, as long as the attackers were not invisible.

That being said, I would recommend using disengage instead of dodge:

If you take the Disengage action, your movement doesn’t provoke opportunity attacks for the rest of the turn.

Now, instead of disadvantage, your opponents can’t attack at all.

Dodge and disengage both use an action. So you could do either of those for your action and still dash with your bonus action. So disengage would likely be better than dodge, unless there’s a possibility of getting attacked from range by other enemies, then dodge might be helpful for imposing disadvantage on those ranged attacks, unless you remember this clever trick: ranged attacks have disadvantage against a prone target.

You disengage with your action, then bonus action dash and get 60 feet away from your melee attackers. Then, if there are no more melee attackers within 30-40 feet of you, drop prone, and all ranged attacks will have disadvantage. When your turn comes back around, stand up and double dash for 75 feet of movement.

dnd 5e – When a UA Wild Soul barbarian’s Wild Surge feature conjures “intangible spirits” that fly 30 feet in a random direction, do they move through walls?

Yes

From the text you linked, it says

You conjure 1d4 intangible spirits that look like flumphs in unoccupied spaces within 30 feet of you. Each spirit immediately flies 30 feet in a random direction. At the end of your turn, all spirits explode and each creature within 5 feet of one or more of them must succeed on a Dexterity saving throw or take 2d8 force damage.

As far as I know, Intangible is not a in-game term, therefore, we should interpret it in plain English.

Intangible, from Google, says:

unable to be touched or grasped; not having physical presence.

Since it “can not be touched” and does not possess any physical form, there is no reason for them to be stopped by a wall or any other object. The fact that they are spirits also supports this interpretation. Again, simply “spirits” is not an in-game term, so, we should go with usual reading: spirits are incorporeal and can move through walls.

As another indicator of this intention, as V2Blast mentioned in the comments, there is no reason to even include the term “intangible” as a description to the spirits if it was not meant to state that they ignore physical obstructions (such as walls).

PS: I am reading your question as “can the spirits”, rather than “do the spirits”, which I believe is what you meant. Ultimately, what the spirits do, in fact, depends on the DM, but they certainly can move through walls.

dnd 5e – Do the conjured “intangible spirits” that randomly move 30 feet in the Path of the Wild Soul (UA) move though walls?

Yes

From the text you linked, it says

You conjure 1d4 intangible spirits that look like flumphs in unoccupied spaces within 30 feet of you. Each spirit immediately flies 30 feet in a random direction. At the end of your turn, all spirits explode and each creature within 5 feet of one or more of them must succeed on a Dexterity saving throw or take 2d8 force damage.

As far as I know, Intangible is not a in-game term, therefore, we should interpret it in plain English.

Intangible, from Google, says:

unable to be touched or grasped; not having physical presence.

Since it “can not be touched” and does not possess any physical form, there is no reason for them to be stopped by a wall or any other object. The fact that they are spirits also supports this interpretation. Again, simply “spirits” is not an in-game term, so, we should go with usual reading: spirits are incorporeal and can move through walls.

PS: I am reading your question as “can the spirits”, rather than “do the spirits”, which I believe is what you meant. Ultimately, what the spirits do, in fact, depends on the DM, but they certainly can move through walls.

backup – Possible to recover app data from .asec file (SD card in Samsung) and move to new device (OnePlus)?

I have been using an Android app since 2014. Recently, I switched to a new device (OnePlus) and realized that the data backup was only till 2018. It was narrowed down to the data being stored in the old device’s (Samsung) SD card. That is a challenge as the new device doesn’t have an SD card slot. Now, I got a memory card reader, connected with my laptop, and transferred all the data but this.

New challenge:

  1. There is no “.pc” file (default extension for the backup file) in the SD card’s folder. This folder is empty and I couldn’t identify any hidden files – Androiddatacom.popularapp.appName.
  2. In .android_secure folder, we have this “.asec” file, which may or may not be useful.

Here are my questions:

  1. W.r.t. #1 above, am I missing something? Should I look for the backup/data somewhere else ?
  2. W.r.t. #2 above, since it is .asec file and the app needs .pc file for the backup (otherwise it throws error code 2116), is there a way to use this file to get the data for the missing time period ?

The app name is Period Tracker (https://play.google.com/store/apps/details?id=com.popularapp.periodcalendar)

If this is not the correct platform to ask the question, please direct me to the right one.
Any help would be appreciated. Let me know if further information is required. Thanks!