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

tyky79it  于 2023-11-21  发布在  SQLite
关注(0)|答案(1)|浏览(223)

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

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

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

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

jgzswidk

jgzswidk1#

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

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

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

  1. package require textutil
  2. package require jitc 0.5.3
  3. set str [string repeat a 1048576]
  4. set bufferRowMaxLength 250
  5. set split_cdef {
  6. code {
  7. #include <string.h>
  8. OBJCMD(split) {
  9. int code = TCL_OK;
  10. Tcl_Obj** chunkobjs = NULL;
  11. Tcl_Obj* res = NULL;
  12. enum {A_cmd, A_STR, A_CHUNK, A_objc};
  13. CHECK_ARGS_LABEL(finally, code, "str chunk");
  14. int len;
  15. const char* str = Tcl_GetStringFromObj(objv[A_STR], &len);
  16. int chunk;
  17. TEST_OK_LABEL(finally, code, Tcl_GetIntFromObj(interp, objv[A_CHUNK], &chunk));
  18. const int chunks = (len + chunk - 1) / chunk;
  19. chunkobjs = ckalloc(chunks * sizeof(chunkobjs[0]));
  20. for (int i=0; i<chunks; i++) {
  21. const int start = i * chunk;
  22. const int end = start + chunk;
  23. const int clen = end < len ? chunk : len - start;
  24. chunkobjs[i] = Tcl_NewStringObj(str + start, clen);
  25. }
  26. replace_tclobj(&res, Tcl_NewListObj(chunks, chunkobjs));
  27. Tcl_SetObjResult(interp, res);
  28. finally:
  29. replace_tclobj(&res, NULL);
  30. if (chunkobjs) ckfree(chunkobjs);
  31. return code;
  32. }
  33. OBJCMD(foreach_chunk) {
  34. int code = TCL_OK;
  35. enum {A_cmd, A_STR, A_CHUNK, A_VAR, A_SCRIPT, A_objc};
  36. CHECK_ARGS_LABEL(finally, code, "str chunk var script");
  37. int len;
  38. const char* str = Tcl_GetStringFromObj(objv[A_STR], &len);
  39. Tcl_WideInt chunk;
  40. TEST_OK_LABEL(finally, code, Tcl_GetWideIntFromObj(interp, objv[A_CHUNK], &chunk));
  41. const int chunks = (len + chunk - 1) / chunk;
  42. for (int i=0; i<chunks; i++) {
  43. const int start = i * chunk;
  44. const int end = start + chunk;
  45. const int clen = end < len ? chunk : len - start;
  46. if (NULL == Tcl_ObjSetVar2(interp, objv[A_VAR], NULL, Tcl_NewStringObj(str + start, clen), TCL_LEAVE_ERR_MSG)) {
  47. code = TCL_ERROR;
  48. goto finally;
  49. }
  50. TEST_OK_LABEL(finally, code, Tcl_EvalObjEx(interp, objv[A_SCRIPT], 0));
  51. }
  52. finally:
  53. return code;
  54. }
  55. }
  56. }
  57. jitc::bind jitc_split $split_cdef split
  58. jitc::bind jitc_foreach_chunk $split_cdef foreach_chunk
  59. set chunk .{1,$bufferRowMaxLength}
  60. puts "regexp: [timerate {
  61. set pieces [llength [regexp -all -inline $chunk $str]]
  62. }], pieces: $pieces"
  63. puts "splitn: [timerate {
  64. set pieces [llength [::textutil::split::splitn $str $bufferRowMaxLength]]
  65. }], pieces: $pieces"
  66. puts "jitc split: [timerate {
  67. set pieces [llength [jitc_split $str $bufferRowMaxLength]]
  68. }], pieces: $pieces"
  69. puts "jitc foreach_chunk: [timerate {
  70. set pieces 0
  71. jitc_foreach_chunk $str $bufferRowMaxLength chunk {
  72. incr pieces
  73. }
  74. }], pieces: $pieces"
  1. regexp: 348330.7 µs/# 3 # 2.871 #/sec 1044.992 net-ms, pieces: 4195
  2. splitn: 567.176 µs/# 1764 # 1763.1 #/sec 1000.498 net-ms, pieces: 4195
  3. jitc split: 235.065 µs/# 4255 # 4254.1 #/sec 1000.201 net-ms, pieces: 4195
  4. 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之外的字符使用代理对)。这些字节块边界可能会分割单个字符的编码,因此块本身不会是字符串的有效编码。它们的目的是作为计算此结果所需的最小工作量的替代,任何按字符拆分的实现都必须完成它们的工作,并考虑字符边界,因此它们作为工作的下限估计是有效的。

展开查看全部

相关问题