【问题标题】:JavaScript - reading a whitespace delimited text file into array and use as lookup tableJavaScript - 将空格分隔的文本文件读入数组并用作查找表
【发布时间】:2019-10-30 08:39:26
【问题描述】:

首先:我是 JavaScript 的绝对初学者,两周前开始每天学习很多小时。我在 GNU/Linux 上运行 node.JS 服务器,我尝试了很多变体来实现目标。不幸的是,我卡住了,不知道如何继续。

我有一个带有空格和换行符的文本文件,该文件包含大约 2000 行以上的内容。我想将此文本文件读入我的 javascript 程序,以便稍后用作查找表。我不确定是否需要对它进行 JSON 字符串化以供以后使用,也许将其保留为对象/数组很简单,以后我可以将其用于查找函数。我只想从这个文本文件中提取那些包含字符“#”的行并将其用作分隔符。所有其他行都可以忽略。每一行代表一个数据集、元素、对象或任何它被正确调用的东西。最终目标是:用户要求“Apple”,他应该得到“-9.99”和“BTW”(例如)作为答案。以下是原始文本文件的示例:

 Sugar#    1051#      331#     BAD#     1.23#    -4.56#    -5.0#  WWF#
 N3T;
 Apple#     551#     3815#     F3W#     5.55#    -9.99#    -1.0#  BTW#
 BBC;
 Berry#      19#       22#      FF#     19.5#   -12.34#     5.0#  CYA#
 T1K;

它应该代表 3 个元素,每个元素包含 8 对:

 name: 'Sugar'
 sec: 1051
 ter: 331
 wrd: 'BAD'
 a: 1.23
 b: -4.56
 c: -5.0
 spon: 'WWF'

 name: 'Apple'
 sec: 551
 ter: 3815
 wrd: 'F3W'
 a: 5.55
 b: -9.99
 c: -1.0
 spon: 'BTW'

 name: 'Berry'
 sec: 19
 ter: 22
 wrd: 'FF'
 a: 19.5
 b: -12.34
 c: 5.0
 spon: 'CYA'

一开始我尝试使用 fs.readFileSync 将整个文本文件作为字符串读取,但没有成功。失望的是,我尝试了另一种方法 readline 来读取我的文本文件 line-by-line 并进行过滤,因为我在网上获得了这种方法更多记忆的印象-友好,甚至可以读取非常大的文件。虽然我很确定 3000 行是个笑话 :)

这是我使用 readline 时的代码:

const fs = require('fs');
const readline = require('readline');

