sqlite 将字符串拆分成等长段的有效方法?

tyky79it  于 12个月前  发布在  SQLite
关注(0)|答案(1)|浏览(166)

在SQLite和/或Tcl中,有没有一种快速的方法可以将一个字符串分割成等长的段?
我的目标是在内存中获取一个字符串,并将拆分写入表中的行。有时字符串通过通道传递,有时它是从SQLite表中已经保存的片段连接起来的。
我一直在使用substr的CTE,我能得到的最好的结果是大约1.6秒,一个大约1 MB的字符串变成4216个/行。
最简单的拆分尝试示例是

select
   sg.value, substr(:textContent, sg.value, 250)
from
   generate_series(1, :tcLen, 250) sg
;

字符串
使用group_concat组合表中的4216个片段只需要0.129秒,并且每行都包含一个substr来选择每行的全部或部分字符串。
感谢您提供的任何指导。
编辑:
我现在看到这个answertcllib中提到了:textutil::spit::splitn。将给予尝试;应该想到在那里查找,但假设SQL会更快。
使用下面的文本工具并插入循环中,将1.6秒减少到大约0.26秒。

foreach row [::textutil::split::splitn $textContent $bufferRowMaxLength] {
  set charLength [expr {min([string length $row], $bufferRowMaxLength)}]
  db eval {
    insert into mem.pt_buffers values ( ... );
    insert into mem.pt_pointer values ( ... );
  }
}

jgzswidk

jgzswidk1#

textutil::split::splitn非常快,尽管它只是该解决方案的明显Tcl脚本,比我用c所能做的最好的慢2倍。不应该太令人惊讶,Tcl真的很擅长处理字符串。
我尝试的一个替代方法(基于Donal多年前分享的一个技巧)是用regexp分块:

set chunks [regexp -all -inline .{1,$bufferMaxRowLength} $textContent]

字符串
但这是令人惊讶的慢。
以下是我的所有结果:

package require textutil
package require jitc 0.5.3

set str [string repeat a 1048576]

set bufferRowMaxLength  250

set split_cdef {
    code {
        #include <string.h>

        OBJCMD(split) {
            int         code = TCL_OK;
            Tcl_Obj**   chunkobjs = NULL;
            Tcl_Obj*    res = NULL;

            enum {A_cmd, A_STR, A_CHUNK, A_objc};
            CHECK_ARGS_LABEL(finally, code, "str chunk");
            int len;
            const char* str = Tcl_GetStringFromObj(objv[A_STR], &len);
            int chunk;
            TEST_OK_LABEL(finally, code, Tcl_GetIntFromObj(interp, objv[A_CHUNK], &chunk));
            const int chunks = (len + chunk - 1) / chunk;
            chunkobjs = ckalloc(chunks * sizeof(chunkobjs[0]));
            for (int i=0; i<chunks; i++) {
                const int start = i * chunk;
                const int end = start + chunk;
                const int clen = end < len ? chunk : len - start;
                chunkobjs[i] = Tcl_NewStringObj(str + start, clen);
            }
            replace_tclobj(&res, Tcl_NewListObj(chunks, chunkobjs));
            Tcl_SetObjResult(interp, res);

        finally:
            replace_tclobj(&res, NULL);
            if (chunkobjs) ckfree(chunkobjs);
            return code;
        }

        OBJCMD(foreach_chunk) {
            int         code = TCL_OK;

            enum {A_cmd, A_STR, A_CHUNK, A_VAR, A_SCRIPT, A_objc};
            CHECK_ARGS_LABEL(finally, code, "str chunk var script");
            int len;
            const char* str = Tcl_GetStringFromObj(objv[A_STR], &len);
            Tcl_WideInt chunk;
            TEST_OK_LABEL(finally, code, Tcl_GetWideIntFromObj(interp, objv[A_CHUNK], &chunk));
            const int chunks = (len + chunk - 1) / chunk;
            for (int i=0; i<chunks; i++) {
                const int start = i * chunk;
                const int end = start + chunk;
                const int clen = end < len ? chunk : len - start;
                if (NULL == Tcl_ObjSetVar2(interp, objv[A_VAR], NULL, Tcl_NewStringObj(str + start, clen), TCL_LEAVE_ERR_MSG)) {
                    code = TCL_ERROR;
                    goto finally;
                }
                TEST_OK_LABEL(finally, code, Tcl_EvalObjEx(interp, objv[A_SCRIPT], 0));
            }

        finally:
            return code;
        }
    }
}

jitc::bind jitc_split         $split_cdef split
jitc::bind jitc_foreach_chunk $split_cdef foreach_chunk

set chunk   .{1,$bufferRowMaxLength}

puts "regexp: [timerate {
    set pieces [llength [regexp -all -inline $chunk $str]]
}], pieces: $pieces"

puts "splitn: [timerate {
    set pieces  [llength [::textutil::split::splitn $str $bufferRowMaxLength]]
}], pieces: $pieces"

puts "jitc split: [timerate {
    set pieces [llength [jitc_split $str $bufferRowMaxLength]]
}], pieces: $pieces"

puts "jitc foreach_chunk: [timerate {
    set pieces  0
    jitc_foreach_chunk $str $bufferRowMaxLength chunk {
        incr pieces
    }
}], pieces: $pieces"
regexp: 348330.7 µs/# 3 # 2.871 #/sec 1044.992 net-ms, pieces: 4195
splitn: 567.176 µs/# 1764 # 1763.1 #/sec 1000.498 net-ms, pieces: 4195
jitc split: 235.065 µs/# 4255 # 4254.1 #/sec 1000.201 net-ms, pieces: 4195
jitc foreach_chunk: 463.043 µs/# 2160 # 2159.6 #/sec 1000.173 net-ms, pieces: 4195

的数据
regexp的实现比其他实现慢得多,这表明它的实现还有一些改进的空间,一些不明显的低效率。
但对我来说,只要你有用块内容组装Tcl列表的约束,那么::textutil::split::splitn(或Tcl的等效几行)可能是正确的方法。在我的书中,c实现增加的复杂性并不足以证明自己,而且几乎可以肯定的是,对块本身所做的任何工作都会完全抵消这些好处。
请注意,c实现在这里并不等同于Tcl或regexp实现--它们分割成$bufferMaxRowLength字节的块,而不是字符。(而不是bytearray,在这种情况下,实现是等效的),则c实现产生的块是表示CESU-8编码中的字符串的字节块(类似UTF-8,但将空字节编码为0xC 0 0x 80,并对BMP之外的字符使用代理对)。这些字节块边界可能会分割单个字符的编码,因此块本身不会是字符串的有效编码。它们的目的是作为计算此结果所需的最小工作量的替代,任何按字符拆分的实现都必须完成它们的工作,并考虑字符边界,因此它们作为工作的下限估计是有效的。

相关问题