如何使用 sqlite 获取头的最后 运行 的长度

How to get the length of the last run of heads using sqlite

我正在编写一个游戏,其中的游戏玩法取决于从序列中反复找到最后一个 运行 的连续值。玩家被随机选择抛硬币,从而导致一系列的硬币抛掷。例如,如果随机选择爱丽丝和鲍勃抛硬币,他们可能会产生以下正面 (H) 和反面 (T) 序列。

player toss
-------------
Bob      H
Bob      T
Alice    T
Bob      T
Bob      H
Bob      H
Alice    T
Alice    T
Bob      H
Alice    H

一个run在这里被定义为一系列连续的头值或尾值。 Alice 的最后 运行 头的长度是 1。对于 Bob,最后的 运行 头的长度是 3.

主要问题:

一个。是否有 SQLite 查询来查找特定玩家最后 运行 个正面的长度?

这个问题类似于SQL query for run-length, or consecutive identical value encoding,但在这种情况下只需要最后运行个头像的长度。

我目前获取特定玩家的结果,使用时间戳按降序排列结果,然后计算连续正面的数量以获得该玩家最后 运行 个正面的长度.这是来自我的 Room DAO 的解决方案的 SQLite 部分,它为玩家获取结果:

@Query("SELECT toss FROM outcome WHERE player = :player ORDER BY auto_timestamp DESC")
List<Boolean> getOutcomes(String player);

使用 Java 循环计算连续头部的数量似乎效率低下,让整数最后 运行 长度由 SQLite 查询生成会更清晰。

两个次要问题:

获取玩家最后 运行 头的长度用于解决另外两个问题,目前在 Java 中实现:

乙。找出球员子集合的头的最小最后 运行 长度。使用 Alice 和 Bob 的数据,如果玩家的子集合是 {Alice, Bob},那么最小的 last 运行 长度是 1,Alice 的 运行 长度。

摄氏度。从给定的玩家子集合中找到具有 last heads 运行 长度小于整数m。如果 m 为 2 或 3,则返回 {Alice},因为她的最后 运行 长度为 1。如果 m 为 4,则返回 {Alice, Bob}。

另外,你知道有没有解决B和C的SQLite查询?

A. Is there an SQLite query to find the length of the last run of heads for a particular player?

您可以编写一个查询,尽管如果要满足大范围的 Android API's/versions.

会相当复杂

例如下面的查询将 return 最后一个 运行 并且适用于大多数 Android 版本 :-

WITH
    /* CTE for the player - so only need to replce/bind once */
    cte_player(p) AS (
        SELECT 'Bob' /*<<<<<<<<<< change as necessary */
    ),
    cte_toss(t) AS (
        SELECT 'H'   /*<<<<<<<<<< change as/if necessary */
    ),
    /* get the base outcome to start from i.e. the latest row for the player */
    cte_base_outcome AS (
        SELECT auto_timestamp, toss,player 
        FROM outcome 
        WHERE player = (SELECT p FROM cte_player) 
            AND toss = (SELECT t FROM cte_toss) 
        ORDER BY auto_timestamp DESC 
        LIMIT 1
    ), 
    /* The recursive CTE */
    cte1(auto_timestamp,toss,player) AS (
        SELECT auto_timestamp,toss, player 
        FROM cte_base_outcome
        UNION ALL SELECT
            (
                SELECT auto_timestamp 
                FROM outcome  
                WHERE outcome.player = (SELECT p FROM cte_player) 
                    AND outcome.auto_timestamp < cte1.auto_timestamp
                    AND outcome.toss = (SELECT t FROM cte_toss)
                ORDER BY auto_timestamp 
                DESC LIMIT 1
            ),
            (
                SELECT toss 
                FROM outcome  
                WHERE outcome.player = (SELECT p FROM cte_player) 
                    AND outcome.auto_timestamp < cte1.auto_timestamp 
                ORDER BY auto_timestamp 
                DESC LIMIT 1
            ),
            (
                SELECT player 
                FROM outcome  
                WHERE outcome.player = (SELECT p FROM cte_player) 
                    AND outcome.auto_timestamp < cte1.auto_timestamp 
                ORDER BY auto_timestamp DESC 
                LIMIT 1
            )
            FROM cte1 WHERE toss = (SELECT t FROM cte_toss) LIMIT 10
    )
SELECT count() AS result 
FROM cte1 
WHERE toss = (SELECT t FROM cte_toss);

Additionally, do you know if there are SQLite queries that solve B and C.?

理解了以上内容后,您就可以继续解决 B 和 C 了。

您不妨参考https://sqlite.org/lang_with.html

以下代码用于测试以上内容:-

DROP TABLE IF EXISTS outcome;
CREATE TABLE IF NOT EXISTS outcome (player TEXT, toss TEXT, auto_timestamp);

INSERT INTO outcome VALUES 
    ('Alice','H',10),
    ('Bob','H',9),
    ('Alice','T',8),
    ('Alice','T',7),
    ('Bob','H',6),
    ('Bob','H',5),
    ('Bob','T',4),
    ('Alice','T',3),
    ('Bob','T',2),
    ('Bob','H',1);

