【问题标题】:How to extract symbols, numbers and words from a string and store each into an accordingly categorized array?如何从字符串中提取符号、数字和单词并将它们存储到相应分类的数组中?
【发布时间】:2022-01-22 18:03:10
【问题描述】:

如何从字符串中提取符号、数字、最多 3 个单词和至少 4 个字母的单词,并将它们分别存储到相应分类的数组中?

给定的字符串是:

const string = 'There are usually 100 to 200 words + in a paragraph'; 

预期的响应是:

const numbers = ['200', '100'];

const wordsMoreThanThreeLetters = ['There', 'words ', 'paragraph', 'usually'];

const symbols = ['+'];

const words = ['are', 'to', 'in', 'a'];

【问题讨论】:

  • 你能提供预期的输出和你的任何尝试吗 -> How to Ask -> Minimal, Reproducible Example
  • 如果将不带任何标签的字符串放入数组中,则无法区分不同长度的字符串。所以在标记化过程中区分它们是没有用的。

标签: javascript regex string reduce tokenize


【解决方案1】:

一种有效的方法是split 任何空白序列处的字符串,然后在split 方法的结果数组上运行reduce 方法。

reducer 函数的实现方式是,它根据 OP 的类别收集和聚合特定数组中的字符串项(令牌),由辅助方法支持,例如数字和单词测试...

function collectWordsDigitsAndRest(collector, token) {
  const isDigitsOnly = value => (/^\d+$/).test(token);
  const isWord = value => (/^\w+$/).test(token);

  const listName = isDigitsOnly(token)
    ? 'digits'
    : (
        isWord(token)
        ? (token.length <= 3) && 'shortWords' || 'longWords'
        : 'rest'
    );
  (collector[listName] ??= []).push(token);

  return collector;
}
const {

  longWords: wordsMoreThanThreeLetters = [],
  shortWords: words = [],
  digits: numbers = [],
  rest: symbols = [],

} = 'There are usually 100 to 200 words + in a paragraph'

  .split(/\s+/)
  .reduce(collectWordsDigitsAndRest, {});

console.log({
  wordsMoreThanThreeLetters,
  words,
  numbers,
  symbols,
});
.as-console-wrapper { min-height: 100%!important; top: 0; }

当然,也可以通过单个regular expression / RegExpmatchAll 所需的令牌,该令牌具有named capturing groups 并且还使用Unicode escapes 以实现更好的国际化(i18n)覆盖。

正则表达式本身看起来和工作起来都像这样......

...派生自...

第一种方法的 reducer 函数必须适应第二种方法,以便相应地处理每个捕获的组...

function collectWordsDigitsAndRest(collector, { groups }) {
  const { shortWord, longWord, digit, rest } = groups;

  const listName = (shortWord
    && 'shortWords') || (longWord
    && 'longWords') || (digit
    && 'digits') || (rest
    && 'rest');

  if (listName) {
    (collector[listName] ??= []).push(shortWord || longWord || digit || rest);
  }
  return collector;
}

// Unicode Categories ... [https://www.regularexpressions.info/unicode.html#category]
// regex101.com ... [https://regex101.com/r/nCga5u/2]
const regXWordDigitRestTokens =
  /(?:\b(?<digit>\p{N}+)|(?<longWord>\p{L}{4,})|(?<shortWord>\p{L}+)\b)|(?<rest>[^\p{Z}]+)/gmu;

const {

  longWords: wordsMoreThanThreeLetters = [],
  shortWords: words = [],
  digits: numbers = [],
  rest: symbols = [],

} = Array
  .from(
    'There are usually 100 to 200 words ++ -- ** in a paragraph.'
    .matchAll(regXWordDigitRestTokens)
  )
  .reduce(collectWordsDigitsAndRest, {});

console.log({
  wordsMoreThanThreeLetters,
  words,
  numbers,
  symbols,
});
.as-console-wrapper { min-height: 100%!important; top: 0; }

