Splitting string that's really an array? - javascript

I'm trying to split this string
var raw = "GMYTEOR[RHE5DO,SG[A5D[CN[I,Q],EM[I,Q],M],E5D[C[NY,OA],O,Q],M5DC[MY,NA],U5DQ,Y5DCOA]]"
I manually split this out into the following patterns:
const valid_reults = [
'GMYTEORRHE5DO',
'GMYTEORSGA5DCNI',
'GMYTEORSGA5DCNQ',
'GMYTEORSGA5DEMI',
'GMYTEORSGA5DEMQ',
'GMYTEORSGA5DM',
'GMYTEORSGE5DCNY',
'GMYTEORSGE5DCOA',
'GMYTEORSGE5DO',
'GMYTEORSGE5DQ',
'GMYTEORSGM5DCMY',
'GMYTEORSGM5DCNA',
'GMYTEORSGU5DQ',
'GMYTEORSGY5DCOA',
]
I tried parsing it with JSON.parse but that didnt work for obvious reasons.
I also tried splitting the string by the brackets but that was a really bad idea and I couldn't get it working after 20-30 mins of trying.
What's the best way to convert that bizarre string format into a formatted array as shown in valid_results?

This should work:
function splitString(str) {
// if there is no brackets a split will solve
if (!str.includes('[')) {
return str.split(',');
}
// finds first bracket
const bracketsIdx = str.indexOf('[');
// find the close bracket for it
const closingBracketsIdx =
findClosingBracketMatchIndex(str, bracketsIdx);
if (closingBracketsIdx === -1) {
// Invalid input, didn't find a close bracket
throw Error()
}
// find all possibilities inside the bracket
const expand = splitString(str.substring(
bracketsIdx+1,
closingBracketsIdx,
));
return [
// Remove duplicates
...new Set(expand.map(
(expandedStr) => splitString(
(
// concatenate each possibility to what is
// outside of the bracket
str.substring(
0,
bracketsIdx,
)
+ expandedStr
+ str.substring(
closingBracketsIdx+1,
)
)
)
// since each call will return an array we flatten them
).flat())
];
}
function findClosingBracketMatchIndex(str, pos) {
let openBracketCount = 1;
for (let i = pos + 1; i < str.length; i++) {
let char = str[i];
if (char === '[') {
openBracketCount++;
} else if (char === ']') {
openBracketCount--;
}
if (openBracketCount === 0){
return i;
}
}
return -1;
}

Related

How make a more concise code using the For statement?

