将字符串拆分为1-3个单词的字符串数组,具体取决于长度

时间:2019-06-06 21:56:56

标签: javascript

我有以下输入字符串

  

Lorem ipsum dolor坐在舒适的办公室,在escil cillum dolor eu fugia的reprehenderit中,使用eed sed doeiu​​smod tempor incididunt ut Duis aute irure dolor ...

以示例方式分割规则

[
     "Lorem ipsum dolor",  // A: Tree words <6 letters  
     "sit amet",           // B: Two words <6 letters if next word >6 letters
     "consectetur",        // C: One word >=6 letters if next word >=6 letters
     "adipiscing elit",    // D: Two words: first >=6, second <6 letters
     "sed doeiusmod",      // E: Two words: firs<6, second >=6 letters
     "tempor"              // rule C
     "incididunt ut"       // rule D
     "Duis aute irure"     // rule A
     "dolor in"            // rule B
     "reprehenderit in"    // rule D
     "esse cillum"         // rule E
     "dolor eu fugia"      // rule D
     ...
]

因此您可以看到数组中的字符串可以包含最小一和最大树词。我尝试按照以下方式进行操作,但无法正常工作-如何操作?

let s="Lorem ipsum dolor sit amet consectetur adipiscing elit sed doeiusmod tempor incididunt ut Duis aute irure dolor in reprehenderit in esse cillum dolor eu fugia";

let a=[""];
s.split(' ').map(w=> {
  let line=a[a.length-1];
  let n= line=="" ? 0 : line.match(/ /g).length // num of words in line
  if(n<3) line+=w+' ';
  n++;
  if(n>=3) a[a.length-1]=line 
}); 

console.log(a);

更新

边界条件:如果最后一个单词/单词不匹配任何规则,则将它们添加为最后一个数组元素(但是两个长单词不能在一个字符串中更新)

9 个答案:

答案 0 :(得分:5)

您可以将规则表示为缩写的正则表达式,从它们中构建真实的正则表达式并将其应用于输入:

text = "Lorem ipsum, dolor. sit amet? consectetur,   adipiscing,  elit! sed doeiusmod tempor incididunt ut Duis aute irure dolor in reprehenderit in esse cillum dolor eu fugia bla?";

rules = ['(SSS)', '(SS(?=L))', '(L(?=L))', '(SL)', '(LS)', '(.+)']

regex = new RegExp(
    rules
        .join('|')
        .replace(/S/g, '\\w{1,5}\\W+')
        .replace(/L/g, '\\w{6,}\\W+')
    , 'g')

console.log(text.match(regex))

如果规则不变,则仅需要一次正则表达式构造部分。

请注意,这也可以合理地处理标点符号。

答案 1 :(得分:3)

一个选择是首先创建规则数组,例如:

const rules = [
  // [# of words to splice if all conditions met, condition for word1, condition for word2, condition for word3...]
  [3, 'less', 'less', 'less'],
  // the above means: splice 3 words if the next 3 words' lengths are <6, <6, <6
  [2, 'less', 'less', 'eqmore'],
  // the above means: splice 2 words if the next 3 words' lengths are <6, <6, >=6
  [1, 'eqmore', 'eqmore'],
  [2, 'eqmore', 'less'],
  [2, 'less', 'eqmore']
];

然后遍历规则数组,找到匹配的规则,从匹配的规则中提取适当数量的单词进行拼接,然后压入输出数组:

    const rules = [
      [3, 'less', 'less', 'less'],
      [2, 'less', 'less', 'eqmore'],
      [1, 'eqmore', 'eqmore'],
      [2, 'eqmore', 'less'],
      [2, 'less', 'eqmore']
    ];
const s = "Lorem ipsum dolor sit amet consectetur adipiscing elit sed doeiusmod tempor incididunt ut Duis aute irure dolor in reprehenderit in esse cillum dolor eu fugia";

