Recurse in Linked List - javascript

I have been practicing the linked list and wanted to implement the recurse on it, although in some cases I was able to implement it efficiently, in other cases I failed miserably at doing so. I would like to know a method to do the recursive so as not to have to use the "while" to go through the Linked List, I have used the recurse to go through the arrays but when I wanted to do it similar in this case it fails.
I don't have much experience in implementing recursion and wanted to apply it in this method to get more experience with it, but at least it helped me understand the Linked List more by having to do it over and over again. Thank you.
class Node {
// Accept arguments (the second one could be optional)
constructor(data, next) {
this.data = data;
this.next = next;
}
lastNode() { // new method that uses recursion
return this.next?.lastNode() || this;
}
}
class ListRecurse {
constructor() {
this.head = null;
this.size = 0;
}
add(data) {
let newNode = new Node(data); // No second argument. It has a default value
if (this.head === null) {
this.head = newNode;
} else {
// The lastNode implementation uses recursion:
this.head.lastNode().next = newNode;
}
this.size ++;
return this; // to allow chaining
}
insertAdd(data, index) {
if (index < 0 || index > this.size) {
return null;
}
let newNode = new Node(data);
let current = this.head;
let prev;
if (index === 0) {
newNode.next = current;
this.head = newNode;
}
else {
for (let i = 0; i < index; i++) {
prev = current;
current = current.next;
}
this.head.lastNode().next = current;
prev.next = newNode;
}
this.size++;
return this;
}
Print() {
if (!this.size) {
return null;
}
let current = this.head;
let result = "";
while(current) {
result += current.data += "=>";
current = current.next;
}
result += "X";
return result;
}
DeletexData(data) {
let current = this.head;
let prev = null;
if (this.head === null) {
return null;
}
else if (current.data === data) {
if(!prev) {
this.head = this.head.next;
}
else
prev.next = current.next
}
return this.SearchDelete(data)
}
SearchDelete (data) {
let current = this.head;
let prev = null;
while(current != null) {
if (current.data === data) {
if (!current.next) prev.next = null
else prev.next = current.next
this.size--;
return data;
}
prev = current;
current = current.next;
}
return null;
}
DeleteLastNode() {
let current = this.head;
if (current === null) {
return 1
}
else if (current.next === null) {
this.head = null;
}
else return this.LastNode()
};
LastNode() {
let current = this.head;
while (current.next.next != null) {
current = current.next;
}
current.next = null;
this.size--;
}
Search(data) {
let current = this.head;
if (current === null) {
return null;
}
else
return this.RainbowSix(data)
}
RainbowSix(data) {
let current = this.head;
while (current) {
if (current.data === data) {
return current;
}
current = current.next;
}
return null;
}
Size(){
return this.size
}
}
let list = new ListRecurse();
list.add(1).add(2).add(3).add(44).add(66);
list.insertAdd(33,0)
list.DeleteLastNode()
console.log(list.Search(3))
console.log(list.Size())
console.log(list.Print())
console.log(list);

