How to resolve the algorithm Brace expansion step by step in the JavaScript programming language
How to resolve the algorithm Brace expansion step by step in the JavaScript programming language
Table of Contents
Problem Statement
Brace expansion is a type of parameter expansion made popular by Unix shells, where it allows users to specify multiple similar string parameters without having to type them all out. E.g. the parameter enable_{audio,video} would be interpreted as if both enable_audio and enable_video had been specified.
Write a function that can perform brace expansion on any input string, according to the following specification. Demonstrate how it would be used, and that it passes the four test cases given below. In the input string, balanced pairs of braces containing comma-separated substrings (details below) represent alternations that specify multiple alternatives which are to appear at that position in the output. In general, one can imagine the information conveyed by the input string as a tree of nested alternations interspersed with literal substrings, as shown in the middle part of the following diagram: This tree can in turn be transformed into the intended list of output strings by, colloquially speaking, determining all the possible ways to walk through it from left to right while only descending into one branch of each alternation one comes across (see the right part of the diagram). When implementing it, one can of course combine the parsing and expansion into a single algorithm, but this specification discusses them separately for the sake of clarity. Expansion of alternations can be more rigorously described by these rules: Parsing the input string involves some additional complexity to deal with escaped characters and "incomplete" brace pairs: For every possible input string, your implementation should produce exactly the output which this specification mandates. Please comply with this even when it's inconvenient, to ensure that all implementations are comparable. However, none of the above should be interpreted as instructions (or even recommendations) for how to implement it. Try to come up with a solution that is idiomatic in your programming language. (See #Perl for a reference implementation.)
Let's start with the solution:
Step by Step solution about How to resolve the algorithm Brace expansion step by step in the JavaScript programming language
This JavaScript code provides a function called expansions
that takes a braced expression string as input and returns a list of all possible expansions of the expression. For example, the expression ~/{Downloads,Pictures}/*.{jpg,gif,png}
would expand to the following list of strings:
~/{Downloads/*.jpg,Downloads/*.gif,Downloads/*.png,Pictures/*.jpg,Pictures/*.gif,Pictures/*.png}
The function works by first tokenizing the input string, then parsing the tokens into an abstract syntax tree (AST). The AST is then evaluated to produce a list of strings.
The code uses several helper functions to perform the tokenization, parsing, and evaluation tasks. The helper functions are defined as follows:
bracePair
: Finds the closing brace matching the opening brace at the specified index, and returns the index of the closing brace and a list of any immediately-enclosed commas.andTree
: Parses a SYNTAGM subtree and returns an AST.orTree
: Parses a PARADIGM subtree and returns an AST.tokens
: Splits the input string into a list of unescaped braces, commas, and remaining strings.and
: A PARSE TREE OPERATOR that combines its arguments into a single string.or
: A PARSE TREE OPERATOR that returns a flattened list of its arguments.splitAt
: Splits a list into two sublists at the specified index.splitsAt
: Splits a list into several sublists at the specified indices.evaluated
: Evaluates an AST and returns a list of strings.pp
: Prettyprints an AST or other data structure.
The expansions
function is called on a list of sample expressions, and the results are logged to the console.
Here is an example of how the expansions
function works on the input string ~/{Downloads,Pictures}/*.{jpg,gif,png}
:
- The input string is tokenized, resulting in the following list of tokens:
['~', '{', 'Downloads', ',', 'Pictures', '}', '/*.', '{', 'jpg', ',', 'gif', ',', 'png', '}']
- The tokens are parsed into an AST, which is represented as follows:
{
"fn": "[function and]",
"args": [
"~",
{
"fn": "[function or]",
"args": [
{
"fn": "[function and]",
"args": [
"{",
"Downloads",
",",
"Pictures",
"}"
]
},
"/*.",
{
"fn": "[function or]",
"args": [
"{",
"jpg",
",",
"gif",
",",
"png",
"}"
]
}
]
}
]
}
- The AST is evaluated, resulting in the following list of strings:
["~/{Downloads/*.jpg,Downloads/*.gif,Downloads/*.png,Pictures/*.jpg,Pictures/*.gif,Pictures/*.png}"]
- The output is returned as a string:
~/{Downloads/*.jpg,Downloads/*.gif,Downloads/*.png,Pictures/*.jpg,Pictures/*.gif,Pictures/*.png}
Source code in the javascript programming language
(function () {
'use strict'
// Index of any closing brace matching the opening
// brace at iPosn,
// with the indices of any immediately-enclosed commas.
function bracePair(tkns, iPosn, iNest, lstCommas) {
if (iPosn >= tkns.length || iPosn < 0) return null;
var t = tkns[iPosn],
n = (t === '{') ? (
iNest + 1
) : (t === '}' ? (
iNest - 1
) : iNest),
lst = (t === ',' && iNest === 1) ? (
lstCommas.concat(iPosn)
) : lstCommas;
return n ? bracePair(tkns, iPosn + 1, n, lst) : {
close: iPosn,
commas: lst
};
}
// Parse of a SYNTAGM subtree
function andTree(dctSofar, tkns) {
if (!tkns.length) return [dctSofar, []];
var dctParse = dctSofar ? dctSofar : {
fn: and,
args: []
},
head = tkns[0],
tail = head ? tkns.slice(1) : [],
dctBrace = head === '{' ? bracePair(
tkns, 0, 0, []
) : null,
lstOR = dctBrace && (
dctBrace.close
) && dctBrace.commas.length ? (
splitAt(dctBrace.close + 1, tkns)
) : null;
return andTree({
fn: and,
args: dctParse.args.concat(
lstOR ? (
orTree(dctParse, lstOR[0], dctBrace.commas)
) : head
)
}, lstOR ? (
lstOR[1]
) : tail);
}
// Parse of a PARADIGM subtree
function orTree(dctSofar, tkns, lstCommas) {
if (!tkns.length) return [dctSofar, []];
var iLast = lstCommas.length;
return {
fn: or,
args: splitsAt(
lstCommas, tkns
).map(function (x, i) {
var ts = x.slice(
1, i === iLast ? (
-1
) : void 0
);
return ts.length ? ts : [''];
}).map(function (ts) {
return ts.length > 1 ? (
andTree(null, ts)[0]
) : ts[0];
})
};
}
// List of unescaped braces and commas, and remaining strings
function tokens(str) {
// Filter function excludes empty splitting artefacts
var toS = function (x) {
return x.toString();
};
return str.split(/(\\\\)/).filter(toS).reduce(function (a, s) {
return a.concat(s.charAt(0) === '\\' ? s : s.split(
/(\\*[{,}])/
).filter(toS));
}, []);
}
// PARSE TREE OPERATOR (1 of 2)
// Each possible head * each possible tail
function and(args) {
var lng = args.length,
head = lng ? args[0] : null,
lstHead = "string" === typeof head ? (
[head]
) : head;
return lng ? (
1 < lng ? lstHead.reduce(function (a, h) {
return a.concat(
and(args.slice(1)).map(function (t) {
return h + t;
})
);
}, []) : lstHead
) : [];
}
// PARSE TREE OPERATOR (2 of 2)
// Each option flattened
function or(args) {
return args.reduce(function (a, b) {
return a.concat(b);
}, []);
}
// One list split into two (first sublist length n)
function splitAt(n, lst) {
return n < lst.length + 1 ? [
lst.slice(0, n), lst.slice(n)
] : [lst, []];
}
// One list split into several (sublist lengths [n])
function splitsAt(lstN, lst) {
return lstN.reduceRight(function (a, x) {
return splitAt(x, a[0]).concat(a.slice(1));
}, [lst]);
}
// Value of the parse tree
function evaluated(e) {
return typeof e === 'string' ? e :
e.fn(e.args.map(evaluated));
}
// JSON prettyprint (for parse tree, token list etc)
function pp(e) {
return JSON.stringify(e, function (k, v) {
return typeof v === 'function' ? (
'[function ' + v.name + ']'
) : v;
}, 2)
}
// ----------------------- MAIN ------------------------
// s -> [s]
function expansions(s) {
// BRACE EXPRESSION PARSED
var dctParse = andTree(null, tokens(s))[0];
// ABSTRACT SYNTAX TREE LOGGED
console.log(pp(dctParse));
// AST EVALUATED TO LIST OF STRINGS
return evaluated(dctParse);
}
// Sample expressions,
// double-escaped for quotation in source code.
var lstTests = [
'~/{Downloads,Pictures}/*.{jpg,gif,png}',
'It{{em,alic}iz,erat}e{d,}, please.',
'{,{,gotta have{ ,\\, again\\, }}more }cowbell!',
'{}} some }{,{\\\\{ edge, edge} \\,}{ cases, {here} \\\\\\\\\\}'
];
// 1. Return each expression with an indented list of its expansions, while
// 2. logging each parse tree to the console.log() stream
return lstTests.map(function (s) {
return s + '\n\n' + expansions(s).map(function (x) {
return ' ' + x;
}).join('\n');
}).join('\n\n');
})();
{
"fn": "[function and]",
"args": [
"It",
{
"fn": "[function or]",
"args": [
{
"fn": "[function and]",
"args": [
{
"fn": "[function or]",
"args": [
"em",
"alic"
]
},
"iz"
]
},
"erat"
]
},
"e",
{
"fn": "[function or]",
"args": [
"d",
""
]
},
",",
" please."
]
}
You may also check:How to resolve the algorithm Literals/Floating point step by step in the zkl programming language
You may also check:How to resolve the algorithm Conjugate transpose step by step in the PowerShell programming language
You may also check:How to resolve the algorithm Perfect numbers step by step in the Dart programming language
You may also check:How to resolve the algorithm MD5 step by step in the Lasso programming language
You may also check:How to resolve the algorithm Julia set step by step in the BASIC programming language