const words = s.split(' ');
const output = [];
const verify = (cond, word) => cond === 'less' ? word.length < 6 : word.length >= 6;
while (words.length) {
  const [wordCount] = rules.find(
    ([wordCount, ...conds]) => conds.every((cond, i) => verify(cond, words[i]))
  );
  output.push(words.splice(0, wordCount).join(' '));
}
console.log(output);

当然,.find假定每个输入字符串对于每个拼接位置始终具有匹配规则。

对于将与先前规则不匹配的任何单词都添加到输出的附加规则,请将[1]放入rules数组的底部:

const rules = [
      [3, 'less', 'less', 'less'],
      [2, 'less', 'less', 'eqmore'],
      [1, 'eqmore', 'eqmore'],
      [2, 'eqmore', 'less'],
      [2, 'less', 'eqmore'],
      [1]
    ];
const s = "Lorem ipsum dolor sit amet consectetur adipiscing elit sed doeiusmod tempor incididunt ut Duis aute irure dolor in reprehenderit in esse cillum dolor eu fugia";

const words = s.split(' ');
const output = [];
const verify = (cond, word) => cond === 'less' ? word.length < 6 : word.length >= 6;
while (words.length) {
  const [wordCount] = rules.find(
    ([wordCount, ...conds]) => conds.every((cond, i) => words[i] && verify(cond, words[i]))
  );
  output.push(words.splice(0, wordCount).join(' '));
}
console.log(output);

答案 2 :(得分:3)

如果我们定义长度为<6的单词的大小为1,> = 6的单词的大小为2,我们可以将规则重写为“如果下一个单词将使当前行的总大小> = 4,则从下一个开始线”。

function wordSize(word) {
  if (word.length < 6) 
    return 1;
  return 2;
}
let s = "Lorem ipsum dolor sit amet consectetur adipiscing elit sed doeiusd tempor incididunt ut Duis aute irure dolor in reprehenderit in esse cillum dolor eu fugia";
var result = [];
var words = s.split(" ");
var row = [];
for (var i = 0; i < words.length; ++i) {
  if (row.reduce((s, w) => s + wordSize(w), 0) + wordSize(words[i]) >= 4) {
    result.push(row);
    row = [];
  }
  row.push(words[i]);
}
result.push(row);
result = result.map(a => a.join(" "));
console.log(result);

答案 3 :(得分:3)

我也发现这个问题非常有趣。这是一个长格式的答案,显示了我如何到达最终程序的过程。在此过程中,有几个标记为sketch的代码块。我希望这种方法对功能风格的初学者有所帮助。

使用data.maybe模块,我从-

开始
// sketch 1
const wordsToLines = (words = [], r = []) =>
  words.length === 0
    ? Just (r)
    : ruleA (words)
        .orElse (_ => ruleB (words))
        .orElse (_ => ruleC (words))
        .orElse (_ => ruleD (words))
        .orElse (_ => ruleE (words))
        .orElse (_ => defaultRule (words))
        .chain (({ line, next }) => 
          wordsToLines (next, [...r, line ])
        )

然后我开始编写一些规则...

// sketch 2
const success = (line, next) =>
  Just ({ line, next })

const defaultRule = ([ line, ...next ]) =>
  success (line, next)

const ruleA = ([ a, b, c, ...more ]) =>
  small (a) && small (b) && small(c)
    ? success (line (a, b, c), more)
    : Nothing ()

const ruleB = ([ a, b, c, ...more ]) =>
  small (a) && small (b) && large (c)
    ? success (line (a, b), [c, ...more])
    : Nothing ()

// ...
我想

太混乱又重复了。作为这些函数的作者,使它们为我工作是我的职责!所以这次我重新开始设计规则以完成艰苦的工作-

// sketch 3
const rule = (guards = [], take = 0) =>
  // TODO: implement me...

const ruleA =
  rule
    ( [ small, small, small ] // pattern to match
    , 3                       // words to consume
    )

const ruleB =
  rule ([ small, small, large ], 2)