This may or may not help. It suggests a substantially different way to build your lists.
The idea is that recursion, although occasionally used with Object-Oriented (OO) systems, is much more closely tied to Functional Programming (FP). So if you're going to use recursion on your lists, you might as well use it with FP lists.
Creating and manipulating lists is one of the strengths of FP, and we can write your code much more simply. We create a bare list of one item, 42 by calling const list1 = ins (42) (null). We prepend that with 17 by calling const list2 = ins (17) (list1). Or we can write a whole chain of these like this:
const list3 = ins (1) (ins (2) (ins (3) (ins (4) (ins (5) (null)))))
There are many differences from your code, but one of the most fundamental, is that this treats lists as immutable objects. None of our code will change a list, it will just create a new one with the altered properties.
This is what ins might look like:
const ins = (data) => (list) =>
({data, next: list})
We could choose to write this as (data, list) => ... instead of (data) => (list) => .... That's just a matter of personal preference about style.
But the basic construction is that a list is
a value
followed by either
another list
or null
Here is an implementation of these ideas:
const ins = (data) => (list) =>
({data, next: list})
const del = (target) => ({data, next}) =>
target == data ? next : next == null ? {data, next} : {data, next: del (target) (next)}
const delLast = ({data, next}) =>
next == null ? null : {data, next: delLast (next)}
const size = (list) =>
list == null ? 0 : 1 + size (list.next)
const search = (pred) => ({data, next}) =>
pred (data) ? {data, next} : next != null ? search (pred) (next) : null
const fnd = (target) =>
search ((data) => data == target)
const print = ({data, next}) =>
data + (next == null ? '' : ('=>' + print (next)))
const list1 = ins (1) (ins (2) (ins (3) (ins (44) (ins (66) (null)))))
const list2 = ins (33) (list1)
const list3 = delLast (list2)
console .log (fnd (3) (list3))
console .log (size (list3))
console .log (print (list3))
console .log (list3)
.as-console-wrapper {max-height: 100% !important; top: 0}
Note that all of these functions, except for ins and find are directly recursive. They all call themselves. And find simply delegates the recursive work to search.
It's too much to try to describe all of these functions, but lets look at two. print is a simple function.
const print = ({data, next}) =>
data + (next == null ? '' : ('=>' + print (next)))
We build our output string by including our data followed by one of two things:
an empty string, if next is null
'=>' plus the recursive print call on next, otherwise.
del is a somewhat more complex function:
const del = (target) => ({data, next}) =>
target == data
? next
: next == null
? {data, next: null}
: {data, next: del (target) (next)}
We test if our current data is the target we want to delete. If it is, we simply return the list stored as next.
If not, we check whether next is null. If it is, we return (a copy of) the current list. If it is not, then we return a new list formed by our current data and a recursive call to delete the target from the list stored as next.
If you want to learn more about these ideas, you probably want to search for "Cons lists" ("con" here is not the opposite of "pro", but has to do with "construct"ing something.)
I used different terms than are most commonly used there, but the ideas are much the same. If you run across the terms car and cdr, they are equivalent to our data and next, respectively.

