如何使用这个预先计算的查找表来创建多联骨牌的 1 对 1 映射?

问题描述 投票:0回答:1

本周早些时候,我询问了如何加快检查位板是否包含多联骨牌的方法。一位用户表明,使用查找表可以更快地计算 0 岛的数量(我认为大多数人都使用 1,但我现有的很多逻辑都使用 0)。尝试了一段时间后,我开始思考:是否可以直接使用查找表?例如,如果我给它 100,它会给我第 100 个多联骨牌。因为现在我正在每个 ulong 上运行一个 for 循环并检查每个 ulong 直到找到我正在寻找的内容。多骨牌之间的差距可能非常大,最大的差距约为 4 万亿分之一。因此,最好跳过我不需要的内容,只关注多联骨牌。我在评论中询问了用户,他们说这是可能的,他们说是的,除非我误解了他们,所以我创建了这篇文章。当然,欢迎任何人回答。

这是上一篇文章中创建查找表的代码。

/// <summary>
/// Map from the long-form state code for each state to the state number
/// see expandState.
/// 
/// This is only used during state table construction.
/// </summary>
Dictionary<string, ushort> stateNumbers = new Dictionary<string, ushort>();

/// <summary>
/// Packed map from (state*256 + next_row_byte) -> transition
/// 
/// transition is next_state + (island_count<<12), where island_count is the
/// number of islands cut off from the further rows
/// </summary>
List<ushort> transitions = new List<ushort>();

/// <summary>
/// The byte representing a row of all water.  Note that this code counts
/// 0-islands, not 1-islands
/// </summary>
const byte ALL_WATER = (byte)0xFF;

#region Disjoint Set
/*
 * Disjoint set the proper way.  The sets are integers in an array:
 * For each integer i
 *   - i === 0 => set is uninitialized (not yet a set)
 *   - i < 0 => set is a link to ~i
 *   - i >= 0 => set is of size i
 */

// find with path compression.
int find(int[] sets, int s)
{
    int parent = sets[s];
    if (parent > 0)
    {
        return s;
    }
    else if (parent < 0)
    {
        parent = find(sets, ~parent);
        sets[s] = ~parent;
        return parent;
    }
    else
    {
        sets[s] = 1;
        return s;
    }
}

// union by size
bool union(int[] sets, int x, int y)
{
    x = find(sets, x);
    y = find(sets, y);
    if (x == y)
    {
        return false;
    }
    int szx = sets[x];
    int szy = sets[y];
    if (szx < szy)
    {
        sets[y] += szx;
        sets[x] = ~y;
    }
    else
    {
        sets[x] += szy;
        sets[y] = ~x;
    }
    return true;
}

#endregion


/// <summary>
/// Expands the specified state code.
/// 
/// A state code is a string of digits.
///  0 => water
///  x => island number x.  new islands are numbered from left to right
/// </summary>
/// <param name="stateCode">The state code to expand.</param>
/// <param name="nextrow">the lower 8 bits represent the next row.  0-bits are land</param>
/// <returns>The transition code for the transition from stateCode to nextrow</returns>
ushort expandState(string stateCode, int nextrow)
{
    // convert the next row into a disjoint set array
    // if you want to count 1-islands instead of 0-islands, change `~nextrow` into `nextrow` below,
    // and fix the ALL_WATER constant
    int[] sets = new int[8];
    for (int i = 0; i < 8; ++i)
    {
        sets[i] = (~nextrow >> i) & 1;
    }
    for (int i = 0; i < 7; ++i)
    {
        if (((~nextrow >> i) & 3) == 3)
        {
            union(sets, i, i + 1);
        }
    }
    // map from state code island to first attached set in sets
    int[] links = [-1, -1, -1, -1, -1, -1, -1, -1];
    int topIslandCount = 0;
    for (int i = 0; i < 8; ++i)
    {
        char digit = stateCode[i];
        int topisland = digit - '1';
        topIslandCount = Math.Max(topIslandCount, topisland + 1);
        if (sets[i] != 0 && topisland >= 0)
        {
            // connection from prev row to nextrow
            int bottomSet = links[topisland];
            if (bottomSet < 0)
            {
                // this island is not yet connected
                links[topisland] = i;
            }
            else
            {
                // the top island is already connected. union bottom sets
                union(sets, bottomSet, i);
            }
        }
    }

    // count the number of top-row islands that don't connect to the next row.
    int cutOffCount = 0;
    for (int i = 0; i < topIslandCount; ++i)
    {
        if (links[i] < 0)
        {
            ++cutOffCount;
        }
    }

    // turn the new union-find array into a state code
    char nextSet = '1';
    char[] newChars = "00000000".ToCharArray();
    for (int i = 0; i < 8; ++i)
    {
        links[i] = -1;
    }
    for (int i = 0; i < 8; ++i)
    {
        if (sets[i] != 0)
        {
            int set = find(sets, i);
            int link = links[set];
            if (link >= 0)
            {
                newChars[i] = newChars[link];
            }
            else
            {
                newChars[i] = nextSet++;
                links[set] = i;
            }
        }
    }
    string newStateCode = new string(newChars);

    // get the state number
    if (stateNumbers.ContainsKey(newStateCode))
    {
        // state already exists and is/will be expanded
        return (ushort)(stateNumbers[newStateCode] | (cutOffCount << 12));
    }
    ushort newState = (ushort)stateNumbers.Count;
    stateNumbers.Add(newStateCode, newState);

    // fill out the state table
    while (transitions.Count <= (newState + 1) * 256)
    {
        transitions.Add(0);
    }
    for (int i = 0; i < 256; ++i)
    {
        transitions[newState * 256 + i] = expandState(newStateCode, i);
    }
    return (ushort)(newState | (cutOffCount << 12));
}

