typescriptlistfunctional-programmingramda.js

Combine lists with absolute index in functional programming


I am looking for a more simple solution to combine a dictionary and a list into a single list in a functional programming style. The dictionary, list and expected result look something like this:

const myDictionary = new Map<string, number>([
  ['a', 4],
  ['b', 0],
  ['c', 3]
])

const myList = [
  'a',
  'b',
  'c'
]

const expectedResult = [
  [
    {'index': 0, 'payload': 'a0'},
    {'index': 1, 'payload': 'a1'},
    {'index': 2, 'payload': 'a2'},
    {'index': 3, 'payload': 'a3'}
  ],
  [],
  [
    {'index': 4, 'payload': 'c0'},
    {'index': 5, 'payload': 'c1'},
    {'index': 6, 'payload': 'c2'}
  ]
] 

As you can see, the number of the items of the inner list of expectedResult is variable. In this example it equals the values of the map myDictionary, but this is just an example here. The function to create those inner list items is deterministic, but the values have to be somehow computed, matched, combined, ...

My question is, how can I do this in a functional way. I could do something like this here (I'm using Randa in this project):

Given a function that would transform each list item:

const transformListItem = (char: string, offset: number, dictionary: Map<string, number>) =>
  range(0, dictionary.get(char)).map(i => ({
    'index': i + offset,
    'payload': `${char}${i}`
  }))

Then this would be how I would put it together:

const transformList = (list: string[], dictionary: Map<string, number>) =>

    list.map((char, listItemIndex) => {

        const listPriorToThisItem = slice(0, listItemIndex, list)
        const transformedListPriorThisItem = transformList(listPriorToThisItem, dictionary)
        const indexOffset = sum(transformedListPriorThisItem.map(a => a.length))

        return transformListItem(char, indexOffset, dictionary)
    }

const result = transformList(myList, myDictionary)

But with that approach the calculation would be done many times for each list item. So my question is: How can I achieve this without having to do this calculation twice (or several times). I'm aware of the possibility of memoization, but it seems to get even more complex then. The non-functional solution would be to iterate an index variable:


const transformList = (list: string[], dictionary: Map<string, number>) => {

  let indexOffset = 0

  return list.map(char => {
    const transformedItem = transformListItem(char, indexOffset, dictionary)
    indexOffset += transformedItem.length
    return transformedItem
  }
}

const result = transformList(myList, myDictionary)

Are there any more straightforward pattern in FP to achieve this?


Solution

  • The mapAccum function should allow you to map the items, while storing the index for the next item:

    const { range, mapAccum } = R
    
    const transformListItem = (char, offset, dictionary) =>
      range(0, dictionary.get(char)).map(i => ({
        'index': i + offset,
        'payload': `${char}${i}`
      }))
      
    const transformList = (list, dictionary) =>
      mapAccum(
        (index, char) => [
          index + dictionary.get(char),
          transformListItem(char, index, dictionary)
        ],
        0, 
        list
      )[1]
    
    const myDictionary = new Map([['a', 4], ['b', 0], ['c', 3]])
    const myList = ['a', 'b', 'c']
    
    const result = transformList(myList, myDictionary)
    
    console.log(result)
    <script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.29.1/ramda.min.js" integrity="sha512-PVSAmDFNqey8GMII8s9rjjkCFvUgzfsfi8FCRxQa3TkPZfdjCIUM+6eAcHIFrLfW5CTFAwAYS4pzl7FFC/KE7Q==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>