I was working on this answer as Scott made his post, making most of this information redundant. There is a portion which shows how to couple OOP-style with functional (persistent) data structures which you should find helpful.
Similar to Scott's answer, we start by writing plain functions, no classes or methods. I'm going to place mine in a module named list.js -
// list.js
import { raise } from "./func"
const nil =
Symbol("nil")
const isNil = t =>
t === nil
const node = (value, next) =>
({ node, value, next })
const singleton = v =>
node(v, nil)
const fromArray = a =>
a.reduceRight((r, _) => node(_, r), nil)
const insert = (t, v, i = 0) =>
isNil(t)
? singleton(v)
: i > 0
? node(t.value, insert(t.next, v, i - 1))
: node(v, t)
const last = t =>
isNil(t)
? raise("cannot get last element of empty list")
: isNil(t.next)
? t.value
: last(t.next)
const search = (t, q) =>
isNil(t)
? undefined
: t.value === q
? t
: search(t.next, q)
const size = t =>
isNil(t)
? 0
: 1 + size(t.next)
const toString = t =>
isNil(t)
? "Nil"
: `${t.value}->${toString(t.next)}`
const toArray = t =>
isNil(t)
? []
: [ t.value, ...toArray(t.next) ]
Now we can implement our OOP-style, List interface. This gives you the chaining behaviour you want. Notice how the methods are simple wrappers around the plain functions we wrote earlier -
// list.js (continued)
class List
{ constructor(t = nil)
{ this.t = t }
isNil()
{ return isNil(this.t) }
size()
{ return size(this.t) }
add(v)
{ return new List(node(v, this.t)) }
insert(v, i)
{ return new List(insert(this.t, v, i)) }
toString()
{ return toString(this.t) }
}
Finally, make sure to export the parts of your module
// list.js (continued)
export { nil, isNil, node, singleton, fromArray, insert, last, search, size, toArray, toString }
export default List
The List interface allows you to do things in the familiar OOP ways -
import List from "../list"
const t = (new List).add(3).add(2).add(1)
console.log(t.toString())
// 1->2->3->Nil
console.log(t.insert(9, 0).toString())
// 9->1->2->3->Nil
console.log(t.isNil())
// false
console.log(t.size())
// 3
Or you can import your module and work in a more functional way -
import * as list from "../list"
const t = list.fromArray([1, 2, 3])
console.log(list.toString(t))
// 1->2->3->Nil
console.log(list.isNil(t))
// true
console.log(list.size(t))
// 3
I think the important lesson here is that the module functions can be defined once, and then the OOP interface can be added afterwards.
A series of tests ensures the information in this answer is correct. We start writing the plain function tests -
// list_test.js
import List, * as list from "../list.js"
import * as assert from '../assert.js'
import { test, symbols } from '../test.js'
await test("list.isNil", _ => {
assert.pass(list.isNil(list.nil))
assert.fail(list.isNil(list.singleton(1)))
})
await test("list.singleton", _ => {
const [a] = symbols()
const e = list.node(a, list.nil)
assert.equal(e, list.singleton(a))
})
await test("list.fromArray", _ => {
const [a, b, c] = symbols()
const e = list.node(a, list.node(b, list.node(c, list.nil)))
const t = [a, b, c]
assert.equal(e, list.fromArray(t))
})
await test("list.insert", _ => {
const [a, b, c, z] = symbols()
const t = list.fromArray([a, b, c])
assert.equal(list.fromArray([z,a,b,c]), list.insert(t, z, 0))
assert.equal(list.fromArray([a,z,b,c]), list.insert(t, z, 1))
assert.equal(list.fromArray([a,b,z,c]), list.insert(t, z, 2))
assert.equal(list.fromArray([a,b,c,z]), list.insert(t, z, 3))
assert.equal(list.fromArray([a,b,c,z]), list.insert(t, z, 99))
assert.equal(list.fromArray([z,a,b,c]), list.insert(t, z, -99))
})
await test("list.toString", _ => {
const e = "1->2->3->Nil"
const t = list.fromArray([1,2,3])
assert.equal(e, list.toString(t))
assert.equal("Nil", list.toString(list.nil))
})
await test("list.size", _ => {
const [a, b, c] = symbols()
assert.equal(0, list.size(list.nil))
assert.equal(1, list.size(list.singleton(a)))
assert.equal(2, list.size(list.fromArray([a,b])))
assert.equal(3, list.size(list.fromArray([a,b,c])))
})
await test("list.last", _ => {
const [a, b, c] = symbols()
const t = list.fromArray([a,b,c])
assert.equal(c, list.last(t))
assert.throws(Error, _ => list.last(list.nil))
})
await test("list.search", _ => {
const [a, b, c, z] = symbols()
const t = list.fromArray([a, b, c])
assert.equal(t, list.search(t, a))
assert.equal(list.fromArray([b, c]), list.search(t, b))
assert.equal(list.singleton(c), list.search(t, c))
assert.equal(undefined, list.search(t, z))
})
await test("list.toArray", _ => {
const [a,b,c] = symbols()
const e = [a,b,c]
const t = list.fromArray(e)
assert.equal(e, list.toArray(t))
})
Next we ensure the List interface behaves accordingly -
// list_test.js (continued)
await test("List.isNil", _ => {
assert.pass((new List).isNil())
assert.fail((new List).add(1).isNil())
})
await test("List.size", _ => {
const [a,b,c] = symbols()
const t1 = new List
const t2 = t1.add(a)
const t3 = t2.add(b)
const t4 = t3.add(c)
assert.equal(0, t1.size())
assert.equal(1, t2.size())
assert.equal(2, t3.size())
assert.equal(3, t4.size())
})
await test("List.toString", _ => {
const t1 = new List
const t2 = (new List).add(3).add(2).add(1)
assert.equal("Nil", t1.toString())
assert.equal("1->2->3->Nil", t2.toString())
})
await test("List.insert", _ => {
const t = (new List).add(3).add(2).add(1)
assert.equal("9->1->2->3->Nil", t.insert(9, 0).toString())
assert.equal("1->9->2->3->Nil", t.insert(9, 1).toString())
assert.equal("1->2->9->3->Nil", t.insert(9, 2).toString())
assert.equal("1->2->3->9->Nil", t.insert(9, 3).toString())
assert.equal("1->2->3->9->Nil", t.insert(9, 99).toString())
assert.equal("9->1->2->3->Nil", t.insert(9, -99).toString())
})
Dependencies used in this post -
func.raise - allows you to raise an error using an expression instead of a throw statement
// func.js
const raise = (msg = "", E = Error) => // functional throw
{ throw E(msg) }
// ...
export { ..., raise }

