在重复模式中查找每隔一个元素

Finding every second element in a repeating pattern

重复 'i's 后跟 'i's and/or 't's 的数据。

data = ['i','t','t','i','i','t','t','t']

正在尝试检索模式 ['i'、't'、't'] 中最后一个 't' 的索引:

[2,6] // ['i','t','t','i','i','t','t','t'] # position of the returned 't's
      //   _______ ^       _______ ^

我正在寻找仅使用(纯)函数的非递归解决方案,例如使用 ramdajs。

尝试使用 reducetransduce,但至今未成功。

您可以简单地通过 for 循环并检查数组的最后一个值:

        var data = ['i','t','t','i','i','t','t','t'];
        var positions = new Array();

        for(var i=2; i< data.length; i++){
          if(data[i-2] === 'i' && data[i-1] === 't' && data[i] === 't') {
            positions.push(i)
          }
        }
        
        console.log(positions)

您可以使用 Array.prototype.reduce() 和一个简单的条件来完成。

data = ['i','t','t','i','i','t','t','t']


var newData = data.reduce(function (acc, item, index) {
    // Check if current element is `t` and the item before it is `i`, `t` 
    if (item === 't' && data[index - 1] === 'i' && data[index - 2] === 't') {
        acc.push(item)
    }
    return acc;
}, []);

console.log(newData); // ['t', 't']

您可以使用带有临时数组的嵌套方法来检查不同起点的相同模式。该提议适用于任意长度的模式和 returns 预定义模式的索引。

这个解决方案的特点显然很简单 Javascript。

index   i  t  i  t  t  i  i  t  t  t   temp    result    comment
-----  ------------------------------  ------  --------  ------------
    0  <i>                             [0]     []        match
    1   i <t>                          [0]     []        match
          <->                          [0]     []        no match
    2   i  t <->                       []      []        no match
             <i>                       [2]     []        match
    3         i <t>                    [2]     []        match
                <->                    [2]     []        no match
    4         i  t <t>                 []      [4]       pattern found
                   <->                 []      [4]       no match
    5                 <i>              [5]     [4]       match
    6                  i <->           []      [4]       no match
                         <i>           [6]     [4]       match
    7                     i <t>        [6]     [4]       match
                            <->        [6]     [4]       no match
    8                     i  t <t>     []      [4, 8]    pattern found
                               <->     []      [4, 8]    no match
    9                             <->  []      [4, 8]    no match

<t> matches 't' at position
<-> does not match at position

function getPatternPos(array, pattern) {
    var result = [];

    array.reduce(function (r, a, i) {
        return r.concat(i).filter(function (j) {
            if (i - j === pattern.length - 1 && a === pattern[i - j]) {
                result.push(i);
                return false;
            }
            return a === pattern[i - j];
        });
    }, []);
    return result;
}

console.log(getPatternPos(['i', 't', 't', 'i', 'i', 't', 't', 't'], ['i', 't', 't']));
// [2, 6]

console.log(getPatternPos(['i','t','i', 't', 't', 'i', 'i', 't', 't', 't'], ['i', 't', 't']));
// [4, 8]

console.log(getPatternPos(['a', 'b', 'a', 'b', 'b', 'a', 'b', 'c', 'd'], ['a', 'b', 'c']));
// [7]
.as-console-wrapper { max-height: 100% !important; top: 0; }

data.filter((c, i, d) => c === 't' && d[i - 1] === 't' && d[i - 2] === 'I')

**无负指标:**

const matchMaker = () => {
    let memo = [‘a’, ‘b’];
    return (c, i, d) => {
        memo.unshift(c);
        return memo[1] + memo[2] + c === 'itt';
    }
};

data.filter(matchMaker());

一种方法是使用 R.aperture 迭代 data 列表的 3 元素滑动 window,然后跟踪等于模式 ['i', 't', 't'].

const data = ['i','t','t','i','i','t','t','t']

const isPattern = R.equals(['i', 't', 't'])

const reduceWithIdx = R.addIndex(R.reduce)

const positions = reduceWithIdx((idxs, next, idx) =>
  isPattern(next) ? R.append(idx + 2, idxs) : idxs
, [], R.aperture(3, data))

console.log(positions)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.24.1/ramda.min.js"></script>

这种方法的无点版本可能类似于以下内容,尽管这是否更可取取决于 style/readability 的偏好。

const data = ['i','t','t','i','i','t','t','t']

const isPattern = R.equals(['i', 't', 't'])

const run = R.pipe(
  // create sliding window of 3 elements
  R.aperture(3),

  // zip sliding window with index
  R.chain(R.zip, R.compose(R.range(0), R.length)),

  // filter matching pattern
  R.filter(R.compose(isPattern, R.nth(1))),

  // extract index
  R.map(R.compose(R.add(2), R.head))
)

console.log(run(data))
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.24.1/ramda.min.js"></script>

function getPattern(arr, p) {
  var r = [],
    dir = [];
  for (let [i, v] of arr.entries()) {
    dir = dir.concat(i).filter(function(x) {
      if (v === p[i - x] && i - x === p.length - 1) {
        r.push(i);
        return false;
      }
      return v === p[i - x];
    })
  };
  return r;
}