function readAndFilter (source, data) {
 var fields;
 var obj = new Object;
 var arr = new Array;

const readAndFilter = readline.createInterface({
 input: fs.createReadStream('test.in'),
 crlfDelay: Infinity
 });

 readAndFilter.on('line', (line) => {
     if ( line.match( /#/ ) ) {
      fields        = line.split( '#' ).slice();
      obj.name      = fields[0].trim();
      obj.sec       = fields[1].trim();
      obj.ter       = fields[2].trim();
      obj.wrd       = fields[3].trim();
      obj.a         = fields[4].trim();
      obj.b         = fields[5].trim();
      obj.c         = fields[6].trim();
      obj.spon      = fields[7].trim();

     console.log(obj);
     // let jsonView = JSON.stringify(obj);
     // arr.push(obj);
     }
   });

  readAndFilter.on('close', function() {
   return arr;
  });

}

readAndFilter();

这是代码输出的内容(请注意,我通过为每行输出添加时间戳来自定义控制台日志):

 2019-06-16 14:40:10 { name: 'Sugar',
 sec: '1051',
 ter: '331',
 wrd: 'BAD',
 a: '1.23',
 b: '-4.56',
 c: '-5.0',
 spon: 'WWF' }
 2019-06-16 14:40:10 { name: 'Apple',
 sec: '551',
 ter: '3815',
 wrd: 'F3W',
 a: '5.55',
 b: '-9.99',
 c: '-1.0',
 spon: 'BTW' }
 2019-06-16 14:40:10 { name: 'Berry',
 sec: '19',
 ter: '22',
 wrd: 'FF',
 a: '19.5',
 b: '-12.34',
 c: '5.0',
 spon: 'CYA' }

数据字段看起来不错,到目前为止文件已正确处理但是=>对象“obj”将仅保存最后一个数据集(名称:Berry),因为它在每一行之后被重写。我通过切割线仔细检查了

console.log(obj);

从 readAndFilter.on('line', ... 块中插入到 'close' 块中:

[...]
      readAndFilter.on('line', (line) => {
            if ( line.match( /#/ ) ) {
              fields        = line.split( '#' ).slice();
              obj.name      = fields[0].trim();
              obj.sec       = fields[1].trim();
              obj.ter       = fields[2].trim();
              obj.wrd       = fields[3].trim();
              obj.a = fields[4].trim();
              obj.b = fields[5].trim();
              obj.c = fields[6].trim();
              obj.spon      = fields[7].trim();

            // let jsonView = JSON.stringify(obj);
            // arr.push(obj);
            }
      });

      readAndFilter.on('close', function() {
       console.log(obj);
      return arr;
      });
    [...]

产生的输出是:

 { name: 'Berry',
 sec: '19',
 ter: '22',
 wrd: 'FF',
 a: '19.5',
 b: '-12.34',
 c: '5.0',
 spon: 'CYA' }

它不能用作查找表,我需要数组中的所有行,以便以后可以访问它们以进行查找例程。所以我尝试使用以下代码将每个对象添加到一个数组中:

    [...]
      readAndFilter.on('line', (line) => {
            if ( line.match( /#/ ) ) {
              fields        = line.split( '#' ).slice();
              obj.name      = fields[0].trim();
              obj.sec       = fields[1].trim();
              obj.ter       = fields[2].trim();
              obj.wrd       = fields[3].trim();
              obj.a = fields[4].trim();
              obj.b = fields[5].trim();
              obj.c = fields[6].trim();
              obj.spon      = fields[7].trim();

            // let jsonView = JSON.stringify(obj);
            arr.push(obj);
            }
      });

      readAndFilter.on('close', function() {
       console.log(arr);
      return arr;
      });
    [...]

现在我得到一个包含三个对象的数组,但只显示最后一个数据集名称:Berry 再次显示

 [ { name: 'Berry',
 sec: '19',
 ter: '22',
 wrd: 'FF',
 a: '19.5',
 b: '-12.34',
 c: '5.0',
 spon: 'CYA' },
 { name: 'Berry',
 sec: '19',
 ter: '22',
 wrd: 'FF',
 a: '19.5',
 b: '-12.34',
 c: '5.0',
 spon: 'CYA' },
 { name: 'Berry',
 sec: '19',
 ter: '22',
 wrd: 'FF',
 a: '19.5',
 b: '-12.34',
 c: '5.0',
 spon: 'CYA' } ]

我什至尝试过使用 concat 和许多其他变体。我到底做错了什么?我使用 readline/line-by-line 技术的方法是否完全错误,我应该改用 fs.readFileSync 吗?我也试过了,这是我使用 fs.readFileSync 的方法:

            function readAndFilter () {
                var fields;
                var obj = new Object;
                var arr = new Array;
                var data = fs.readFileSync('test.in', 'utf8').replace(/\r\n/g,'\n').split('\n').filter(/./.test, /\#/)
    /*
            if ( data.match( /#/ ) ) {
                fields      = data.split( '#' ).slice();
                obj.name    = fields[0].trim();
                obj.cqz     = fields[1].trim();
                obj.itu     = fields[2].trim();
                obj.cont    = fields[3].trim();
                obj.lng     = fields[4].trim();
                obj.lat     = fields[5].trim();
                obj.tz      = fields[6].trim();
                obj.pfx     = fields[7].trim();
            };
    */
    console.log(typeof data + "\n" + data);
    }

一旦我开始使用 .split('\n'),变量 data 就是 typeof 对象,因此我无法使用下面的 if 子句。它失败了,因为它只能在字符串上工作。也许我完全指向错误的方向而且它更简单?最终目标是:我想根据这个查找表检查像 "Apple" 这样的搜索字符串并检索适当的值(名称、sec、ter、b 或其中任何一个)。

我非常感谢任何有用的答案或提示。请耐心等待我,老实说:我真的很努力!谢谢大家。

【问题讨论】:

    标签: javascript file parsing text filter


    【解决方案1】:

    首先,欢迎来到 SO,并赞扬您的重点和详尽的问题。干得好!

    您的流解决方案无法按预期工作的原因是因为它是异步的,因此您试图在结果实际出现之前访问它。查看我们的classic thread 了解更多信息。

    不过,为了简单起见,我建议坚持使用readFileSync 解决方案。一般来说,出于性能原因,node.js 中不建议使用同步功能,但考虑到文件很小(3000 行),它应该不会造成太大影响。

    读完文件后,解析可以这样完成:

    let text = fs.readFileSync('test.in', 'utf8');
    
    let result = [];
    
    for (let line of text.trim().split('\n')) {
    
        if (!line.includes('#'))
            continue;
    
        let s = line.trim().split(/[#\s]+/g);
    
        result.push({
            name: s[0],
            sec: s[1],
            ter: s[2],
            wrd: s[3],
            a: s[4],
            b: s[5],
            c: s[6],
            spon: s[7],
        });
    }
    
    
    console.log(result)

    【讨论】:

    • 我认为 OP 最终需要一个 result 对象,它将名称映射到条目,而不仅仅是一个条目数组。
    【解决方案2】:

    您好,乔治,非常感谢。我只是交叉阅读了您发布的链接,但稍后会深入探讨。没有预期的意图,我认为我的代码不会失败,因为我试图在结果出现之前访问它,就像你说的那样。在我发布的 readline 变体中,您看到我尝试使用 push 函数将新对象添加到我在开头定义的数组中。

    阅读您的代码并尝试后我很好奇。我对即用型代码不感兴趣,我不知道它的作用,但我真的很想了解幕后发生的事情以及一切是如何工作的。这就是为什么我仍然在问,我的目标是理解。因此,以我的拙见,您所做的事情与我之前已经尝试过的完全一样,唯一的区别是您的数组推送命令看起来与我的不同。我用过

    arr.push(obj);
    

    显然失败了。如前所述,我对 readline 变体使用了以下代码:

     [...]
          readAndFilter.on('line', (line) => {
                if ( line.match( /#/ ) ) {
                  fields        = line.split( '#' ).slice();
                  obj.name      = fields[0].trim();
                  obj.sec       = fields[1].trim();
                  obj.ter       = fields[2].trim();
                  obj.wrd       = fields[3].trim();
                  obj.a = fields[4].trim();
                  obj.b = fields[5].trim();
                  obj.c = fields[6].trim();
                  obj.spon      = fields[7].trim();
    
                arr.push(obj);
                }
          });
    
          readAndFilter.on('close', function() {
           console.log(arr);
          return arr;
          });
        [...]
    

    所以我只是更改/删除了提到的行“arr.push(obj)”并替换了推送功能,使其看起来与您的相同:

     [...]
          readAndFilter.on('line', (line) => {
                if ( line.match( /#/ ) ) {
                  fields        = line.split( '#' ).slice();
    
                arr.push({
                  name: fields[0].trim(),
                  sec: fields[1].trim(),
                  ter: fields[2].trim(),
                  wrd: fields[3].trim(),
                  a: fields[4].trim(),
                  b: fields[5].trim(),
                  c: fields[6].trim(),
                  spon: fields[7].trim(),
                });
                }
          });
    
          readAndFilter.on('close', function() {
           console.log(arr);
          return arr;
          });
        [...]
    

    这样它会输出与您的代码相同的结果,WORKS!!!* 因为我使用的是 readline 并因此逐行处理,所以它不需要 for 循环。真的是这一条线让我生病并造成麻烦吗?另一方面,我问自己如何“美化”代码以使其更简单,所以我不需要写每个 name,sec,ter,wrd,a,b,c,spon 列。想象一下,每个对象有 150 个属性,写下来会很痛苦。这就是为什么我最初尝试了一个简单的 arr.push(obj),遗憾的是它没有像我预期的那样工作。

    任何有用的解释表示赞赏。再次感谢你!现在我需要找到一种方法来读取/搜索保存在内存中的查找表,以便显示/输出我需要的适当的密钥对/值。

    【讨论】:

    • 嗨,是的,这是您原始代码中的一个问题:您一遍又一遍地覆盖相同的obj,而arr.push({...}) 向结果中添加了一个新对象,正如预期的那样。但是,您的 readline 解决方案仍然无法正常工作,尤其是 close 处理程序中的 return array 什么都不做(返回给谁?)我猜同步选项暂时更容易管理。
    • 也就是说,这不是本网站的运作方式。 SO 不是论坛,坚持问答形式会更舒服:您提出问题 - 其他人回答 - 您发表简短评论要求澄清 - 如果您有更多问题,则发布新问题等等.
    • 嗨,乔治。是的,我明白它是如何工作的。出于这个原因,我直截了当地询问了 arr.push(obj); 部分,因为我想让它得到澄清和理解。不幸的是,如果他在对象内部有更多的属性,如何实现所解释的目标仍然没有答案。将它们写在推送部分会很糟糕。是否有任何捷径或更好/更简单的代码来获得相同的结果?关于“返回”部分:好的,我可以从关闭部分删除该行,但代码在我这边运行良好,并获得了预期的结果。嗯?
    • 是的,流代码运行并记录所需的数组,但是,当您添加更多代码以实际处理它时,您会遇到问题。例如,该处理代码到底应该放在哪里?关于“大物体”问题,我会专门发布一个关于它的新问题。
    猜你喜欢
    • 1970-01-01
    • 2019-10-22
    • 2021-02-26
    • 2015-12-03
    • 1970-01-01
    • 2022-12-05
    • 2016-08-19
    • 1970-01-01
    • 2013-02-22
    相关资源
    最近更新 更多