Related

Rank Order Voting function returning the wrong output

I am building a function that count Rank Order Ballots and returns the winner. The rules of this are that if a candidate has a clear majority then the candidate wins the election.
If not, we remove all reference to that candidate and whichever ballots the candidate go are assigned to whoever came second
So for example if we have this
const sample = { "A,B,C": 4, "B,C,A": 3, "C,B,A": 2};
Since C has the least number of votes and noone has a majority, all votes C won are then assigned to B, giving B the majority.
This is what I have written:
function removeLowestVotedCandidate(ballots) {
let lowestVotes = Object.entries(ballots).reduce((largestVal, comparison) =>comparison[1] < largestVal[1] ? comparison
:largestVal)[0]
.split('')[0]
//remove lowest voted candidate from object
const newRankedOrderBallots = JSON.parse(
JSON.stringify(ballots)
.replaceAll(`${lowestVotes}`, '')
.replaceAll(/(^[,\s]+)/g, '')
)
//remove leading commas
return Object.fromEntries(Object.entries(newRankedOrderBallots).map(([key, value]) => [key.replace(/^,+/,''), value]))
}
// console.log(removeLowestVotedCandidate(sample))
function getRankedChoiceWinner(ballots) {
let sum = 0
let winnerFound = false
let stretchWin = 0;
let sumByChar = {};
let winner = []
let updatedBallot = ballots
while(winnerFound === false) {
//count overall votes
for(let votes of Object.values(updatedBallot)){
sum +=votes
}
//calculate what is required for a clear majority
stretchWin = Math.round(sum/2)
//count votes assigned to each candidate
for(const[key, val] of Object.entries(updatedBallot)) {
const char = key[0];
sumByChar[char] = (sumByChar[char] ?? 0) + val;
}
console.log('sumByChar is currently', sumByChar)
//check if any candidate has a clear majority
winner = Object.entries(sumByChar)
.filter(([, val]) => val >= stretchWin)
.map(([keys]) => keys)
console.log('winner is currently', winner)
if (winner.length === 1) {
winnerFound = true
} else {
updatedBallot = removeLowestVotedCandidate(updatedBallot)
console.log('we are inside else', updatedBallot)
}
}
return winner
}
However, I seem to be getting the wrong answer, I am getting A as opposed to B. This is what is happening with my console.logs
sumByChar is currently { A: 4, B: 3, C: 2 }
winner is currently []
we are inside else { 'A,B,': 4, 'B,,A': 3, 'B,A': 2 }
sumByChar is currently { A: 8, B: 6, C: 4 }
winner is currently []
we are inside else { 'A,,': 4, A: 2 }
sumByChar is currently { A: 12, B: 9, C: 6 }
winner is currently [ 'A' ]
[ 'A' ]
It seems sumByChar is not reseting to zero and instead
There are 2 issues:
Your sumByChar is created outside the loop and mutated inside the loop. Every time there's a new iteration, you add additional values to it. Create the object inside the loop instead, so you get the sum only for the current ballots, not the cumulative sum for all iterations so far.
Your sum variable is also declared outside the loop, and you're adding to it inside the loop. Declare it inside the loop instead.
Also, the input structure is pretty badly designed for something like this. I'd highly recommend restructuring it to make removing candidates easier - using JSON.stringify and a regex just to remove something is extremely suspicious.
const sample = {
"A,B,C": 4,
"B,C,A": 3,
"C,B,A": 2
};
function removeLowestVotedCandidate(ballots) {
let lowestVotes = Object.entries(ballots).reduce((largestVal, comparison) => comparison[1] < largestVal[1] ? comparison :
largestVal)[0]
.split('')[0]
//remove lowest voted candidate from object
const newRankedOrderBallots = JSON.parse(
JSON.stringify(ballots)
.replaceAll(`${lowestVotes}`, '')
.replaceAll(/(^[,\s]+)/g, '')
)
//remove leading commas
return Object.fromEntries(Object.entries(newRankedOrderBallots).map(([key, value]) => [key.replace(/^,+/, ''), value]))
}
// console.log(removeLowestVotedCandidate(sample))
function getRankedChoiceWinner(ballots) {
let winnerFound = false
let stretchWin = 0;
let winner = []
let updatedBallot = ballots
while (winnerFound === false) {
let sumByChar = {};
//count overall votes
let sum = 0
for (let votes of Object.values(updatedBallot)) {
sum += votes
}
//calculate what is required for a clear majority
stretchWin = Math.round(sum / 2)
//count votes assigned to each candidate
for (const [key, val] of Object.entries(updatedBallot)) {
const char = key[0];
sumByChar[char] = (sumByChar[char] ?? 0) + val;
}
// console.log('sumByChar is currently', sumByChar)
//check if any candidate has a clear majority
winner = Object.entries(sumByChar)
.filter(([, val]) => val >= stretchWin)
.map(([keys]) => keys);
console.log('winner is currently', winner)
if (winner.length === 1) {
winnerFound = true
} else {
updatedBallot = removeLowestVotedCandidate(updatedBallot)
// console.log('we are inside else', updatedBallot)
}
}
return winner
}
console.log(getRankedChoiceWinner(sample));
Or, refactored to look halfway decent IMO:
const sample = {
"A,B,C": 4,
"B,C,A": 3,
"C,B,A": 2
};
const getRankedChoiceWinner = badBallots => checkOneBallot(restructureBallots(badBallots));
const restructureBallots = badBallots => Object.entries(badBallots)
.map(([candidatesStr, votes]) => [candidatesStr.split(','), votes]);
const checkOneBallot = (ballots) => {
const sumVotes = ballots.reduce((a, b) => a + b[1], 0);
const sumByCandidate = {};
for (const [candidates, voteCount] of ballots) {
const candidate = candidates[0];
sumByCandidate[candidate] = (sumByCandidate[candidate] ?? 0) + voteCount;
}
const winningEntry = Object.entries(sumByCandidate).find(([, val]) => val >= sumVotes / 2);
if (winningEntry) return winningEntry[0][0];
return removeLowestAndRetry(ballots, sumByCandidate);
};
const removeLowestAndRetry = (ballots, sumByCandidate) => {
const lowestVal = Math.min(...Object.values(sumByCandidate));
const lowestCandidateEntry = Object.entries(sumByCandidate).reduce(
(a, entry) => entry[1] < a[1] ? entry : a,
['', Infinity]
);
const lowestCandidate = lowestCandidateEntry[0];
for (const ballot of ballots) {
ballot[0] = ballot[0].filter(candidate => candidate !== lowestCandidate);
}
return checkOneBallot(ballots);
};
console.log(getRankedChoiceWinner(sample));