int startState = expandState("00000000", ALL_WATER);

Console.WriteLine(startState);
Console.WriteLine(stateNumbers.Count);

int Count0Islands(ulong bitboard)
{
    int state = 0;
    int count = 0;
    for (int i = 0; i < 8; ++i)
    {
        var transition = transitions[state * 256 + (int)(bitboard & 0xFF)];
        count += transition >> 12;
        state = transition & 0xFFF;
        bitboard >>= 8;
    }
    // transition to ALL_WATER to count last islands
    count += transitions[state * 256 + ALL_WATER] >> 12;
    return count;
}

ulong[] tests = {
    0x7e220a7e4a58580Ful,
    0x7e22087e4a58580Ful,
    0xFFFFFFFFFFFFFFFFul,
    0x813c425a5a423c81ul
};

foreach (ulong test in tests)
{
    Console.WriteLine();
    Console.WriteLine();
    for (int row = 0; row < 8; ++row)
    {
        int rowByte = (int)(test >> (row * 8)) & 0xFF;
        string rowStr = Convert.ToString(rowByte, 2).PadLeft(8, '0');
        rowStr = rowStr.Replace("1", " ");
        rowStr = rowStr.Replace("0", "#");
        Console.WriteLine(rowStr);
    }
    Console.WriteLine();
    Console.WriteLine("Islands: " + Count0Islands(test));
}
c# algorithm
1个回答
0
投票

我上次为您制作的表格是一个有限状态转换器,它处理行位掩码并返回岛计数。

这并不完全是您所需要的。 为了生成第 N 个 Polymino 位板,我们需要:

  1. 使用该 FST 生成 DFA(确定性有限自动机),该 DFA 与形成多项式的 8 行位掩码序列相匹配;
  2. 对于每个州,计算从该州到达有效多米诺骨牌的总方式数。 我们可以用 DFS 来做到这一点,因为我们的状态机保证是非循环的,因为它接受的所有字符串的长度是有限的;
  3. 以邻接列表(数组)格式而不是转换矩阵格式存储转换;和
  4. 在每个转换上,记录其目标状态的累积总路数以及数组中早期转换的累积总路数。 给定我们想要走的路的索引,这允许我们进行二分搜索来确定适当的转换。

如果我们从具有多项式数 N 的起始状态开始,我们可以沿着第 N 条路径下降到接受状态的 8 个转换。 然后可以使用这些转换上的 8 行位掩码来构造第 N 个多米诺。

您可以将以下内容添加到您/我现有的 FST 生成器中,以制作多米诺生成器表并生成多米诺。

这个表有点大,有 5126 个状态,652583 个转换,每个转换大约 16 个字节,所以大约 10MB。

/// <summary>
/// checker_state + next_row_number * 4096 + have_islands*65536 -> generator_state
/// </summary>
Dictionary<int, ushort> genStateNumbers = new Dictionary<int, ushort>();
/// <summary>
/// Generator states.  These for a DFA that accepts all 8-row polyminoes.
/// State 0 is used as both the unique start state and the unique accept state
/// </summary>
List<List<GenTransitionInfo>> genStates = new List<List<GenTransitionInfo>>();