console.log(getPattern(['i', 't', 't', 'i', 'i', 't', 't', 't'], ['i', 't', 't']));
console.log(getPattern(['i', 't', 'i', 't', 't', 'i', 'i', 't', 't', 't'], ['i', 't', 't']));
console.log(getPattern(['a', 'b', 'a', 'b', 'b', 'a', 'b', 'c', 'd'], ['a', 'b', 'c']));
.as-console-wrapper { max-height: 100% !important; top: 0; }

如果有人想在不使用不使用前瞻或后视(“i + 1”或“i - 2'”等)的库的情况下查看示例。

我认为它的工作原理与 Ramda 方法类似,但我选择将分区和相等性检查结合在同一个循环中:

  • reduce
  • 中的每一步
  • 取与pattern长度相匹配的数组部分
  • 检查是否等于模式
  • 如果是,则将节中最后一个元素的索引添加到reduce
  • 的结果中

代码,其中patterndata都是字符串数组:

const findPattern = (pattern, data) => data.reduce(
  (results, _, i, all) => 
    // Check if a slice from this index equals the pattern
    arrEqual(all.slice(i, i + pattern.length), pattern)
      // Add the last index of the pattern to our results
      ? (results.push(i + pattern.length - 1), results)
      // or, return what we had
      : results, 
    []);

// Utility method to check array equality
const arrEqual = (arr1, arr2) =>
  arr1.length === arr2.length &&
  arr1.every((x, i) => x === arr2[i]);

我测试了几个数据集,认为它满足所有要求:

const findPattern = (pattern, data) => data.reduce(
  (results, _, i, all) => 
    arrEqual(all.slice(i, i + pattern.length), pattern)
      ? push(results, i + pattern.length - 1)
      : results, 
    []);


const arrEqual = (arr1, arr2) =>
  arr1.length === arr2.length &&
  arr1.every((x, i) => x === arr2[i]);
  
const push = (xs, x) => (xs.push(x), xs);

// For just string patterns we could also do:
// const arrEqual = (arr1, arr2) => arr1.join("") === arr2.join("");

// Test cases
const dataSets = [
  // (i) marks a matching index
  // [i] marks a last matching index that should be returned
  //  |  marks a new start

  { pattern: ["i","t","t"], input: ['i','t','t','i','i','t','t','t'],         output: [2, 6] },
  //                               |(0) (1) [2]| 3 -(4) (5) [6]| 7     
  
  { pattern: ["i","t"],     input: ['i','t','i','t','t','i','i','t','t','t'], output: [1, 3, 7] },
  //                               |(0) [1]|(2) [3]| 4 | 5 |(6) [7]| 8 | 9
  
  { pattern: ["i","t","t"], input: ['i','t','i','t','t','i','i','t','t','t'], output: [4, 8] },
  //                               |(0) (1)|(2) (3) [4]| 5 |(6) (7) [8]| 9
  
  { pattern: ["i","t","i"], input: ['i','t','i','t','i','t','i','t','i','t'], output: [2, 4, 6, 8] }
  //                               |(0) (1) [2]|           |(6) (7) [8]| 9
  //                                       |(2) (3) [4]  
  //                                               |(4) (5) [6]  
];

dataSets.forEach(({pattern, input, output}) => 
  console.log(
    "| input:", input.join(" "), 
    "| control:", output.join(", "), 
    "| answer:", findPattern(pattern, input).join(", ")
  )
)

两年后,迷路的旅行者偶然发现了这个问题,并注意到对于可变大小(尤其是具有更大输入数组或多个输入数组的大模式),经典 KMP algorithm 会很棒。

我觉得这个算法值得研究

我们将从简单的命令式实现开始。然后切换到(至少对我而言)更直观(但可能稍微不太理想,而且在内存方面肯定不太理想)的有限自动机版本。最后,我们会看到一些看起来像功能但不是 100% 纯的东西。我没有心情用 JS 中 KMP 的纯函数实现来折磨自己 :)。

前缀函数KMP,命令式实现:

function getPatternPos(array, pattern) {
    const result = [];

 // trying to explain this is a waste of time :)

    function createPrefix(pattern) {
     // initialize array with zeros
     const prefix = Array.apply(null, Array(pattern.length)).map(Number.prototype.valueOf, 0);
     let s = 0;
     prefix[0] = 0;

     for (let i = 1; i < pattern.length; ++i) {
   while (s > 0 && pattern[s] !== pattern[i]) {
    s = prefix[s - 1];
   }
   if (pattern[i] === pattern[s]) {
        ++s;
   }
   prefix[i] = s;
     }

     return prefix;
    }

    const prefix = createPrefix(pattern);
    let s = 0;
    for (let i = 0; i < array.length; ++i) {
  while (s > 0 && pattern[s] !== array[i]) {
   s = prefix[s - 1];
  }
  if (array[i] === pattern[s]) {
   ++s;
  }
  if (s === pattern.length) {
   result.push(i);
   s = 0;
  }
    }

    return result;
}