【讨论】:

    【解决方案2】:

    您尝试执行的操作称为tokenization。通常这是通过正则表达式完成的。您为要识别的每个标记编写一个正则表达式。每个标记都被空白包围。空格和单词之间的位置称为单词边界,由\b 匹配。以下正则表达式使用Unicode character classes。符号不是单词,所以它们没有单词边界。

    • 三个或更少字母的单词:\b\p{Letter}{1,3}\b
    • 三个以上字母的单词:\b\p{Letter}{4,}\b
    • 号码:\b\p{Number}+\b
    • 符号:\p{Symbol}+

    为了解析不同的标记,将正则表达式放入命名的捕获组是很有用的:(?&lt;anything&gt;.*)。这将匹配任何内容并将匹配存储在捕获组anything

    const input = 'There are usually 100 to 200 words + in a paragraph'; 
    
    let rx = new RegExp ([
      '(?<wle3>\\b\\p{L}{1,3}\\b)',
      '(?<wgt3>\\b\\p{L}{4,}\\b)',
      '(?<n>\\b\\p{N}+\\b)',
      '(?<s>\\p{S}+)'
      ].join ('|'),
      'gmu');
    
    let words_le_3 = [];
    let words_gt_3 = [];
    let numbers = [];
    let symbols = [];
    
    for (match of input.matchAll(rx)) {
      let g = match.groups;
      switch (true) {
      case (!!g.wle3): words_le_3.push (g.wle3); break;
      case (!!g.wgt3): words_gt_3.push (g.wgt3); break;
      case (!!g.n):    numbers   .push (g.n);    break;
      case (!!g.s):    symbols   .push (g.s);    break;
      }
    }
    
    console.log (`Words with up to three letters: ${words_le_3}`);
    console.log (`Words with more than three letters: ${words_gt_3}`);
    console.log (`Numbers: ${numbers}`);
    console.log (`Symbols: ${symbols}`);

    如果您将匹配项存储在一个对象而不是四个顶级数组中,则代码会更简单。在这种情况下,switch 语句可以被组上的循环和赋值替换。

    【讨论】:

      【解决方案3】:

      您可以为这些情况编写一个单独的函数:

      const txt  = 'There are usually 100 to 200 words in a paragraph';
      console.log(txt);
      console.log( ctrim(txt) )
      
      function ctrim(txt) {  
        let w = txt.split(' ');
        let _w = []
        w.forEach((w) => {
          if(w.length <= 3) {
            _w.push( w )
          }
        }) 
        return _w
      }

      【讨论】:

      • @Onkarkole ...上述方法甚至不符合OP的第一个含糊不清的要求。它甚至更少满足新的需求。 OP 怎么可能评论... “它有效,谢谢。” ..?
      • @PeterSeliger 我更新了。
      • @MaikLowrey ... “我更新了” ...我看到 ... 从 if(w.length &lt; 3) { ...if(w.length &lt;= 3) { ...。请阅读要求。即使在开始时,OP 也希望至少在单词和数字/数字之间进行区分。上面的代码没有显示出朝着这个方向的任何努力。
      • @PeterSeliger 在我阅读 OP 的问题时,我还没有阅读任何相关内容。 OP在我回答后才修改了这个问题。而且我不能每次都检查问题是否已被修改。 stackoverflow.com/posts/70433369/timeline
      【解决方案4】:

      const string = 'There are usually 100 to 200 words + in a paragraph';
      const response = [];
      for (let i = 0; i < string.length; i++) {
        response.push(string[i]);
        // console.log(response); All process of the loop
      }
      console.log(response);

      【讨论】:

        猜你喜欢
        • 2017-10-01
        • 1970-01-01
        • 2018-01-01
        • 2018-07-30
        • 1970-01-01
        • 2018-09-02
        • 1970-01-01
        • 2020-12-08
        • 1970-01-01
        相关资源
        最近更新 更多