Convert multiple recursive calls into tail-recursion - javascript

Just wondering if a function like this can be done tail-recursively. I find it quite difficult because it calls itself twice.
Here is my non-tail-recursive implementation in javascript. (Yes I know most javascript engine doesn't support TCO, but this is just for theory.) The goal is to find all sublists of a certain length(size) of a given array(arr). Ex: getSublistsWithFixedSize([1,2,3] ,2) returns [[1,2], [1,3], [2,3]]
function getSublistsWithFixedSize(arr, size) {
if(size === 0) {
return [[]];
}
if(arr.length === 0 ) {
return [];
}
let [head, ...tail] = arr;
let sublists0 = getSublistsWithFixedSize(tail, size - 1);
let sublists1 = getSublistsWithFixedSize(tail, size);
let sublists2 = sublists0.map(x => {
let y = x.slice();
y.unshift(head);
return y;
});
return sublists1.concat(sublists2);
}

One such way is to use continuation-passing style. In this technique, an additional parameter is added to your function to specify how to continue the computation
Below we emphasize each tail call with /**/
function identity(x) {
/**/return x;
}
function getSublistsWithFixedSize(arr, size, cont = identity) {
if(size === 0) {
/**/ return cont([[]]);
}
if(arr.length === 0 ) {
/**/ return cont([]);
}
let [head, ...tail] = arr;
/**/return getSublistsWithFixedSize(tail, size - 1, function (sublists0) {
/**/ return getSublistsWithFixedSize(tail, size, function (sublists1) {
let sublists2 = sublists0.map(x => {
let y = x.slice();
y.unshift(head);
return y;
});
/**/ return cont(sublists1.concat(sublists2));
});
});
}
console.log(getSublistsWithFixedSize([1,2,3,4], 2))
// [ [ 3, 4 ], [ 2, 4 ], [ 2, 3 ], [ 1, 4 ], [ 1, 3 ], [ 1, 2 ] ]
You can think of the continuation almost like we invent our own return mechanism; only it's a function here, not a special syntax.
This is perhaps more apparent if we specify our own continuation at the call site
getSublistsWithFixedSize([1,2,3,4], 2, console.log)
// [ [ 3, 4 ], [ 2, 4 ], [ 2, 3 ], [ 1, 4 ], [ 1, 3 ], [ 1, 2 ] ]
Or even
getSublistsWithFixedSize([1,2,3,4], 2, sublists => sublists.length)
// 6
The pattern might be easier to see with a simpler function. Consider the famous fib
const fib = n =>
n < 2
? n
: fib (n - 1) + fib (n - 2)
console.log (fib (10))
// 55
Below we convert it to continuation-passing style
const identity = x =>
x
const fib = (n, _return = identity) =>
n < 2
? _return (n)
: fib (n - 1, x =>
fib (n - 2, y =>
_return (x + y)))
fib (10, console.log)
// 55
console.log (fib (10))
// 55
I want to remark that the use of .slice and .unshift is unnecessary for this particular problem. I'll give you an opportunity to come up with some other solutions before sharing an alternative.
Edit
You did a good job rewriting your program, but as you identified, there are still areas which it can be improved. One area I think you're struggling the most is by use of array mutation operations like arr[0] = x or arr.push(x), arr.pop(), and arr.unshift(x). Of course you can use these operations to arrive at the intended result, but in a functional program, we think about things in a different way. Instead of destroying an old value by overwriting it, we only read values and construct new ones.
We'll also avoid high level operations like Array.fill or uniq (unsure which implementation you chose) as we can build the result naturally using recursion.
The inductive reasoning for your recursive function is perfect, so we don't need to adjust that
if the size is zero, return the empty result [[]]
if the input array is empty, return an empty set, []
otherwise the size is at least one and we have at least one element x - get the sublists of one size smaller r1, get the sublists of the same size r2, return the combined result of r1 and r2 prepending x to each result in r1
We can encode this in a straightforward way. Notice the similarity in structure compared to your original program.
const sublists = (size, [ x = None, ...rest ], _return = identity) =>
size === 0
? _return ([[]])
: x === None
? _return ([])
: sublists // get sublists of 1 size smaller, r1
( size - 1
, rest
, r1 =>
sublists // get sublists of same size, r2
( size
, rest
, r2 =>
_return // return the combined result
( concat
( r1 .map (r => prepend (x, r)) // prepend x to each r1
, r2
)
)
)
)
We call it with a size and an input array
console.log (sublists (2, [1,2,3,4,5]))
// [ [ 1, 2 ]
// , [ 1, 3 ]
// , [ 1, 4 ]
// , [ 1, 5 ]
// , [ 2, 3 ]
// , [ 2, 4 ]
// , [ 2, 5 ]
// , [ 3, 4 ]
// , [ 3, 5 ]
// , [ 4, 5 ]
// ]
Lastly, we provide the dependencies identity, None, concat, and prepend - Below concat is an example of providing a functional interface to an object's method. This is one of the many techniques used to increase reuse of functions in your programs and help readability at the same time
const identity = x =>
x
const None =
{}
const concat = (xs, ys) =>
xs .concat (ys)
const prepend = (value, arr) =>
concat ([ value ], arr)
You can run the full program in your browser below
const identity = x =>
x
const None =
{}
const concat = (xs, ys) =>
xs .concat (ys)
const prepend = (value, arr) =>
concat ([ value ], arr)
const sublists = (size, [ x = None, ...rest ], _return = identity) =>
size === 0
? _return ([[]])
: x === None
? _return ([])
: sublists // get sublists of 1 size smaller, r1
( size - 1
, rest
, r1 =>
sublists // get sublists of same size, r2
( size
, rest
, r2 =>
_return // return the combined result
( concat
( r1 .map (r => prepend (x, r)) // prepend x to each r1
, r2
)
)
)
)
console.log (sublists (3, [1,2,3,4,5,6,7]))
// [ [ 1, 2, 3 ]
// , [ 1, 2, 4 ]
// , [ 1, 2, 5 ]
// , [ 1, 2, 6 ]
// , [ 1, 2, 7 ]
// , [ 1, 3, 4 ]
// , [ 1, 3, 5 ]
// , [ 1, 3, 6 ]
// , [ 1, 3, 7 ]
// , [ 1, 4, 5 ]
// , [ 1, 4, 6 ]
// , [ 1, 4, 7 ]
// , [ 1, 5, 6 ]
// , [ 1, 5, 7 ]
// , [ 1, 6, 7 ]
// , [ 2, 3, 4 ]
// , [ 2, 3, 5 ]
// , [ 2, 3, 6 ]
// , [ 2, 3, 7 ]
// , [ 2, 4, 5 ]
// , [ 2, 4, 6 ]
// , [ 2, 4, 7 ]
// , [ 2, 5, 6 ]
// , [ 2, 5, 7 ]
// , [ 2, 6, 7 ]
// , [ 3, 4, 5 ]
// , [ 3, 4, 6 ]
// , [ 3, 4, 7 ]
// , [ 3, 5, 6 ]
// , [ 3, 5, 7 ]
// , [ 3, 6, 7 ]
// , [ 4, 5, 6 ]
// , [ 4, 5, 7 ]
// , [ 4, 6, 7 ]
// , [ 5, 6, 7 ]
// ]

Here is my solution with the help of an accumulator. It's far from perfect but it works.
function getSublistsWithFixedSizeTailRecRun(arr, size) {
let acc= new Array(size + 1).fill([]);
acc[0] = [[]];
return getSublistsWithFixedSizeTailRec(arr, acc);
}
function getSublistsWithFixedSizeTailRec(arr, acc) {
if(arr.length === 0 ) {
return acc[acc.length -1];
}
let [head, ...tail] = arr;
//add head to acc
let accWithHead = acc.map(
x => x.map(
y => {
let z = y.slice()
z.push(head);
return z;
}
)
);
accWithHead.pop();
accWithHead.unshift([[]]);
//zip accWithHead and acc
acc = zipMerge(acc, accWithHead);
return getSublistsWithFixedSizeTailRec(tail, acc);
}
function zipMerge(arr1, arr2) {
let result = arr1.map(function(e, i) {
return uniq(e.concat(arr2[i]));
});
return result;
}

Related

Kyu 8 Code Wars - Finding the Sum of an array after removing the highest and lowest values

I am practising on code wars and am currently stuck on a kyu 8 question, all the tests seem to pass bar the last one. I will add my code and the tests below plus the output I get below.
function sumArray(array) {
if (array == null || array.length <= 2) {
return 0
} else {
let largestInt = Math.max.apply(null, array)
let smallestInt = Math.min.apply(null, array)
let indexSmallest = array.indexOf(largestInt)
let indexLargest = array.indexOf(smallestInt)
array.splice(indexSmallest, 1)
array.splice(indexLargest, 1)
let sum = 0
for (let i = 0; i < array.length; i++) {
sum += array[I]
}
return sum
}
}
The tests:
const {
assert
} = require("chai");
it("example tests", () => {
assert.strictEqual(sumArray(null), 0);
assert.strictEqual(sumArray([]), 0);
assert.strictEqual(sumArray([3]), 0);
assert.strictEqual(sumArray([3, 5]), 0);
assert.strictEqual(sumArray([6, 2, 1, 8, 10]), 16);
assert.strictEqual(sumArray([0, 1, 6, 10, 10]), 17);
assert.strictEqual(sumArray([-6, -20, -1, -10, -12]), -28);
assert.strictEqual(sumArray([-6, 20, -1, 10, -13]), 3);
});
The output:
Test Results:
example tests
expected -10 to equal 3
function total(array) {
// always assure at least an empty array.
array = Array.from(array ?? []);
// sort array values ascending.
array.sort((a, b) => a - b);
array.pop(); // remove last/higest value.
array.shift(); // remove first/lowest value.
// for any to be reduced/summed-up (empty) array
// the initial value of zero always assures the
// minimum expected result of zero.
return array
.reduce((total, value) => total + value, 0);
}
const testEntries = [
[ null, 0 ],
[ [ ], 0 ],
[ [ 3 ], 0 ],
[ [ 3, 5 ], 0 ],
[ [ 6, 2, 1, 8, 10 ], 16 ],
[ [ 0, 1, 6, 10, 10 ], 17 ],
[ [ -6, -20, -1, -10, -12 ], -28 ],
[ [ -6, 20, -1, 10, -13 ], 3 ],
];
console.log(
testEntries
.map(([value, result]) =>
`(total([${ value }]) === ${ result }) ... ${ total(value) === result }`
)
);
.as-console-wrapper { min-height: 100%!important; top: 0; }

Why is my Steinhaus-Johnson-Trotter Algorithm implementation producing duplicate permutations?

I have implemented the Steinhaus-Johnson-Trotter Algorithm using JavaScript, this implementation only calculates all permutations for arrays of numbers.
When the size of the array is >= 4 the algorithm is producing duplicate results. I see why it produces the results, but I am not sure how to avoid this, as creating these duplicates satisfies the algorithms principles.
class Direction {
constructor(dir) {
if(dir === 'LEFT' || dir === 'RIGHT') {
this.dir = dir
}
}
setDir(dir) {
if(dir === 'LEFT' || dir === 'RIGHT') {
this.dir = dir
}
}
switchDir() {
switch(this.dir) {
case 'LEFT':
this.dir = 'RIGHT'
break
case 'RIGHT':
this.dir = 'LEFT'
break
}
}
}
var permute = function(nums) {
if(nums.length === 1) return [nums]
if(nums.length === 2) return [nums, [nums[1], nums[0]]]
// I'm only worried about arrays up to length 6
const facts = [1, 2, 6, 24, 120, 720]
const dirs = {}
const max = Math.max(...nums)
nums.forEach(v => {
dirs[v] = new Direction('LEFT')
})
const res = []
const move = (n) => {
const i = nums.indexOf(n)
const ele = dirs[n]
switch(ele.dir) {
case 'LEFT':
[nums[i], nums[i - 1]] = [nums[i - 1], nums[i]]
break
case 'RIGHT':
[nums[i], nums[i + 1]] = [nums[i + 1], nums[i]]
break
}
if(n === max) {
return
}
nums.forEach(v => {
if(v > n) dirs[v].switchDir()
})
}
// Number is said to mobile if it can move to its direction
const isMobile = (n) => {
const d = dirs[n].dir
if(d === 'LEFT' && nums.indexOf(n) !== 0) {
return true
}
if(d === 'RIGHT' && nums.indexOf(n) !== nums.length - 1) {
return true
}
return false
}
// Finding mobiles means finding the largest number and checking if it is mobile
const findMobile = () => {
// If not max then lets find the next largest mobile
var num = Number.MIN_VALUE
nums.forEach(v => {
if(isMobile(v) && v > num) {
num = v
}
})
return num
}
// Loop through the max length factorial, included up to only 6 as req
while(res.length < facts[nums.length - 1]) {
const next = findMobile()
move(next)
res.push([...nums])
console.log(res)
}
return res
};
Test Cases:
Test 1:
Input: [1,2,3]
Result: [[1,3,2],[3,1,2],[3,2,1],[2,3,1],[2,1,3],[1,2,3]], Passed
Test 2:
Input: [5,4,6,2]
Result: [
[ 5, 6, 4, 2 ], [ 6, 5, 4, 2 ],
[ 5, 6, 4, 2 ], [ 5, 4, 6, 2 ],
[ 5, 4, 2, 6 ], [ 4, 5, 2, 6 ],
[ 4, 5, 6, 2 ], [ 4, 6, 5, 2 ],
[ 6, 4, 5, 2 ], [ 6, 4, 2, 5 ],
[ 4, 6, 2, 5 ], [ 4, 2, 6, 5 ],
[ 4, 2, 5, 6 ], [ 4, 2, 6, 5 ],
[ 4, 6, 2, 5 ], [ 6, 4, 2, 5 ],
[ 4, 6, 2, 5 ], [ 4, 2, 6, 5 ],
[ 4, 2, 5, 6 ], [ 4, 5, 2, 6 ],
[ 4, 5, 6, 2 ], [ 4, 6, 5, 2 ],
[ 6, 4, 5, 2 ], [ 6, 5, 4, 2 ]
], Failed
As shown in the results, the algorithm is producing duplicates, however as mentioned earlier, the steps between the duplicates satisfy the algorithm.
My Understanding of the Algorithm:
All elements start out facing right to left:
ie. <1<2<3<4
We find the next largest "mobile" number, in this case 4. Then we shift it towards its direction.
ie. <1<2<4<3
Repeat the process.
If a mobile number is moved that is less than another number, the larger number has its direction swapped.
EDIT
I have solved the problem, it involved in not checking the size of the mobile number and number to be swapped.
Compare steps with this simple Python implementation (ideone link to look at results, order is similar to wiki example ).
I don't see direction items swapping together with elements in your code
def SJTperms(a, dirs):
n = len(a)
id = -1
for i in range(n):
# can check mobility mobile largest mobile
if (0<=i+dirs[i]<n) and (a[i] > a[i+dirs[i]]) and ((id == -1) or (a[i] > a[id])):
id = i
if (id == -1): #last permutation
return False
for i in range(n):
if a[i] > a[id]:
dirs[i] = - dirs[i]
#swap elements AND their directions
a[id], a[id + dirs[id]] = a[id + dirs[id]], a[id]
t = dirs[id]
dirs[id], dirs[id + t] = dirs[id + t], dirs[id]
return True
a = [1,2,3,4]
d = [-1]*len(a)
cont = True
while cont:
print(a)
#print(d)
cont = SJTperms(a, d)

Why array is not iterable in reduce

I want to split array by even and odd elements, this is my code
A.reduce((a,v,i)=> v % 2 == 0 ? [...a[0],v] : [...a[1],v],[[],[]])
A is array of numbers. I don't understand why do I get an error
a[1] is not iterable?
Considering that this code is working ok:
let arr = [[],[]];
console.log([...arr[1], 4]);
You are only returning a single array in reduce(). You also need to return the second.
In the first iteration the a is [[],[]]. But after the first it will become only a single array.
let A = [1,2,3,4]
const res= A.reduce((a,v,i)=> v % 2 == 0 ? [a[0],[...a[1],v]] : [[...a[0],v],a[1]],[[],[]])
console.log(res)
You could use a trick here. As v % 2 will return 1 or 0 so you could push() to that and use , to return the original a without spread operator.
let A = [1,2,3,4]
const res= A.reduce((a,v,i)=> (a[v % 2].push(v),a),[[],[]])
console.log(res)
You could also just filter twice:
const res = [A.filter(it => it % 2), A.filter(it => !(it % 2))];
You can use destructuring assignment to make this a little easier -
const data =
[ 1, 2, 3, 4 ]
const result =
data.reduce
( ([ odd, even ], v) =>
Boolean (v & 1)
? [ [...odd, v], even ]
: [ odd, [...even, v] ]
, [ [], [] ]
)
console.log(result)
// [ [ 1, 3 ], [ 2, 4 ] ]
You can make a generic function, partition -
const partition = (p, a = []) =>
a.reduce
( ([ t, f ], v) =>
p (v)
? [ [...t, v], f ]
: [ t, [...f, v] ]
, [ [], [] ]
)
const evenOdds =
partition (v => Boolean (v & 1), [ 1, 2, 3, 4 ])
const lessThan2 =
partition (v => v < 2, [ 1, 2, 3, 4 ])
console.log(evenOdds)
// [ [ 1, 3 ], [ 2, 4 ] ]
console.log(lessThan2)
// [ [ 1 ], [ 2, 3, 4 ] ]
The problem with your solution is that in reduce function you return one array of many elements (not one array with two arrays). Try this instead (where B=[[],[]], time complexity n )
A.forEach(x=> B[x%2].push(x) )
let A=[1,2,3,4,5,6,7], B=[ [],[] ];
A.forEach(x=> B[x%2].push(x) );
console.log(B);

Take top X total items in a round robin from multiple arrays with Ramda

I have an array of arrays and want to write a function that returns the top x number of items, by taking items from each array in order.
Here is an example of what I'm after:
const input = [
["1a", "2a", "3a", "4a", "5a"],
["1b", "2b", "3b", "4b", "5b"],
["1c", "2c", "3c", "4c", "5c"],
["1d", "2d", "3d", "4d", "5d"]
];
const takeRoundRobin = count => arr => {
// implementation here
};
const actual = takeRoundRobin(5)(input);
const expected = [
"1a", "1b", "1c", "1d", "2a"
];
I saw a suggestion to a Scala question that solved this using zip but in Ramda you can only pass 2 lists to zip.
Here, Ramda's transpose can be your base. Add a dollop of unnest, a dash of take, and you get this:
const {take, unnest, transpose} = R
const takeRoundRobin = (n) => (vals) => take(n, unnest(transpose(vals)))
const input = [
['1a', '2a', '3a', '4a', '5a'],
['1b', '2b', '3b', '4b', '5b'],
['1c', '2c', '3c', '4c', '5c'],
['1d', '2d', '3d', '4d', '5d']
]
console.log(takeRoundRobin(5)(input))
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.js"></script>
Note also that this can handle arrays of varying lengths:
If you want to be able to wrap around to the beginning and continue taking values, you could replace take with a recursiveTake like this:
const {take, unnest, transpose, concat } = R
//recursive take
const recursiveTake = (n) => (vals) => {
const recur = (n,vals,result) =>
(n<=0)
? result
: recur(n-vals.length,vals,result.concat(take(n,vals)))
return recur(n,vals,[]);
};
const takeRoundRobin = (n) => (vals) =>
recursiveTake(n)(unnest(transpose(vals)));
const input = [
['1a', '2a', '3a', '4a'],
['1b'],
['1c', '2c', '3c', '4c', '5c'],
['1d', '2d']
]
console.log(takeRoundRobin(14)(input))
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.js"></script>
Another version of that function, without the explicit recursion would look like:
const takeCyclic = (n) => (vals) => take(
n,
unnest(times(always(vals), Math.ceil(n / (vals.length || 1))))
)
Here's one way you can do it using recursion –
const None =
Symbol ()
const roundRobin = ([ a = None, ...rest ]) =>
// base: no `a`
a === None
? []
// inductive: some `a`
: isEmpty (a)
? roundRobin (rest)
// inductive: some non-empty `a`
: [ head (a), ...roundRobin ([ ...rest, tail (a) ]) ]
It works in a variety of cases –
const data =
[ [ 1 , 4 , 7 , 9 ]
, [ 2 , 5 ]
, [ 3 , 6 , 8 , 10 , 11 , 12 ]
]
console.log (roundRobin (data))
// => [ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 ]
console.log (roundRobin ([ [ 1 , 2 , 3 ] ]))
// => [ 1 , 2 , 3 ]
console.log (roundRobin ([]))
// => []
Free variables are defined using prefix notation which is more familiar with functional style –
const isEmpty = xs =>
xs.length === 0
const head = xs =>
xs [0]
const tail = xs =>
xs .slice (1)
Verify it works in your browser below –
const None =
Symbol ()
const roundRobin = ([ a = None, ...rest ]) =>
a === None
? []
: isEmpty (a)
? roundRobin (rest)
: [ head (a), ...roundRobin ([ ...rest, tail (a) ]) ]
const isEmpty = xs =>
xs.length === 0
const head = xs =>
xs [0]
const tail = xs =>
xs .slice (1)
const data =
[ [ 1 , 4 , 7 , 9 ]
, [ 2 , 5 ]
, [ 3 , 6 , 8 , 10 , 11 , 12 ]
]
console.log (roundRobin (data))
// => [ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 ]
console.log (roundRobin ([ [ 1 , 2 , 3 ] ]))
// => [ 1 , 2 , 3 ]
console.log (roundRobin ([]))
// => []
Here's another way using a secondary parameter with default assignment –
const roundRobin = ([ a = None, ...rest ], acc = []) =>
// no `a`
a === None
? acc
// some `a`
: isEmpty (a)
? roundRobin (rest, acc)
// some non-empty `a`
: roundRobin
( append (rest, tail (a))
, append (acc, head (a))
)
const append = (xs, x) =>
xs .concat ([ x ])
To demonstrate what you may have seen as the implementation in other languages, the applicative instance for a ZipList can be used to transpose the array, where a ZipList applies the functions contained in the ZipList in a pair-wise manner with the corresponding ZipList of values unlike the standard permutative version of ap for lists.
const ZipList = xs => ({
getZipList: xs,
map: f => ZipList(R.map(f, xs)),
ap: other => ZipList(R.zipWith(R.applyTo, other.getZipList, xs))
})
ZipList.of = x => ZipList(new Proxy([], {
get: (target, prop) =>
prop == 'length' ? Infinity : /\d+/.test(prop) ? x : target[prop]
}))
This has an interesting requirement which is somewhat clunky to represent in JS, where the of function to produce a "pure" value needs to produce a ZipList containing a repeating list of the "pure" value, implemented here using a Proxy instance of an array.
The transpose can then be formed via:
xs => R.unnest(R.traverse(ZipList.of, ZipList, xs).getZipList)
After all of this, we have just reinvented R.transpose as per the answer from #scott-sauyet.
It is nevertheless an interesting implementation to be aware of.
(full example below)
const ZipList = xs => ({
getZipList: xs,
map: f => ZipList(R.map(f, xs)),
ap: other => ZipList(R.zipWith(R.applyTo, other.getZipList, xs))
})
ZipList.of = x => ZipList(new Proxy([], {
get: (target, prop) =>
prop == 'length' ? Infinity : /\d+/.test(prop) ? x : target[prop]
}))
const fn = xs => R.unnest(R.traverse(ZipList.of, ZipList, xs).getZipList)
const input = [
["1a", "2a", "3a", "4a", "5a"],
["1b", "2b", "3b", "4b", "5b"],
["1c", "2c", "3c", "4c", "5c"],
["1d", "2d", "3d", "4d", "5d"]
];
const expected = [
"1a", "1b", "1c", "1d", "2a"
];
const actual = R.take(5, fn(input))
console.log(R.equals(expected, actual))
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.min.js"></script>
Not sure what Ramda functions to use to address this particular problem but here is an answer not using Ramda that'll only work if all arrays are the same length:
const input = [
['1a', '2a', '3a', '4a', '5a'],
['1b', '2b', '3b', '4b', '5b'],
['1c', '2c', '3c', '4c', '5c'],
['1d', '2d', '3d', '4d', '5d'],
];
const takeRoundRobin = (count) => (arr) => {
const recur = (arr, current, count, result) =>
(current === count)
? result
: recur(
arr,
current + 1,
count,
result.concat(
arr
[current % arr.length]//x value
[//y value
Math.floor(current / arr.length) %
(arr.length + 1)
],
),
);
return recur(arr, 0, count, []);
};
console.log(takeRoundRobin(22)(input));

How can I prevent a tail recursive function from reversing the order of a List?

I am experimenting with the functional List type and structural sharing. Since Javascript doesn't have a Tail Recursive Modulo Cons optimization, we can't just write List combinators like this, because they are not stack safe:
const list =
[1, [2, [3, [4, [5, []]]]]];
const take = n => ([head, tail]) =>
n === 0 ? []
: head === undefined ? []
: [head, take(n - 1) (tail)];
console.log(
take(3) (list) // [1, [2, [3, []]]]
);
Now I tried to implement take tail recursively, so that I can either rely on TCO (still an unsettled Promise in Ecmascript) or use a trampoline (omitted in the example to keep things simple):
const list =
[1, [2, [3, [4, [5, []]]]]];
const safeTake = n => list => {
const aux = (n, acc, [head, tail]) => n === 0 ? acc
: head === undefined ? acc
: aux(n - 1, [head, acc], tail);
return aux(n, [], list);
};
console.log(
safeTake(3) (list) // [3, [2, [1, []]]]
);
This works but the new created list is in reverse order. How can I solve this issue in a purely functional manner?
Laziness gives you tail recursion modulo cons for free. Hence, the obvious solution is to use thunks. However, we don't just want any kind of thunk. We want a thunk for an expression in weak head normal form. In JavaScript, we can implement this using lazy getters as follows:
const cons = (head, tail) => ({ head, tail });
const list = cons(1, cons(2, cons(3, cons(4, cons(5, null)))));
const take = n => n === 0 ? xs => null : xs => xs && {
head: xs.head,
get tail() {
delete this.tail;
return this.tail = take(n - 1)(xs.tail);
}
};
console.log(take(3)(list));
There are lots of advantages to using lazy getters:
Normal properties and lazy properties are used in the same way.
You can use it to create infinite data structures.
You don't have to worry about blowing up the stack.
Hope that helps.
One way to prevent the list from reversing is to use continuation passing style. Now just put it on a trampoline of your choice...
const None =
Symbol ()
const identity = x =>
x
const safeTake = (n, [ head = None, tail ], cont = identity) =>
head === None || n === 0
? cont ([])
: safeTake (n - 1, tail, answer => cont ([ head, answer ]))
const list =
[ 1, [ 2, [ 3, [ 4, [ 5, [] ] ] ] ] ]
console.log (safeTake (3, list))
// [ 1, [ 2, [ 3, [] ] ] ]
Here it is on a trampoline
const None =
Symbol ()
const identity = x =>
x
const call = (f, ...values) =>
({ tag: call, f, values })
const trampoline = acc =>
{
while (acc && acc.tag === call)
acc = acc.f (...acc.values)
return acc
}
const safeTake = (n = 0, xs = []) =>
{
const aux = (n, [ head = None, tail ], cont) =>
head === None || n === 0
? call (cont, [])
: call (aux, n - 1, tail, answer =>
call (cont, [ head, answer ]))
return trampoline (aux (n, xs, identity))
}
const list =
[ 1, [ 2, [ 3, [ 4, [ 5, [] ] ] ] ] ]
console.log (safeTake (3, list))
// [ 1, [ 2, [ 3, [] ] ] ]

Categories