Search code examples
javascriptfunctional-programmingramda.js

Finding every second element in a repeating pattern


Data with repeated 'i's followed by 'i's and/or 't's.

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

Trying to retrieve the index of the last 't' in the pattern ['i','t','t']:

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

I'm looking for a non-recursive solution using (pure) functions only, using ramdajs for example.

Tried to use reduce and transduce, but unsuccessful sofar.


Solution

  • One approach would be to use R.aperture to iterate over a 3-element sliding window of the data list, then tracking the position of any sub-list that equals the pattern ['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>

    A point-free version of this approach could look something like the following, though whether this is preferable comes down to a preference of 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>