WITH
    /* CTE for the player - so only need to replce/bind once */
    cte_player(p) AS (
        SELECT 'Bob' /*<<<<<<<<<< change as necessary */
    ),
    cte_toss(t) AS (
        SELECT 'H'   /*<<<<<<<<<< change as/if necessary */
    ),
    /* get the base outcome to start from i.e. the latest row for the player */
    cte_base_outcome AS (
        SELECT auto_timestamp, toss,player 
        FROM outcome 
        WHERE player = (SELECT p FROM cte_player) 
            AND toss = (SELECT t FROM cte_toss) 
        ORDER BY auto_timestamp DESC 
        LIMIT 1
    ), 
    /* The recursive CTE */
    cte1(auto_timestamp,toss,player) AS (
        SELECT auto_timestamp,toss, player 
        FROM cte_base_outcome
        UNION ALL SELECT
            (
                SELECT auto_timestamp 
                FROM outcome  
                WHERE outcome.player = (SELECT p FROM cte_player) 
                    AND outcome.auto_timestamp < cte1.auto_timestamp
                    AND outcome.toss = (SELECT t FROM cte_toss)
                ORDER BY auto_timestamp 
                DESC LIMIT 1
            ),
            (
                SELECT toss 
                FROM outcome  
                WHERE outcome.player = (SELECT p FROM cte_player) 
                    AND outcome.auto_timestamp < cte1.auto_timestamp 
                ORDER BY auto_timestamp 
                DESC LIMIT 1
            ),
            (
                SELECT player 
                FROM outcome  
                WHERE outcome.player = (SELECT p FROM cte_player) 
                    AND outcome.auto_timestamp < cte1.auto_timestamp 
                ORDER BY auto_timestamp DESC 
                LIMIT 1
            )
            FROM cte1 WHERE toss = (SELECT t FROM cte_toss) LIMIT 10
    )
SELECT count() AS result 
FROM cte1 
WHERE toss = (SELECT t FROM cte_toss);
/* Cleanup the Testing Environment */
DROP TABLE IF EXISTS outcome;

Bob 和 H 结果是:-

Alice 和 H 结果是:-

对于 Bob 和 T,结果是:-

最后是 Alice 和 T :-

要在 Room 中使用,则 SQL 将被放入 @Query 中,您将指定 function/method return 类型的 Int/int 或 Long/long(单列 return 值不需要 POJO/Entity class)。

例如:-

@Query("WITH " +
        "/* CTE for the player - so only need to replace/bind once */ " +
        " cte_player(p) AS ( SELECT :player)," +
        " cte_toss(t) AS (SELECT :toss)," +
        "/* get the base outcome to start from i.e. the latest row for the player */" +
        " cte_base_outcome AS (" +
        "   SELECT auto_timestamp, toss,player " +
        "   FROM outcome " +
        "   WHERE player = (SELECT p FROM cte_player) " +
        "       AND toss = (SELECT t FROM cte_toss) " +
        "   ORDER BY auto_timestamp DESC " +
        "   LIMIT 1" +
        ")," +
        "/* The recursive CTE */" +
        " cte1(auto_timestamp,toss,player) AS (" +
        "   SELECT auto_timestamp,toss, player FROM cte_base_outcome " +
        "   UNION ALL SELECT(SELECT auto_timestamp " +
        "   FROM outcome " +
        "   WHERE outcome.player = (SELECT p FROM cte_player) " +
        "       AND outcome.auto_timestamp < cte1.auto_timestamp " +
        "       AND outcome.toss = (SELECT t FROM cte_toss) " +
        "   ORDER BY auto_timestamp DESC " +
        "   LIMIT 1" +
        "   )," +
        "   (" +
        "       SELECT toss " +
        "       FROM outcome " +
        "       WHERE outcome.player = (SELECT p FROM cte_player) " +
        "       AND outcome.auto_timestamp < cte1.auto_timestamp " +
        "       ORDER BY auto_timestamp DESC " +
        "       LIMIT 1" +
        "   )," +
        "   (" +
        "       SELECT player " +
        "       FROM outcome " +
        "       WHERE outcome.player = (SELECT p FROM cte_player) " +
        "           AND outcome.auto_timestamp < cte1.auto_timestamp " +
        "       ORDER BY auto_timestamp DESC " +
        "       LIMIT 1" +
        "   ) " +
        "   FROM cte1 " +
        "   WHERE toss = (SELECT t FROM cte_toss) " +
        "   LIMIT 10" +
        ") " +
        "SELECT count() AS result " +
        "FROM cte1 " +
        "WHERE toss = (SELECT t FROM cte_toss);")
abstract fun getLastRun(player: String, toss: String): Long
  • 注意 LIMIT 10 不应该是必需的,但显然是一种故障保护,您可能希望将其增加到合适的值或将其排除。它限制了递归的次数。但是,在 LIMIT 1 已编码的情况下,只需要获取相应的 1 行即可。