I'm trying to split a string by curly brackets but ignore nested brackets. E.g given the following:
hello {world} this is some text. {split me {but not me} thanks} heaps!
split into:
[
'hello ',
'{world}',
'this is some text. ',
'{split me {but not me} thanks}',
' heaps!'
]
Any help would be appreciated!
This is what I have so far:
const result = [];
let current = '';
let stack = 0;
for (let i = 0; i < str.length; i++)
{
var ch = str[i];
current += ch;
if (ch.trim() === '')
{
continue;
}
if (ch === '{')
{
stack++;
}
else if (ch === '}')
{
stack--;
}
if (ch === '}' && stack <= 0)
{
result.push(current);
current = '';
}
}
result.push(current);
You have all the logic in your attempt, you just need to reorder a little and push to the results array.
Here's a quick working snippet. It can probably be cleaned up or condensed a little, but a way forward at least.
const str = 'hello {world} this is some text. {split me {but not me} thanks} heaps!';
const result = [];
let current = '';
let stack = 0;
for (let i = 0; i < str.length; i++) {
if (str[i] === '{') {
if (!stack) {
result.push(current);
current = ''
}
stack++;
}
current += str[i];
if (str[i] === '}') {
stack--;
if (!stack) {
result.push(current);
current = ''
}
}
}
if (current.length) {
result.push(current);
}
console.log(result)
You can simplify this a lot with some targeted replacements and then a single split:
const str = 'hello {world} this is some text. {split me {but not me} thanks} heaps!';
const split = str
.replace(/\{([^}]+)/g, '|{$1') // Mark the first `{` of a section that does not contain a `}`.
.replace(/([^{]+)\}/g, '$1}|') // Mark the last `}` of a section that does not contain a `{`.
.split('|') // Split the string on the marks.
console.log(split);
One disadvantage is that you need an additional delimiter in your text.
I used |, but that can be any character you want.
Related
If you see two consecutive characters that are the same, you pop them from left to right, until you cannot pop any more characters. Return the resulting string.
let str = "abba"
"abba" - pop the two b's -> "aa"
"aa" - pop the two a's -> ""
return ""
Here's what i have tried so far:
function match(str){
for (let i = 0; i< str.length; i++){
if (str[i] === str[i+1]){
return str.replace(str[i], "");
}
}
};
match('abba');
But it replaces one character only.The problem is if any two consecutive characters matches it needs to remove both of those and console (Like 'abba' to 'aa'). Then it needs to go over the updated string to do the same thing again (Like 'aa' to '')and console until the return string can't be changed anymore.
Here's another solution i found:
function removeAdjacentDuplicates(str) {
let newStr = '';
for (let i = 0; i < str.length; i++) {
if (str[i] !== str[i + 1])
if (str[i - 1] !== str[i])
newStr += str[i];
}
return newStr;
};
removeAdjacentDuplicates('abba');
But this iterates one time only. I need this to go on until there's no more consecutive characters. Also It would be great if good time complexity is maintained.
You can use a while loop to continuously loop until the result is equal to the previous result.
function removeAdjacentDuplicates(str) {
let newStr = '';
for (let i = 0; i < str.length; i++) {
if (str[i] !== str[i + 1])
if (str[i - 1] !== str[i])
newStr += str[i];
}
return newStr;
};
let before = 'abba';
let result = removeAdjacentDuplicates(before);
while(result != before){
before = result;
result = removeAdjacentDuplicates(before);
}
console.log(result);
If you want to add a limit to the number of pops, you can store the maximum pops in a variable and the number of pops in another (incremented in the loop), then add an expression to the while loop that instructs it not to execute when the number of pops is no longer smaller than the maximum number of pops permitted.
E.g:
function removeAdjacentDuplicates(str) {
let newStr = '';
for (let i = 0; i < str.length; i++) {
if (str[i] !== str[i + 1])
if (str[i - 1] !== str[i])
newStr += str[i];
}
return newStr;
};
let before = 'cabbac';
let result = removeAdjacentDuplicates(before);
const maxPop = 2;
var pops = 1; //It's 1 because we already removed the duplicates once on line 11
while (result != before && pops < maxPop) {
before = result;
result = removeAdjacentDuplicates(before);
pops++;
}
console.log(result);
You can use a regular expression to match consecutive characters and keep replacing until the string is unchanged.
function f(s) {
while (s != (s = s.replace(/(.)\1+/g, '')));
return s;
}
console.log(f("abba"))
I am trying to make a Caesar's Cipher for a challenge, and there are specific rules I must follow:
The algorithm uses a numeric "shift" value. You should use a shift of 8 in your code.
You should IGNORE any characters that are not a letter (this includes symbols such as #*!$^) and they should not be in your output (however a space will remain a space in the encrypted string).
Your output should take into account both uppercase and lowercase letters. That is, both a lowercase 'a' and uppercase 'A' will have the same shift value.
Your final answer should be in all capital letters.
JS code:
function shift8(string){
string = string.toUpperCase();
const alphabetArray = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
let shiftedString = '';
for (i = 0; i < string.length; i++) {
var currentIndex = alphabetArray.indexOf(string[i]);
var newIndex = currentIndex + 8;
var currentCharacter = alphabetArray[currentIndex];
var shiftedCharacter = alphabetArray[newIndex];
if (currentCharacter == ' ') {
shiftedString += ' ';
} else if ('ABCDEFGHIJKLMNOPQRSTUVWXYZ '.includes(shiftedCharacter)) {
shiftedString += shiftedCharacter;
}
}
return shiftedString;
}
var output = shift8('The quick brown fox jumps over the lazy dog');
console.log(output);
Is there something I am missing? Is my logic incorrect?
alphabetArray.indexOf(string[i]) will return -1 for all values not part of alphabetArray, such as spaces. This means that currentCharacter will be undefined for any such values.
To fix this, do var currentCharacter = string[i]; instead.
Your variable currentCharacter is just a member of alphabetArray, which doesn't contain any ' ' (spaces), that's why it's never triggered.
Here, I refactored your code. There is one important thing - you need to loop those indexes, as their max value should be of alphabetArray.length.
function shift8(string){
string = string.toUpperCase();
const alphabetArray = 'ABCDEFGHIJKLMNOPQRSTUVXYZABCDEFGHIJKLMNOPQRSTUVWXYZA'.split('');
let shiftedString = '';
for (i = 0; i < string.length; i++) {
let oldCharacter = string[i];
if( !alphabetArray.includes(oldCharacter) ){
if( oldCharacter == ' ' ) shiftedString += oldCharacter;
} else{
let currentIndex = alphabetArray.indexOf(oldCharacter);
let newIndex = (currentIndex + 8) % alphabetArray.length;
let shiftedCharacter = alphabetArray[newIndex];
shiftedString += shiftedCharacter;
}
}
return shiftedString;
}
var output = shift8('The quick brown fox jumps over the lazy dog');
console.log(output);
For an space the following will return -1
var currentIndex = alphabetArray.indexOf(string[i]);
If you should use a shift of 8 in your code alphabetArray doesn't need to have repeated values after the first eight characters in the alphabet because you will never use these values.
The code should be:
function shift8(string){
string = string.toUpperCase();
const alphabetArray = 'ABCDEFGHIJKLMNOPQRSTUVXYZABCDEFGH'.split('');
let shiftedString = '';
for (i = 0; i < string.length; i++) {
const element = string[i]
if (element == ' ') {
shiftedString += ' ';
} else {
const currentIndex = alphabetArray.indexOf(string[i]);
if(currentIndex === -1){ // To not include symbols such as #*!$^
continue
}
const newIndex = currentIndex + 8;
const currentCharacter = alphabetArray[currentIndex];
const shiftedCharacter = alphabetArray[newIndex];
shiftedString += shiftedCharacter
}
}
return shiftedString;
}
var output = shift8('The quick brown fox jumps over the lazy dog');
console.log(output); // CPM ZDQKS JAXHV NXF RDUYB XEMA CPM TIHG LXO
For example if I had the string "GCG", I'd like to insert a
"C" at every "G" match, and a "G" at every "C" match, making it GCCGGC.
So far I have the following, but it prints GCGCGC.
function pairElement(str) {
for(i = 0; i < str.length; i++) {
if(str[i] == "G") {
return str.replace(/([GC+/])/g, "GC");
} else if (str[i] == "C") {
return str.replace(/([?=CG+/])/g, "CG");
}
}
}
pairElement("GCG");
Edit: I think only the first if statement is executing and not running the else if. Are there any other methods I can use to search for different letters, not only one, and insert another letter depending on what the search for letter is?
You can convert the string into array using splitand then iterate through the array and replace each character.
Then you can use join to convert the array into string.
var string = 'GCG';
var str = string.split('').map(c => {
if(c === 'G') c = 'GC';
else if (c === 'C') c = 'CG';
return c;
}).join('');
console.log('String ' + string);
console.log('New String ' + str);
you can do
function pairElement(str) {
return str.replace(/G|C/g, e => e=='G'?'GC':'CG')
}
console.log(pairElement("GCG"));
You are not using recursion. Once it hits the return statement, the control exits. A better way would be to use regex as one of the answers suggested but if you want to just make tiny modifications in your own code, maybe try something like this.
function pairElement(str) {
var newStr= ""; // using new string to make it more readible
for(i = 0; i < str.length; i++) {
if(str[i] == "G") {
newStr = newStr + str[i] + "C";
} else if (str[i] == "C") {
newStr = newStr + str[i] + "G";
} else {
newStr = newStr + str[i]; //you didn't specify what do you want to do in this case
}
}
return newStr;
}
pairElement("GCG");
input
books.copies.[read_by.[p_id="65784"].page=5468].text.[paragraph="20"].letters
the idea is to split the string by dots but ignore those inside square brackets
so after splitting there should be an array
[
'books',
'copies',
'[read_by.[p_id="65784"].page=5468]',
'text',
'[paragraph="20"]',
'letters'
]
I already looked at this answer but it doesn't work with nested square brackets, which is what i need. Also I'm using javascript, so negative lookbehinds are not supported.
Help is much appreciated.
Edit 1: expand example
It isn't possible to do it with a regex in Javascript that isn't able to match nested structures. You need to use the good old method: a stack.
var text = 'books.copies.[read_by.[p_id="65784"].page=5468].text.[paragraph="20"].letters';
var item = '', result = [], stack = 0;
for (var i=0; i < text.length; i++) {
if ( text[i] == '.' && stack == 0 ) {
result.push(item);
item = '';
continue;
} else if ( text[i] == '[' ) {
stack++;
} else if ( text[i] == ']' ) {
stack--;
}
item += text[i];
}
result.push(item);
console.log(result);
You need to write a parser for this since a JavaScript regex does not support regex recursion, nor balanced constructs.
The point in these functions is that they keep a stack (level, openBrackets) of opening delimiters (in your case, it is [) and then check the stack state: if the stack is not emppty, the found . is considered inside the brackets, and is thus just appended to the current match. Else, when the stack is empty, the . found is considered outside of brackets, and is thus used to split on (the current value is appended to the output array (result, ret)).
function splitByDotsOutsideBrackets(string){
var openBrackets = 0, ret = [], i = 0;
while (i < string.length){
if (string.charAt(i) == '[')
openBrackets++;
else if (string.charAt(i) == ']')
openBrackets--;
else if (string.charAt(i) == "." && openBrackets == 0){
ret.push(string.substr(0, i));
string = string.substr(i + 1);
i = -1;
}
i++;
}
if (string != "") ret.push(string);
return ret;
}
var res = splitByDotsOutsideBrackets('books.copies.[read_by.[p_id="65784"].page=5468].text.[paragraph="20"].letters');
console.log(res);
Or another variation:
function splitOnDotsOutsideNestedBrackets(str) {
var result = [], start = 0, level = 0;
for (var i = 0; i < str.length; ++i) {
switch (str[i]) {
case '[':
++level;
break;
case ']':
if (level > 0)
--level;
break;
case '.':
if (level)
break;
if (start < i)
result.push(str.substr(start, i - start));
start = i + 1;
break;
}
}
if (start < i)
result.push(str.substr(start, i - start));
return result;
}
var s = 'books.copies.[read_by.[p_id="65784"].page=5468].text.[paragraph="20"].letters';
console.log(splitOnDotsOutsideNestedBrackets(s))
Adapted from one of my previous answers.
I have a variable which contains this:
var a = "hotelRoomNumber";
Is there a way I can create a new variable from this that contains: "Hotel Room Number" ? I need to do a split on the uppercase character but I've not seen this done anywhere before.
Well, you could use a regex, but it's simpler just to build a new string:
var a = "hotelRoomNumber";
var b = '';
if (a.length > 0) {
b += a[0].toUpperCase();
for (var i = 1; i != a.length; ++i) {
b += a[i] === a[i].toUpperCase() ? ' ' + a[i] : a[i];
}
}
// Now b === "Hotel Room Number"
var str = "mySampleString";
str = str.replace(/([A-Z])/g, ' $1').replace(/^./, function(str){ return str.toUpperCase(); });
http://jsfiddle.net/PrashantJ/zX8RL/1/
I have made a function here:
http://jsfiddle.net/wZf6Z/2/
function camelToSpaceSeperated(string)
{
var char, i, spaceSeperated = '';
// iterate through each char
for (i = 0; i < string.length; i++) {
char = string.charAt(i); // current char
if (i > 0 && char === char.toUpperCase()) { // if is uppercase
spaceSeperated += ' ' + char;
} else {
spaceSeperated += char;
}
}
// Make the first char uppercase
spaceSeperated = spaceSeperated.charAt(0).toUpperCase() + spaceSeperated.substr(1);
return spaceSeperated;
}
The general idea is to iterate through each char in the string, check if the current char is already uppercased, if so then prepend a space to it.