I'm new in StackOverflow and JavaScript, I'm trying to get the first letter that repeats from a string considering both uppercase and lowercase letters and counting and obtaining results using the for statement. The problem is that the form I used is too long Analyzing the situation reaches such a point that maybe you can only use a "For" statement for this exercise, which I get to iterate, but not with a cleaner and reduced code has me completely blocked, this is the reason why I request help to understand and continue with the understanding and use of this sentence. In this case, the result was tested in a JavaScript script inside a function and 3 "For" sentences obtaining quite positive results, but I can not create it in 1 only For (Sorry for my bad english google translate)
I making in HTML with JavasScript
var letter = "SYAHSVCXCyXSssssssyBxAVMZsXhZV";
var contendor = [];
var calc = [];
var mycalc = 0;
letter = letter.toUpperCase()
console.log(letter)
function repeats(){
for (var i = 0; i < letter.length; i++) {
if (contendor.includes(letter[i])) {
}else{
contendor.push(letter[i])
calc.push(0)
}
}
for (var p = 0; p < letter.length; p++) {
for (var l = 0; l < contendor.length; l++) {
if (letter[p] == contendor[l]) {
calc [l]= calc [l]+1
}
}
}
for (var f = 0; f < calc.length; f++) {
if ( calc[f] > calc[mycalc]) {
mycalc = f
}
}
}
repeats()
console.log("The most repeated letter its: " + contendor[mycalc]);
I Expected: A result with concise code
It would probably be a lot more concise to use a regular expression: match a character, then lookahead for more characters until you can match that first character again:
var letter = "SYAHSVCXCyXSssssssyBxAVMZsXhZV";
const firstRepeatedRegex = /(.)(?=.*\1)/;
console.log(letter.match(firstRepeatedRegex)[1]);
Of course, if you aren't sure whether a given string contains a repeated character, check that the match isn't null before trying to extract the character:
const input = 'abcde';
const firstRepeatedRegex = /(.)(?=.*\1)/;
const match = input.match(firstRepeatedRegex);
if (match) {
console.log(match[0]);
} else {
console.log('No repeated characters');
}
You could also turn the input into an array and use .find to find the first character whose lastIndexOf is not the same as the index of the character being iterated over:
const getFirstRepeatedCharacter = (str) => {
const chars = [...str];
const char = chars.find((char, i) => chars.lastIndexOf(char) !== i);
return char || 'No repeated characters';
};
console.log(getFirstRepeatedCharacter('abcde'));
console.log(getFirstRepeatedCharacter('SYAHSVCXCyXSssssssyBxAVMZsXhZV'));
If what you're actually looking for is the character that occurs most often, case-insensitive, use reduce to transform the string into an object indexed by character, whose values are the number of occurrences of that character, then identify the largest value:
const getMostRepeatedCharacter = (str) => {
const charsByCount = [...str.toUpperCase()].reduce((a, char) => {
a[char] = (a[char] || 0) + 1;
return a;
}, {});
const mostRepeatedEntry = Object.entries(charsByCount).reduce((a, b) => a[1] >= b[1] ? a : b);
return mostRepeatedEntry[0];
};
console.log(getMostRepeatedCharacter('abcde'));
console.log(getMostRepeatedCharacter('SYAHSVCXCyXSssssssyBxAVMZsXhZV'));
If the first repeated character is what you want, you can push it into an array and check if the character already exists
function getFirstRepeating( str ){
chars = []
for ( var i = 0; i < str.length; i++){
var char = str.charAt(i);
if ( chars.includes( char ) ){
return char;
} else {
chars.push( char );
}
}
return -1;
}
This will return the first repeating character if it exists, or will return -1.
Working
function getFirstRepeating( str ){
chars = []
for ( var i = 0; i < str.length; i++){
var char = str.charAt(i);
if ( chars.includes( char ) ){
return char;
} else {
chars.push( char );
}
}
return -1;
}
console.log(getFirstRepeating("SYAHSVCXCyXSssssssyBxAVMZsXhZV"))
Have you worked with JavaScript objects yet?
You should look into it.
When you loop through your string
let characters = "hemdhdksksbbd";
let charCount = {};
let max = { count: 0, ch: ""}; // will contain max
// rep letter
//Turn string into an array of letters and for
// each letter create a key in the charcount
// object , set it to 1 (meaning that's the first of
// that letter you've found) and any other time
// you see the letter, increment by 1.
characters.split("").forEach(function(character)
{
if(!charCount[character])
charCount[character] = 1;
else
charCount[character]++;
}
//charCount should now contain letters and
// their counts.
//Get the letters from charCount and find the
// max count
Object.keys(charCount). forEach (function(ch){
if(max.count < charCount[ch])
max = { count: charCount[ch], ch: ch};
}
console.log("most reps is: " , max.ch);
This is a pretty terrible solution. It takes 2 loops (reduce) and doesn't handle ties, but it's short and complicated.
Basically keep turning the results into arrays and use array methods split and reduce to find the answer. The first reduce is wrapped in Object.entries() to turn the object back into an array.
let letter = Object.entries(
"SYAHSVCXCyXSssssssyBxAVMZsXhZV".
toUpperCase().
split('').
reduce((p, c) => {
p[c] = isNaN(++p[c]) ? 1 : p[c];
return p;
}, {})
).
reduce((p, c) => p = c[1] > p[1] ? c : p);
console.log(`The most repeated letter is ${letter[0]}, ${letter[1]} times.`);

Javascript Regex Custom Replace

How do I get the following conversion using Regex?
Content(input data structure):
a-test
b-123
c-qweq
d-gdfgd
e-312
Conversion:
1-test
2-123
3-qweq
4-gdfgd
Final-312
var index = 1;
function c_replace() {
if(index == 5) { return "Final"; }
return index++;
}
there you go :D
// i assume you have a string input that contains linebreaks due to your question format
const input = `a-test
b-123
c-qweq
d-gdfgd
e-312`.trim(); // removing whitespace in front or behind the input data.
//splitting the lines on whitespace using \s+
const output = input.split(/\s+/).map((s, i, a) => {
// this will map your pattern asd-foooasdasd
const data = s.match(/^[a-z]+-(.+)$/);
// you may want to tweak this. right now it will simply throw an error.
if (!data) throw new Error(`${s} at position ${i} is a malformed input`);
// figure out if we are in the final iteration
const final = i == a.length -1;
// the actual output data
return `${final ? "Final" : (i + 1)}-${data[1]}`;
// and of course join the array into a linebreak separated list similar to your input.
}).join("\n");
console.log(output);
Test
var index=1;
var text=`a-test
b-123
c-qweq
d-gdfgd
e-312`;
function c_replace() {
if(index == 5) { return "Final-"; }
return index++ +'-';
}
console.log(text.replace(/.-/g,c_replace));
var input = [
'a-test',
'b-123',
'c-qweq',
'd-gdfgd',
'e-312'
];
var output = input.map((e, i) => ++i + e.slice(1));
output[output.length - 1] = 'Final' + output[output.length - 1].slice(1);
console.log(output);

How do I replace integers in an array with a string when it's a function parameter in JavaScript?

What I am trying to do is make a function that takes user input, splits that input into an array of numbers, then replaces each number with a string depending on what the number is. It seems all this does now is return undefined, because it doesn't want to reassign the index to what It tell it to. I want to do this using a for loop or the forEach method if possible.
Here is my code so far:
function stringify(num){
var array = num;
for (i in array) {
if (array[i] == 2) {
array[i] = "x";
} else if (array[i] == 5) {
array[i] = "y";
} else {
array[i] = "meow"
}
return array;
}
}
Here is an example of what I want to eventually happen:
stringify(52527);
y x y x meow
You could map the new array by using an object for the wanted replacings.
function stringify(num) {
return Array.from(num, v => ({ 2: 'x', 5: 'y' }[v] || v));
}
console.log(stringify('5252'));
With default value
function stringify(num) {
var values = { 2: 'x', 5: 'y', default: 'meow' };
return Array.from(num, v => values[v] || values.default);
}
console.log(stringify('52527'));
Convert the input data to a string, and split the string to characters:
function stringify(num) {
var array = String(num).split('');
for (i in array) {
if (array[i] === '2') {
array[i] = "x";
} else if (array[i] === '5') {
array[i] = "y";
} else {
array[i] = "meow"
}
}
return array; // the return should be after the loop ends
}
console.log(stringify(52527));
Another solution would be to use a Array.map() to iterate after the split, and an object with the characters that should be replaced:
function stringify(num) {
var rep = { 2: 'x', 5: 'y' };
return String(num)
.split('')
.map(function(c) {
return rep[c] || 'meow';
});
}
console.log(stringify(52527));
I think you might have a couple of problems:
Using a for ... in loop for an array or an iterator isn't ideal; rather use the construction for (var i = 0; i < array.length; i += 1).
As other commenters have said, make sure you're converting the passed argument, which is a Number, into a String. My preference for doing this something like var numString = '' + num;. If you're using es6, you could also use the template literal notation:
var numString = `${num}`;
Be careful of using the double equal sign == versus the triple equals === to check equality. The double equal will do some arcane type-casting of its operands. Safer to stick with the triple equal unless you're sure.
Strings are immutable, which means that while you can access the values of their indices, you can't change those values. I would suggest creating a new array that you then join upon return:
function stringify(num) {
var numString = '' + num;
var ret = [];
for (var i = 0; i < numString.length; i += 1) {
if (numString[i] === '2') { // The index values will be strings, not numbers now
ret.push('x');
} else if (numString[i] === '5') {
ret.push('y');
} else {
ret.push('meow');
}
}
return ret.join(' ');
}
this results in:
stringify(52527);
y x y x meow

Recursive parser using split in javascript

I have an algorithm where the user will enter a string and I will parse it into an array of 2+ dimensions. So, for example, the user can enter 1,2,3;4,5,6 and set the text to be parsed by the semicolon and the comma. The first pass through will create an array with 2 entries. The second pass through will create a 3 entry array in both prior spots.
The user can add or remove the number of text items to be used to parse the original string such as the semicolon or comma, meaning the resulting array can have as many dimensions as parsing items.
This doesn't seem like a difficult problem, but I have run into some snags.
Here is my code so far.
vm.parsers = [';', ','];
vm.inputString = "1,2,3,4,5;6,7,8,9,10";
function parseDatasetText( )
{
vm.real = vm.parseMe( vm.inputString, 0);
};
function parseMe( itemToParse, indexToParse )
{
if ( indexToParse < vm.parsers.length )
{
console.log('Parsing *'+itemToParse+'* with '+vm.parsers[indexToParse]);
var tempResults = itemToParse.split( vm.parsers[indexToParse] );
for (var a=0; a<tempResults.length; a++)
{
console.log('Pushing '+tempResults[a]);
tempResults[a] = vm.parseMe( tempResults[a], parseInt( indexToParse ) + 1 )
console.log('This value is '+tempResults[a]);
}
}else
{
console.log('Returning '+itemToParse);
return itemToParse
}
};
As you can see from the console logs, the algorithm spits out an undefined after the last parse, and the final answer is undefined.
Maybe I just haven't slept enough, but I was thinking that the array would recursively populate via the splits?
Thanks
function parseDatasetText(){
//composing parser from right to left into a single function
//that applies them from left to right on the data
var fn = vm.parsers.reduceRight(
(nextFn, delimiter) => v => String(v).split(delimiter).map(nextFn),
v => v
);
return fn( vm.inputString );
}
Don't know what else to add.
You can use a simple recursive function like the following (here an example with 3 different delimiters):
function multiSplit(xs, delimiters) {
if (!delimiters.length) return xs;
return xs.split(delimiters[0]).map(x => multiSplit(x, delimiters.slice(1)));
}
data = '1:10,2:20,3:30;4:40,5:50,6:60';
res = multiSplit(data, [';', ',', ':']);
console.log(res)
The following function should suit your requirements, please let me know if not
var parsers = [';', ',', ':'],
inputString = "1:a,2:b,3:c,4:d,5:e;6:f,7:g,8:h,9:i,10:j",
Result = [];
function Split(incoming) {
var temp = null;
for (var i = 0; i < parsers.length; i++)
if (incoming.indexOf(parsers[i]) >= 0) {
temp = incoming.split(parsers[i]);
break;
}
if (temp == null) return incoming;
var outgoing = [];
for (var i = 0; i < temp.length; i++)
outgoing[outgoing.length] = Split(temp[i])
return outgoing;
}
Result = Split(inputString);
try it on https://jsfiddle.net/cgy7nre1/
Edit 1 -
Added another inputString and another set of parsers: https://jsfiddle.net/cgy7nre1/1/
Did you mean this?
var inputString = "1,2,3,4,5;6,7,8,9,10";
var array=inputString.split(';');
for (var i=0;i<array.length;i++){
array[i]=array[i].split(',');
}
console.log(array);

Javascript: Split a string by comma, except inside parentheses

Given string in the form:
'"abc",ab(),c(d(),e()),f(g(),zyx),h(123)'
How can I split it to get the below array format:
abc
ab()
c(d(),e())
f(g(),zyx)
h(123)
I have tried normal javascript split, however it doesn't work as desired. Trying Regular Expression but not yet successful.
You can keep track of the parentheses, and add those expressions when the left and right parens equalize.
For example-
function splitNoParen(s){
var left= 0, right= 0, A= [],
M= s.match(/([^()]+)|([()])/g), L= M.length, next, str= '';
for(var i= 0; i<L; i++){
next= M[i];
if(next=== '(')++left;
else if(next=== ')')++right;
if(left!== 0){
str+= next;
if(left=== right){
A[A.length-1]+=str;
left= right= 0;
str= '';
}
}
else A=A.concat(next.match(/([^,]+)/g));
}
return A;
}
var s1= '"abc",ab(),c(d(),e()),f(g(),zyx),h(123)';
splitNoParen(s1).join('\n');
/* returned value: (String)
"abc"
ab()
c(d(),e())
f(g(),zyx)
h(123)
*/
This might be not the best or more refined solution, and also maybe won't fit every single possibility, but based on your example it works:
var data = '"abc",ab(),c(d(),e()),f(g(),zyx),h(123)';
// Create a preResult splitting the commas.
var preResult = data.replace(/"/g, '').split(',');
// Create an empty result.
var result = [];
for (var i = 0; i < preResult.length; i++) {
// Check on every preResult if the number of parentheses match.
// Opening ones...
var opening = preResult[i].match(/\(/g) || 0;
// Closing ones...
var closing = preResult[i].match(/\)/g) || 0;
if (opening != 0 &&
closing != 0 &&
opening.length != closing.length) {
// If the current item contains a different number of opening
// and closing parentheses, merge it with the next adding a
// comma in between.
result.push(preResult[i] + ',' + preResult[i + 1]);
i++;
} else {
// Leave it as it is.
result.push(preResult[i]);
}
}
Demo
For future reference, here's another approach to top-level splitting, using string.replace as a control flow operator:
function psplit(s) {
var depth = 0, seg = 0, rv = [];
s.replace(/[^(),]*([)]*)([(]*)(,)?/g,
function (m, cls, opn, com, off, s) {
depth += opn.length - cls.length;
var newseg = off + m.length;
if (!depth && com) {
rv.push(s.substring(seg, newseg - 1));
seg = newseg;
}
return m;
});
rv.push(s.substring(seg));
return rv;
}
console.log(psplit('abc,ab(),c(d(),e()),f(g(),zyx),h(123)'))
["abc", "ab()", "c(d(),e())", "f(g(),zyx)", "h(123)"]
Getting it to handle quotes as well would not be too complicated, but at some point you need to decide to use a real parser such as jison, and I suspect that would be the point. In any event, there's not enough detail in the question to know what the desired handling of double quotes is.
You can't use .split for this, but instead you'll have to write a small parser like this:
function splitNoParen(s){
let results = [];
let next;
let str = '';
let left = 0, right = 0;
function keepResult() {
results.push(str);
str = '';
}
for(var i = 0; i<s.length; i++) {
switch(s[i]) {
case ',':
if((left === right)) {
keepResult();
left = right = 0;
} else {
str += s[i];
}
break;
case '(':
left++;
str += s[i];
break;
case ')':
right++;
str += s[i];
break;
default:
str += s[i];
}
}
keepResult();
return results;
}
var s1= '"abc",ab(),c(d(),e()),f(g(),zyx),h(123)';
console.log(splitNoParen(s1).join('\n'));
var s2='cats,(my-foo)-bar,baz';
console.log(splitNoParen(s2).join('\n'));
Had a similar issue and existing solutions were hard to generalize. So here's another parser that's a bit more readable and easier to extend to your personal needs. It'll also work with curly braces, brackets, normal braces, and strings of any type. License is MIT.
/**
* This function takes an input string and splits it by the given token, but only if the token is not inside
* braces of any kind, or a string.
* #param {string} input The string to split.
* #param {string} split_by Must be a single character.
* #returns {string[]} An array of split parts without the split_by character.
*/
export function parse_split(input:string, split_by:string = ",") : string[]
{
// Javascript has 3 types of strings
const STRING_TYPES = ["'","`","\""] as const;
// Some symbols can be nested, like braces, and must be counted
const state = {"{":0,"[":0,"(":0};
// Some cannot be nested, like a string, and just flip a flag.
// Additionally, once the string flag has been flipped, it can only be unflipped
// by the same token.
let string_state : (typeof STRING_TYPES)[number] | undefined = undefined
// Nestable symbols come in sets, usually in pairs.
// These sets increase or decrease the state, depending on the symbol.
const pairs : Record<string,[keyof typeof state,number]> = {
"{":["{",1],
"}":["{",-1],
"[":["[",1],
"]":["[",-1],
"(":["(",1],
")":["(",-1]
}
let start = 0;
let results = [];
let length = input.length;
for(let i = 0; i < length; ++i)
{
let char = input[i];
// Backslash escapes the next character. We directly skip 2 characters by incrementing i one extra time.
if(char === "\\")
{
i++;
continue;
}
// If the symbol exists in the single/not nested state object, flip the corresponding state flag.
if(char == string_state)
{
string_state = undefined;
console.log("Closed string ", string_state);
}
// if it's not in a string, but it's a string opener, remember the string type in string_state.
else if(string_state === undefined && STRING_TYPES.includes(char as typeof STRING_TYPES[number]))
{
string_state = char as typeof STRING_TYPES[number];
}
// If it's not in a string, and if it's a paired symbol, increase or decrease the state based on our "pairs" constant.
else if(string_state === undefined && (char in pairs) )
{
let [key,value] = pairs[char];
state[key] += value;
}
// If it's our split symbol...
else if(char === split_by)
{
// ... check whether any flags are active ...
if(Object.entries(state).every(([k,v])=>v == 0) && (string_state === undefined))
{
// ... if not, then this is a valid split.
results.push(input.substring(start,i))
start = i+1;
}
}
}
// Add the last segment if the string didn't end in the split_by symbol, otherwise add an empty string
if(start < input.length)
{
results.push(input.substring(start,input.length))
}
else
results.push("");
return results;
}
With this regex, it makes the job:
const regex = /,(?![^(]*\))/g;
const str = '"abc",ab(),c(d(),e()),f(g(),zyx),h(123)';
const result = str.split(regex);
console.log(result);
Javascript
var str='"abc",ab(),c(d(),e()),f(g(),zyx),h(123)'
str.split('"').toString().split(',').filter(Boolean);
this should work

Categories