Trying to solve Lowest common ancestor

var lowestCommonAncestor = function(root, p, q) {
// return the path to the node
let path = []
const search = (node, target) => {
if (node === null) return false
path.push(node)
if (node === target) return true
const leftSearched = search(node.left, target)
if (leftSearched) return true
const rightSearched = search(node.right,target)
if (rightSearched) return true
path.pop()
}
search(root, p)
const pathP = path
path = []
search(root, q)
const pathQ = path
let result
while(pathP.length > 0 && pathQ.length > 0 && pathP[0] === pathQ[0]) {
result = pathP[0]
pathP.shift()
pathQ.shift()
}
return result
};
console.log(lowestCommonAncestor([3,5,1,6,2,0,8,null,null,7,4],5,1));
Iam getting following error message
const leftSearched = search(node.left, target)
^
TypeError: Cannot read property 'left' of undefined
Could someone help me to fix this issue
Leet Code, as some other code challenge sites, will transform the array input (actually the text input having JSON notation) into an instance of TreeNode, and will pass that as argument to the function with your solution code.
When you want to run the solution locally, you'll have to take care of this transformation yourself. For that purpose you could make use of the fromList function -- specifically for binary trees.
NB: you have a bug in your search function. if (node === target) should be if (node.val === target).
// LeetCode template:
function TreeNode(val) {
this.val = val;
this.left = this.right = null;
}
// Tool to convert array input to a tree:
function fromList(values) {
if (!values) return;
let it = (function* () {
for (let value of values) {
yield value == null ? null : new TreeNode(value);
}
while (true) yield null;
})();
let root = it.next().value;
let nextlevel = [root];
while (nextlevel.length) {
let level = nextlevel;
nextlevel = [];
for (let node of level) {
if (node) {
node.left = it.next().value;
node.right = it.next().value;
nextlevel.push(node.left, node.right);
}
}
}
return root;
}
// Your function
var lowestCommonAncestor = function(root, p, q) {
// return the path to the node
let path = []
const search = (node, target) => {
if (node === null) return false;
path.push(node);
if (node.val === target) return true;
const leftSearched = search(node.left, target);
if (leftSearched) return true;
const rightSearched = search(node.right,target);
if (rightSearched) return true;
path.pop();
}
search(root, p);
const pathP = path;
path = [];
search(root, q);
const pathQ = path;
let result;
while(pathP.length > 0 && pathQ.length > 0 && pathP[0] === pathQ[0]) {
result = pathP[0];
pathP.shift();
pathQ.shift();
}
return result;
};
// Running your solution on some input
let input = [3,5,1,6,2,0,8,null,null,7,4];
// Make the conversion that LeetCode would do
let root = fromList(input);
let lca = lowestCommonAncestor(root,5,1);
// For your own purposes, just print the value of that node:
console.log(lca.val); // 3