// ruleC, ruleD, ruleE, ...

const defaultRule =
  rule ([ always (true) ], 1)

这些规则要简单得多。接下来,我想稍微清理一下wordsToLines-

// sketch 4
const wordsToLines = (words = [], r = []) =>
  words.length === 0
    ? Just (r)
    : oneOf (ruleA, ruleB, ruleC, ruleD, ruleE, defaultRule)
        (words)
        .chain (({ line, next }) => 
          wordsToLines (next, [...r, line ])
        )

在我们的初始草图中,规则构造了一个{line, next}对象,但是更高阶的rule意味着我们可以隐藏更多的复杂性。并且oneOf帮助程序使内联规则轻松移动-

// final revision
const wordsToLines = (words = [], r = []) =>
  words.length === 0
    ? Just (r)
    : oneOf
        ( rule ([ small, small, small ], 3) // A
        , rule ([ small, small, large ], 2) // B
        , rule ([ large, large ], 1)        // C
        , rule ([ large, small ], 2)        // D
        , rule ([ small, large ], 2)        // E
        , rule ([ always (true) ], 1) // default
        )
        ([ words, r ])
        .chain (apply (wordsToLines))

最后,我们可以编写主要功能formatSentence-

const formatSentence = (sentence = "") =>
  wordsToLines (sentence .split (" "))
    .getOrElse ([])

现在大多数电线都没有缠结。我们只需要提供其余的依赖项-

const { Just, Nothing } =
  require ("data.maybe")

const [ small, large ] =
  dual ((word = "") => word.length < 6)

const oneOf = (init, ...more) => x =>
  more.reduce((r, f) => r .orElse (_ => f(x)), init (x))

const rule = (guards = [], take = 0) =>
  ([ words = [], r = [] ]) =>
    guards .every ((g, i) => g (words[i]))
      ? Just
          ( [ words .slice (take)
            , [ ...r, words .slice (0, take) .join (" ") ]
            ]
          )
      : Nothing ()

和一些功能性原语-

const identity = x =>
  x

const always = x =>
  _ => x

const apply = (f = identity) =>
  (args = []) => f (...args)

const dual = f =>
  [ x => Boolean (f (x))
  , x => ! Boolean (f (x))
  ]

让我们运行程序-

formatSentence ("Lorem ipsum dolor sit amet consectetur adipiscing elit sed doeiusmod tempor incididunt ut Duis aute irure dolor in reprehenderit in esse cillum dolor eu fugia ...")

// [ 'Lorem ipsum dolor'
// , 'sit amet'
// , 'consectetur'
// , 'adipiscing elit'
// , 'sed doeiusmod'
// , 'tempor'
// , 'incididunt ut'
// , 'Duis aute irure'
// , 'dolor in'
// , 'reprehenderit in'
// , 'esse cillum'
// , 'dolor eu fugia'
// , '...'
// ]

在repl.it上查看完整程序,然后将其运行到see the results-

答案 4 :(得分:2)

无需技巧。此代码遍历单词数组,并检查每个3序列的规则。应用规则以尝试执行更少的循环并创建更少的中间对象,从而获得良好的性能和内存使用率。

function apply_rules(stack, stack_i) {

    let small_word_cnt = 0;

    for(let i = 0; i<= 2; i++){

        //Not enough elements to trigger a rule
        if(!stack[stack_i+i]){
            return stack.slice(stack_i, stack.length);
        }

        //Increment the small word counter
        small_word_cnt += stack[stack_i+i].length < 6;

        //2 big words
        if(i== 1 && small_word_cnt == 0){
            return [stack[stack_i]];
        }

        //3 small words
        if(small_word_cnt == 3){
            return stack.slice(stack_i,stack_i+3);
        }
    }

    //mixed small and big words;
    return stack.slice(stack_i,stack_i+2);
}