console.log(getPatternPos(['i', 't', 't', 'i', 'i', 't', 't', 't'], ['i', 't', 't']));
// [2, 6]
console.log(getPatternPos(['i','t','i', 't', 't', 'i', 'i', 't', 't', 't'], ['i', 't', 't']));
// [4, 8]
console.log(getPatternPos(['a', 'b', 'a', 'b', 'b', 'a', 'b', 'c', 'd'], ['a', 'b', 'c']));
// [7]
console.log(getPatternPos("ababxabababcxxababc".split(""), "ababc".split("")));
// [11, 18]
console.log(getPatternPos("abababcx".split(""), "ababc".split("")));
.as-console-wrapper { max-height: 100% !important; top: 0; }

Finate 自动机 KMP 实现:

function getPatternPos(array, pattern) {
    const result = [];

    function patternCode(i) {
     return pattern[i].charCodeAt(0);
    }

    function createStateMachine(pattern) {
  // return single dimensional array as matrix instead of array of arrays,
  // for better perfomanse (locality - cache optimizations) and memory usage.
     // zero initialize matrix
     const sm = Array.apply(null, Array(256 * pattern.length)).map(Number.prototype.valueOf, 0);
     let s = 0;
     sm[patternCode(0) * pattern.length + 0] = 1;

     for (let i = 1; i < pattern.length; ++i) {
   // go to same states as if we would go after backing up, so copy all
      for (let code = 0; code < 256; ++code)
    sm[code * pattern.length + i] = sm[code * pattern.length + s];

   // only in case of current symbol go to different/next state
   sm[patternCode(i) * pattern.length + i] = i + 1;

   // update the state that fallows backup path
      s = sm[patternCode(i) * pattern.length + s];
     }

     return sm;
    }

    const sm = createStateMachine(pattern);
    numStates = pattern.length;
 let s = 0;
 // now simply fallow state machine
    for (let i = 0; i < array.length; ++i) {
     s = sm[array[i].charCodeAt(0) * numStates + s];
     if (s === pattern.length) {
      result.push(i);
      s = 0;
     }
    }

    return result;
}

console.log(getPatternPos(['i', 't', 't', 'i', 'i', 't', 't', 't'], ['i', 't', 't']));
// [2, 6]
console.log(getPatternPos(['i','t','i', 't', 't', 'i', 'i', 't', 't', 't'], ['i', 't', 't']));
// [4, 8]
console.log(getPatternPos(['a', 'b', 'a', 'b', 'b', 'a', 'b', 'c', 'd'], ['a', 'b', 'c']));
// [7]
console.log(getPatternPos("ababxabababcxxababc".split(""), "ababc".split("")));
// [11, 18]
console.log(getPatternPos("abababcx".split(""), "ababc".split("")));
.as-console-wrapper { max-height: 100% !important; top: 0; }

Funcational-ish KMP 实施:

function getPatternPos(array, pattern) {
 // pure function that creates state machine,
 // but it's implementation is not complitely pure internally.
 function createStateMachine(pattern) {
  const initState = Object.create(null);
  initState[pattern[0]] = Object.create(initState);

  const  {currState: finalState} = pattern.slice(1).reduce(function(acc, cval, cidx) {
   const newFallbackState = acc.fallbackState[cval] || initState;
   // WARNING: non-functional/immutable part,
   // to make it complitely pure we would probably need to
   // complicate our lives with better data structures or
   // lazy evalutaion.
   acc.currState[cval] = Object.create(newFallbackState);
   return {currState: acc.currState[cval], fallbackState: newFallbackState};
  }, {currState: initState[pattern[0]], fallbackState: initState});

  return {initState: initState, finalState: finalState};
 }

 const {initState, finalState} = createStateMachine(pattern);

 return array.reduce(function (acc, cval, cidx, array) {
  const newState = acc.currState[cval];
  if (typeof newState === 'undefined') {
   return {currState: initState, result: acc.result};
  }
  if (newState === finalState) {
   // WARNING: not purly functional/immutable,
   // still implemenations of JS pure functional/immutable libraries
   // probaly use mutation under the hood, and just make it look pure,
   // this is what happens here also :)
   acc.result.push(cidx);
   return {currState: initState, result: acc.result};
  }
  return {currState: newState, result: acc.result};
 }, {currState: initState, result: []}).result;
}

console.log(getPatternPos(['i', 't', 't', 'i', 'i', 't', 't', 't'], ['i', 't', 't']));
// [2, 6]
console.log(getPatternPos(['i','t','i', 't', 't', 'i', 'i', 't', 't', 't'], ['i', 't', 't']));
// [4, 8]
console.log(getPatternPos(['a', 'b', 'a', 'b', 'b', 'a', 'b', 'c', 'd'], ['a', 'b', 'c']));
// [7]
console.log(getPatternPos("ababxabababcxxababc".split(""), "ababc".split("")));
// [11, 18]
console.log(getPatternPos("abababcx".split(""), "ababc".split("")));
.as-console-wrapper { max-height: 100% !important; top: 0; }