This question is about an exercise in the book Eloquent JavaScript

The last part to this exercise is to write a recursive function that takes two parameters, a joined list and an index respectively. The function will find the value in the object within the list at it's respective index. The code i have written works the way i want (i can see it working when i console.log for every occasion the function is called. But on the last occasion it refers undefined as my value. I cannot understand why. Oh and it works for index of 0. code as followed.
and first, list looks like this:
list = {
value: 1,
rest: {
value: 2,
rest: {
value: 3,
rest: null
}
}
};
const nth = (list, targetNum) => {
let value = Object.values(list)[0];
if (targetNum == 0) {
return value;
} else {
targetNum = targetNum -1;
list = Object.values(list)[1];
// console.log(value);
// console.log(targetNum);
// console.log(list);
nth(list, targetNum);
}
};
console.log(nth(arrayToList([1,2,3]),2));
below is the code for arrayToList it was the first part of the exercise and if you have any comments that's cool, cause the hints ended up suggesting to build the list from the end.
const arrayToList = (arr) => {
let list = {
value: arr[0],
rest: nestObject()
};
function nestObject() {
let rest = {};
arr.shift();
const length = arr.length;
if (length == 1) {
rest.value = arr[0];
rest.rest = null;
} else {
rest.value = arr[0];
rest.rest = nestObject();
}
return rest;
}
return list;
};
Both solutions are convoluted and unnecessary verbose. Actually, both functions could be one-liners. Here are a few hints:
For the toList thing consider the following:
if the input array is empty, return null (base case)
otherwise, split the input array into the "head" (=the first element) and "tail" (=the rest). For example, [1,2,3,4] => 1 and [2,3,4]
return an object with value equal to "head" and rest equal to toList applied to the "tail" (recursion)
On a more advanced note, the split can be done right in the function signature with destructuring:
const toList = ([head=null, ...tail]) => ...
Similarly for nth(list, N)
if N is zero, return list.value (base case)
otherwise, return an application of nth with arguments list.rest and N-1 (recursion)
Again, the signature can benefit from destructuring:
const nth = ({value, rest}, n) =>
Full code, if you're interested:
const toList = ([value = null, ...rest]) =>
value === null
? null
: {value, rest: toList(rest)}
const nth = ({value, rest}, n) =>
n === 0
? value
: nth(rest, n - 1)
//
let lst = toList(['a', 'b', 'c', 'd', 'e', 'f'])
// or simply toList('abcdef')
console.log(lst)
console.log(nth(lst, 0))
console.log(nth(lst, 4))
You simply need to add a return when recursively calling nth. Otherwise the logic is carried out but no value is returned (unless targetNum is 0)
const nth = (list, targetNum) => {
let value = Object.values(list)[0];
if (targetNum == 0) {
return value;
} else {
targetNum = targetNum -1;
list = Object.values(list)[1];
return nth(list, targetNum); // return needed here too
}
};
Or more succinctly:
const nth = (list, n) => n === 0 ? list.value : nth(list.rest, n - 1)
Here's another non-recursive arrayToList that builds the list from the end:
const arrayToList = arr => arr.slice().reverse().reduce((rest, value) => ({value, rest}), null);
(The slice here is just to make a copy of the array so that the original is not reversed in place.)
Georg’s recursive solutions are beautiful!
I’d like to add the hinted “build the list from the end” solution from the book:
const arrayToList => (arr) => {
var list
while (arr.length) {
list = {value: arr.pop(), rest: list}
}
return list
}