function split_text(text) {
    const words = text.split(' '), results = [];
    let i = 0;

    while(i < words.length) {
        const chunk = apply_rules(words, i);
        i+= chunk.length;
        results.push(chunk.join(' '));
    }

    return results;
}

console.log(split_text("Lorem ipsum dolor sit amet consectetur adipiscing elit sed doeiusmod tempor incididunt ut Duis aute irure dolor in reprehenderit in esse cillum dolor eu fugia"));

答案 5 :(得分:2)

已更新,其中包含来自user633183的建议。)

我发现这是一个有趣的问题。我想立即编写一个更通用的版本,然后决定接受一个规则列表,每个规则都描述了它将收集的单词数量以及对每个单词的测试。因此,lt6本质上是(str) => str.length < 6,第一个规则(A)看起来像这样:

[3, lt6, lt6, lt6],

事实证明,这与SomePerformance的解决方案非常相似;该答案使用字符串表示两种不同的行为;这个使用实际功能。但是它们非常相似。尽管实现方式大不相同。

const allMatch = (fns, xs) =>
  fns.every ( (fn, i) =>  fn ( xs[i] ) )

const splitByRules = (rules) => {
  const run = 
    ( xs
    , res = []
    , [count] = rules .find 
        ( ([count, ...fns]) => 
          count <= xs .length 
          && allMatch (fns, xs)
        ) 
        || [1] // if no rules match, choose next word only
    ) => xs.length === 0
      ? res
      : run 
        ( xs .slice (count) 
        , res .concat ([xs .slice (0, count) ])
        )

  return (str) => 
    run (str .split (/\s+/) ) 
      .map (ss => ss .join (' '))
}

const shorterThan = (n) => (s) => 
  s .length < n

const atLeast = (n) => (s) =>
  s .length >= n

const lt6 = shorterThan (6)
const gte6 = atLeast (6)

const rules = [
// +------------- Number of words to select in next block 
// |        +--------- Functions to test againt each word
// |   _____|_____
// V  /           \
  [3, lt6, lt6, lt6],   // A
  [2, lt6, lt6, gte6],  // B
  [1, gte6, gte6],      // C
  [2, gte6, lt6],       // D
  [2, lt6, gte6],       // E
]

const words  = 'Lorem ipsum dolor sit amet consectetur adipiscing elit sed doeiusmod tempor incididunt ut Duis aute irure dolor in reprehenderit in esse cillum dolor eu fugia ...';

console .log (
  splitByRules (rules) (words) 
)

这使用了一个递归函数,当剩余的单词列表为空时,它会自动递减,否则将搜索匹配的第一个规则(同样,与某些特定的规则一样,默认规则只是获取下一个单词),然后选择相应的数字单词,其余单词重复出现。

为简单起见,递归函数接受一个单词数组,并返回一个单词数组。包装函数处理将这些字符串与字符串进行转换。

这里物质的唯一其他功能是辅助功能allMatch。本质上是([f1, f2, ... fn], [x1, x2, ..., xn, ...]) => f1(x1) && f2(x2) && ... && fn(xn)

当然,这种循环表示splitByRules (myRules)返回一个可以存储并针对不同字符串运行的函数。

规则的顺序可能很重要。如果两个规则可能重叠,则需要将首选匹配项放在另一个规则之前。


您可能会或可能不会对这种增加的一般性感兴趣,但是我认为该技术具有显着的优势:如果规则发生更改,则修改起来会容易得多。假设您现在还想包含四个个单词,如果它们的长度都少于五个个字符。然后,我们只需编写const lt5 = shorterThan(5)并包含规则

[4, lt5, lt5, lt5, lt5]

在列表的开头。

对我来说,这是一个巨大的胜利。

答案 6 :(得分:1)

这听起来像是您在求职面试或测试中遇到的问题。 解决此问题的正确方法是考虑如何将问题简化为我们能理解的并为其编写清晰代码。 / p>

我们知道有两个条件:小于或小于六个。我们可以将字符串中的每个单词表示为0(小于6 )或1(大于6 )的二进制数字。