/// <summary>
/// Fill out a state in the generator table if it doesn't exist
/// Return the state number
/// </summary>
ushort makeGenState(int nextRowNumber, int checkerState, int haveIslands) {
    int stateKey = checkerState + nextRowNumber * 4096 + haveIslands * 65536;
    if (genStateNumbers.ContainsKey(stateKey)) {
        return genStateNumbers[stateKey];
    }
    ushort newGenState = (ushort)genStates.Count;
    genStateNumbers.Add(stateKey, newGenState);
    var tlist = new List<GenTransitionInfo>();
    genStates.Add(tlist);
    int transitionsOffset = checkerState*256;
    ulong totalPaths = 0;
    for (int i = 0; i < 256; ++i) {
        var transition = transitions[transitionsOffset + i];
        int nextCheckerState = transition & 0x0FFF;
        var newIslands = (transition >> 12) + haveIslands;
        if (newIslands > (i == ALL_WATER ? 1 : 0)) {
            // we are destined to get too many islands this way.
            continue;
        }
        if (nextRowNumber == 7) {
            // all transitions for row 7 have to to the accept state
            // calculate total number of islands
            newIslands += transitions[nextCheckerState * 256 + ALL_WATER] >> 12;
            if (newIslands == 1) {
                totalPaths += 1;
                tlist.Add(new GenTransitionInfo { nextRow = (byte)i, nextState = 0, cumulativePaths = totalPaths });
            }
        } else {
            ushort nextGenState = makeGenState(nextRowNumber + 1, nextCheckerState, newIslands);
            ulong newPaths = genStates[nextGenState].LastOrDefault().cumulativePaths;
            if (newPaths > 0) {
                totalPaths += newPaths;
                tlist.Add(new GenTransitionInfo { nextRow = (byte)i, nextState = nextGenState, cumulativePaths = totalPaths });
            }
        }
    }
    return newGenState;
}

// generate the DFA
makeGenState(0, startState, 0);
ulong TOTAL_POLYMINOES = genStates[0].LastOrDefault().cumulativePaths;

ulong getNthPolimyno(ulong n) {
    int state = 0;
    ulong poly = 0;
    for (int row=0; row < 8; ++row) {
        var tlist = genStates[state];
        // binary search to find the transition that contains the nth path
        int hi = tlist.Count - 1;
        int lo = 0;
        while (hi > lo) {
            int test = (lo + hi) >> 1;
            if (n >= tlist[test].cumulativePaths) {
                // test is too low
                lo = test + 1;
            } else {
                // test is high enough
                hi = test;
            }
        }
        if (lo > 1) {
            n -= tlist[lo - 1].cumulativePaths;
        }
        var transition = tlist[lo];
        poly = (poly<<8) | transition.nextRow;
        state = transition.nextState;
    }
    return poly;
}


// TEST

Console.WriteLine("Total Polyminoes: " + TOTAL_POLYMINOES);
Console.WriteLine("Total Generator States: " + genStates.Count);
Console.WriteLine("Total Transitions: " + genStates.Sum(x => x.Count));

Random rand = new Random();
for (int i=0; i < 10; ++i) {
    // Genterate a random ulong from 0 to TOTAL_POLYMINOES
    ulong n = (ulong)rand.NextInt64((long) TOTAL_POLYMINOES);
    ulong poly = getNthPolimyno(n);
    Console.WriteLine();
    Console.WriteLine("Polymino " + n + " is:");
    for (int row = 0; row < 8; ++row)
    {
        int rowByte = (int)(poly >> (row * 8)) & 0xFF;
        string rowStr = Convert.ToString(rowByte, 2).PadLeft(8, '0');
        rowStr = rowStr.Replace("1", " ");
        rowStr = rowStr.Replace("0", "#");
        Console.WriteLine(rowStr);
    }
}


struct GenTransitionInfo {
    public byte nextRow;
    public ushort nextState;
    public ulong cumulativePaths;
}

包含测试代码,并产生如下输出:

Total Polyminoes: 51016818604894742
Total Generator States: 5126
Total Transitions: 652583

Polymino 42260088568217858 is:
####### 
  # #   
#   ####
######  
  # ####
 #### ##
  ### # 
  # # # 

Polymino 17307633173140022 is:
# # ####
#####  #
# #  # #
# # ####
    ####
    # # 
###  ###
# #### #

请注意,总共有 51016818604894742 个不同的 64 位位板多米诺。 得到这个数字是我遇到这些麻烦的真正原因:)

您可以搜索这个号码,看看它以前出现在哪里!

© www.soinside.com 2019 - 2024. All rights reserved.