Exit function on subfunction result

I wonder if there is a method to exit function based on other function output.
In other words: I have a function that evaluates some values and if they are fine returns them, otherwise causes a warning to user.
const verifier = () => {
const a = Number(document.querySelector("#a").value);
const b = Number(document.querySelector("#b").value);
if(isNaN(a) || isNaN(b)) {
console.log("Sent notification to user");
return
}
return [a, b]
}
Now, I use it multiple times, e.g.:
const adder = () => {
const [a,b] = verifier() || return; // <--- HERE!
console.log(a);
console.log(b);
}
In HERE: if verification function returns false / undefined / null I would like to exit my function prematurely, otherwise: assign to variables.
As I wrote in my comment, if you're willing to introduce an intermediary helper, you could write it as follows:
const verifier = () => {
const a = Number(document.querySelector("#a").value);
const b = Number(document.querySelector("#b").value);
if(isNaN(a) || isNaN(b)) {
console.log("Sent notification to user");
return;
}
return [a, b];
}
// helper that only calls `fn` if verifier does not return undefined
const ifVerified = (fn) => {
const values = verifier();
return undefined === values ? undefined : fn(values);
};
const adder = () => ifVerified(([a, b]) => {
// do something with a and b
});
You can't use return where an expression is expected, because return is a statement.
There are a bunch of other ways to do it, but they're all going to be clunkier than you're going to want. :-) For instance, you could have verifier return the NaN values and then test the result:
const verifier = () => {
const a = Number(document.querySelector("#a").value);
const b = Number(document.querySelector("#b").value);
if (isNaN(a) || isNaN(b)) {
console.log("Sent notification to user");
return [NaN, NaN];
}
return [a, b];
};
const adder = () => {
const [a,b] = verifier();
if (isNaN(a)) {
return;
}
console.log(a);
console.log(b);
};
Or you could leave verifier as it is and use nullish coalescing (quite new) at the point where you call it:
const adder = () => {
const [a, b] = verifier() ?? [];
if (a === undefined) {
return;
}
console.log(a);
console.log(b);
};
perhaps with an explicit flag value as a default:
const adder = () => {
const [a = null, b] = verifier() ?? [];
if (a === null) {
return;
}
console.log(a);
console.log(b);
};
Or store the returned array/undefined and test that:
const adder = () => {
const result = verifier();
if (!result) {
return;
}
const [a, b] = result;
console.log(a);
console.log(b);
};
But they all involve that if. (Or you could throw.)

How to correctly serialize Javascript curried arrow functions?