字符串转换为二进制字符串将使处理和理解更加容易:

const s = "Lorem ipsum dolor sit amet consectetur adipiscing elit sed doeiusmod tempor incididunt ut Duis aute irure dolor in reprehenderit in esse cillum dolor eu fugia";

const b = s.split(' ').reduce((array, word) => {
  return array + (word.length >= 6 ? "1" : "0");
}, "");

console.log(b);

接下来,我们需要简化规则。每个规则都可以视为二进制字符串(一组单词)。由于某些规则比其他规则更复杂,因此我们将下一个单词添加为字符串的一部分:

  1. [0,0,0]-> 000
  2. [0,0,1]-> 001
  3. [1,1]-> 11
  4. [1,0]-> 10
  5. [0,1]-> 01

对于剩余的数字字符串,下一个适合的规则是下一组字符串。这是一个非常简单的逻辑操作:

const s = "Lorem ipsum dolor sit amet consectetur adipiscing elit sed doeiusmod tempor incididunt ut Duis aute irure dolor in reprehenderit in esse cillum dolor eu fugia";

let b = s.split(' ').reduce((array, word) => {
  return array + (word.length >= 6 ? "1" : "0");
}, "");

//console.log(b);
let a = '';
while (b != "") {
  switch (0) {
    case b.indexOf('000'):
      b = b.substring(3);
      a += '3';
      break;
    case b.indexOf('10'):
      b = b.substring(2);
      a += '2';
      break;
    case b.indexOf('01'):
      b = b.substring(2);
      a += '2';
      break;
    case b.indexOf('001'):
      b = b.substring(2);
      a += '2';
      break;
    case b.indexOf('11'):
      b = b.substring(1);
      a += '1';
      break;
  }
}

console.log(a);
//Go through the string of multi-word lengths and turn the old string into separate strings. 

const acc = [];
words = s.split(' ');
for (let index in a) {
  acc.push(words.splice(0, a[index]).join(' '));
}
console.log(acc);

是的!我们成功地将一个复杂的问题转换为容易理解的问题。尽管这不是最短的解决方案,但它非常优雅,并且仍有改进空间,而不会牺牲可读性(与其他解决方案相比)。

以这种方式概念化问题打开了更多规则甚至更复杂状态(0,1,2)的大门。

答案 7 :(得分:1)

我在这里看到了非常聪明的解决方案,谢谢大家!

但是,我认为这里有一个针对“自我记录”进行优化的解决方案的空间。请注意,我的目标是-自我文档编制-因此,该解决方案肯定不是最短的代码,最快的内存或最少的内存。

starts_with

答案 8 :(得分:0)

我写了BoltKey answer中提出的想法的简短快速版本(如果您想投票,请回答他的答案)。

主要思想

  • ws是单词大小,其中我们只有两个值1(短单词)和2(长单词)
  • s是当前循环中的行大小(我们遍历每个字的大小)
  • 如果当前行大小加上下一个单词大小s + ws> 3,则规则被打破(这是BoltKey发现的主要想法)
  • 如果规则未违反,则在第l行中添加单词,并将其大小更改为s行大小
  • 如果违反规则,则将行l添加到输出数组r并清理ls
  • 在return语句中,我们处理终止情况:如果l不为空,则添加行l中的最后一个字

let s = "Lorem ipsum dolor sit amet consectetur adipiscing elit sed doeiusd tempor incididunt ut Duis aute irure dolor in reprehenderit in esse cillum dolor eu fugia";

function split(n,str) {
  let words= str.split(' '), s=0, l=[], r=[];
  
  words.forEach(w=>{ 
    let ws= w.length<n ? 1:2;
    if(s+ws>3) r.push(l.join(' ')), s=0, l=[];
    l.push(w), s+=ws;
  })

  return l.length ? r.concat(l.join(' ')) : r;
}

console.log( split(6,s) );