const makeIncrementer = s=>a=>a+s
makeIncrementer(10).toString() // Prints 'a=>a+s'
which would make it impossible to de-serialize correctly (I would expect something like a=>a+10 instead.
Is there a way to do it right?
This is a great question. While I don't have a perfect answer, one way you could get details about the argument/s is to create a builder function that stores the necessary details for you. Unfortunately I can't figure out a way to know which internal variables relate to which values. If I figure out anything else i'll update:
const makeIncrementer = s => a => a + s
const builder = (fn, ...args) => {
return {
args,
curry: fn(...args)
}
}
var inc = builder(makeIncrementer, 10)
console.log(inc) // logs args and function details
console.log(inc.curry(5)) // 15
UPDATE: It will be a mammoth task, but I realised, that if you expand on the builder idea above, you could write/use a function string parser, that could take the given args, and the outer function, and rewrite the log to a serialised version. I have a demo below, but it will not work in real use cases!. I have done a simple string find/replace, while you will need to use an actual function parser to replace correctly. This is just an example of how you could do it. Note that I also used two incrementer variables just to show how to do multiples.
function replaceAll(str, find, replace) {
return str.replace(new RegExp(find, 'g'), replace)
}
const makeIncrementer = (a, b) => c => c + a + b
const builder = (fn, ...args) => {
// get the outer function argument list
var outers = fn.toString().split('=>')[0]
// remove potential brackets and spaces
outers = outers.replace(/\(|\)/g,'').split(',').map(i => i.trim())
// relate the args to the values
var relations = outers.map((name, i) => ({ name, value: args[i] }))
// create the curry
var curry = fn(...args)
// attempt to replace the string rep variables with their true values
// NOTE: **this is a simplistic example and will break easily**
var serialised = curry.toString()
relations.forEach(r => serialised = replaceAll(serialised, r.name, r.value))
return {
relations,
serialised,
curry: fn(...args)
}
}
var inc = builder(makeIncrementer, 10, 5)
console.log(inc) // shows args, serialised function, and curry
console.log(inc.curry(4)) // 19
You shouldn't serialize/parse function bodies since this quickly leads to security vulnerabilities. Serializing a closure means to serialize its local state, that is you have to make the closure's free variables visible for the surrounding scope:
const RetrieveArgs = Symbol();
const metaApply = f => x => {
const r = f(x);
if (typeof r === "function") {
if (f[RetrieveArgs])
r[RetrieveArgs] = Object.assign({}, f[RetrieveArgs], {x});
else r[RetrieveArgs] = {x};
}
return r;
}
const add = m => n => m + n,
f = metaApply(add) (10);
console.log(
JSON.stringify(f[RetrieveArgs]) // {"x":10}
);
const map = f => xs => xs.map(f)
g = metaApply(map) (n => n + 1);
console.log(
JSON.stringify(g[RetrieveArgs]) // doesn't work with higher order functions
);
I use a Symbol in order that the new property doesn't interfere with other parts of your program.
As mentioned in the code you still cannot serialize higher order functions.
Combining ideas from the two answers so far, I managed to produce something that works (though I haven't tested it thoroughly):
const removeParentheses = s => {
let match = /^\((.*)\)$/.exec(s.trim());
return match ? match[1] : s;
}
function serializable(fn, boundArgs = {}) {
if (typeof fn !== 'function') return fn;
if (fn.toJSON !== undefined) return fn;
const definition = fn.toString();
const argNames = removeParentheses(definition.split('=>', 1)[0]).split(',').map(s => s.trim());
let wrapper = (...args) => {
const r = fn(...args);
if (typeof r === "function") {
let boundArgsFor_r = Object.assign({}, boundArgs);
argNames.forEach((name, i) => {
boundArgsFor_r[name] = serializable(args[i]);
});
return serializable(r, boundArgsFor_r);
}
return r;
}
wrapper.toJSON = function () {
return { function: { body: definition, bound: boundArgs } };
}
return wrapper;
}
const add = m => m1 => n => m + n * m1,
fn = serializable(add)(10)(20);
let ser1, ser2;
console.log(
ser1 = JSON.stringify(fn) // {"function":{"body":"n => m + n * m1","bound":{"m":10,"m1":20}}}
);
const map = fn => xs => xs.map(fn),
g = serializable(map)(n => n + 1);
console.log(
ser2 = JSON.stringify(g) // {"function":{"body":"xs => xs.map(fn)","bound":{"fn":{"function":{"body":"n => n + 1","bound":{}}}}}}
);
const reviver = (key, value) => {
if (typeof value === 'object' && 'function' in value) {
const f = value.function;
return eval(`({${Object.keys(f.bound).join(',')}}) => (${f.body})`)(f.bound);
}
return value;
}
const rev1 = JSON.parse(ser1, reviver);
console.log(rev1(5)); // 110
const rev2 = JSON.parse(ser2, reviver);
console.log(rev2([1, 2, 3])); // [2, 3, 4]
This works for arrow functions, that do not have default initializers for the arguments. It supports higher order functions as well.
One still has to be able to wrap the original function into serializable before applying it to any arguments though.
Thank you #MattWay and #ftor for valuable input !

Categories

Resources