Split string in array on whitespace before the 10th character - javascript

I want to split a string into characters of 10. But I don't want to split inside a word.
So the string "Nine characters to go - then some more" would be split in ["Nine", "characters", "to go -", "then some", "more"].
The word can split if it's more than 10 characters.
The closest I got using regex was .{1,10}(?<=\s).
That would split "Nine characters to go - then some more" into ["Nine ", "haracters ", "to go - ", "then some "].
But the behaviour is weird. In the example string it completely skips the character "c". In other test strings it would only add "- " in a separate array item when just the dash would fit in with the array item before it. So it splits after the whitespace.
I also attempted to .split() on white spaces (.split(' ')) and using .reduce() or a for loop to join array items into other array items of max 10 characters.
for ( i = 0; i < splitArray.length; i++ ) {
if ( i === 0 ) {
// add first word in new array. Doesn't take into account yet that word can be longer than 10 characters
newArray.push( splitArray[i] );
} else {
if ( newArray[ newArray.length - 1 ].length + splitArray[i].length + 1 < 10 ) {
// if next word fits with the word in the new array (taking space into account), add it to it
newArray[ newArray.length - 1 ] = newArray[ newArray.length - 1 ] + " " + splitArray[i];
} else if ( newArray[ newArray.length - 1 ].length + splitArray[i].length + 1 >= 10 ) {
// next word doesn't fit
// split word and add only part to it and add the rest in separate item in newArray
const index = 9 - newArray[ newArray.length - 1 ].length
const prev = splitArray[i].slice( 0, index );
const next = splitArray[i].slice( index, splitArray[i].length );
newArray[ newArray.length - 1 ] = newArray[ newArray.length - 1 ] + " " + prev;
newArray.push( next );
} else {
// push new item in newArray
newArray.push( splitArray[i] );
}
}
}
Results in: ["Nine chara", "cters to g", "o - then s", "ome more"].
Without the else if: ["Nine", "characters", "to go -", "then some", "more"]
Without the else if other string: ["Paul van", "den Dool", "-", "Alphabet", "- word"]
This is close, but "Alphabet" won't join with the hyphen, because together they don't fit. I tried catching that with an else if statement but that is breaking words again that I don't want to break and is the same result as the regex above.
My mind is depleted on this issue, I need the hive mind for this. So any help on this is very much appreciated.
Context
I'm trying to display text on canvas in a limited sized box with a minimum font size. My solution would be to break the string, which can be entered by the user, into multiple lines if necessary. For this I need to split the string into an array, loop over it and position the text accordingly.

const string = "Nine characters to go - then some more"
let arr = string.split(" ");
for(let i = 1; i < arr.length; i++) {
if(arr[i].length >= 10 || arr[i].length + arr[i-1].length >= 10) {
continue;
}
if(arr[i].length < 10 && arr[i].length + arr[i-1].length <= 10) {
arr[i] = arr[i - 1] + " " + arr[i];
arr[i-1] = false;
}
}
arr = arr.filter(string => string)
console.log(arr);

Use
console.log(
"Nine characters to go - then some more"
.match(/.{1,10}(?=\s|$)/g)
.map(z => z.trim())
);
With .match(/.{1,10}(?=\s|$)/g), the items will be 1 to 10 characters long, and (?=\s|$) will assure a whitespace or end of string is matched.

If you need to split, use .split():
const str = 'Nine characters to go - then some more',
result = str.split(/(.{1,10})\s/).filter(Boolean)
console.log(result)

I can solve this using a simple for loop:
const str = "Nine characters to go - then some more";
// make an array with each words
const arr = str.trim().split(' ');
// This is the length, how much we want to take the length of the words
const length = 10;
const res = [];
/**
* Put the first word into the result array
* because if the word greater or less than
* the `length` we have to take it.
*/
res.push(arr[0]);
// Result array's current index
let index = 0;
for (let i = 1, l = arr.length; i < l; i++) {
/**
* If the length of the concatenation of the
* last word of the result array
* and the next word is less than or equal to the length
* then concat them and put them as the last value
* of the resulting array.
*/
if ((res[index] + arr[i]).length <= length) {
res[index] += ' ' + arr[i];
} else {
/**
* Otherwise push the current word
* into the resulting array
* and increase the last index of the
* resulting array.
*/
res.push(arr[i]);
index++;
}
}
console.log(res);
.as-console-wrapper{min-height: 100%!important; top: 0}

Related

Write a function which takes a sentence as an input and output a sorted sentence. (The answer should be valid for any given input.)

I want to write a function which takes a sentence as an input and output a sorted sentence, and there are two criterias:
Each character of the word should be arranged in alphabetical order.
Words should be arranged in ascending order depending on its character count.
Note: - Word only can have lowercase letters
Example :
Inputs str = "she lives with him in a small apartment"
Output = "a in ehs him hitw eilsv allms aaemnprtt"
Here is my code.
function makeAlphabetSentenceSort(str) {
if (!str || str.length === 0) return 0;
var word = str.split(' ');
for (var j = 0; j < word.length; j++) {
word[j] = word[j].split('').sort().join('');
}
for (var h = 0; h < word.length - 1; h++) {
for (var i = 0; i < word.length - h - 1; i++) {
if (String(word[i]).length > String(word[i + 1]).length) {
var temp = word[i];
word[i] = word[i + 1];
word[i + 1] = temp;
}
}
}
return word.join(' ');
}
makeAlphabetSentenceSort("she lives with him in a small apartment");
Based on the assumption that the output should contain only lowercase letters.
Well if you want to use the built-in functions you could also write that as:
function makeAlphabetSentenceSort(str) {
if (!str) return str;
const nonCharacters = /[^a-z]/g; // to replace any thing other than letters
// We split the sentence to words by any whitespace first
return str.toLowerCase().split(/\s+/).map(word => {
// Here we remove all non-characters from the word
// And sort the remaining characters alphabetically
return word.replace(nonCharacters, '').split('').sort().join('');
// It might be that the sentence looks like:
// "Hey! ???"
// In that case the "word" ??? would become just an empty string
// since all the non-characters have been removed.
// But then you would end up with a result:
// " ehy"
// because the empty word would still get added to the beginning of the sentence
// Because of that we need to filter the empty words out
// And to do that I use this lil trick of mine, using "Boolean"
// as a filter function since Boolean('') is false
// and Boolean('any word') is true
}).filter(Boolean).sort((a, b) => {
// Here we sort all the words by their length
return a.length - b.length;
}).join(' ');
}
console.log(makeAlphabetSentenceSort("Isn't it?"));
console.log(makeAlphabetSentenceSort("she lives with him in a small apartment"));

Having trouble re-indexing my array after looping from the end of the array while adding characters in place

I am trying to solve this particular algorithm question:
You are given a license key represented as a string S which consists only alphanumeric character and dashes. The string is separated into N+1 groups by N dashes.
Given a number K, we would want to reformat the strings such that each group contains exactly K characters, except for the first group which could be shorter than K, but still must contain at least one character. Furthermore, there must be a dash inserted between two groups and all lowercase letters should be converted to uppercase.
Given a non-empty string S and a number K, format the string according to the rules described above.
Example 1:
Input: S = "5F3Z-2e-9-w", K = 4
Output: "5F3Z-2E9W"
Explanation: The string S has been split into two parts, each part has 4 characters.
Note that the two extra dashes are not needed and can be removed.
Example 2:
Input: S = "2-5g-3-J", K = 2
Output: "2-5G-3J"
Explanation: The string S has been split into three parts, each part has 2 characters except the first part as it could be shorter as mentioned above.
Note:
The length of string S will not exceed 12,000, and K is a positive integer.
String S consists only of alphanumerical characters (a-z and/or A-Z and/or 0-9) and dashes(-).
String S is non-empty.
I have written the following code:
const licenseKeyFormatting = (S, K) => {
//convert to array, remove special characters, and capitalize
let s = [...S.replace(/\W/g, '').toUpperCase()]
let pos = 1
//from end of array add '-' for every K
for (let i = s.length - 1; i > 0; i--) {
if (pos === K) {
s.splice(i, 0, '-')
pos = 1
i-- //re-index bc adding to the array
}
pos++
}
return s
}
console.log(licenseKeyFormatting("5F3Z-2e-9-w", 4)) //5F3Z-2E9W
console.log(licenseKeyFormatting("2-5g-3-J", 2)) //2-5G-3J
console.log(licenseKeyFormatting("a-a-a-a-", 1)) // this test case fails should be A-A-A-A, I am getting AAA-A
I am pretty sure the flaw in my logic is due to the re-index, but I can't figure out how to address it.
You should not alter the index. Using splice to insert an element pushes the other elements back, however since you iterate from back to front that doesn't matter. You've already handled the shifted elements.
Another issue is setting pos = 1 in the loop. This is directly followed by pos++. So when pos reaches K the value of pos will be reset to 2 at the end of the loop. Either set pos = 0 (in the loop) so it ends on 1 or move pos++ into the else section.
const licenseKeyFormatting = (S, K) => {
//convert to array, remove special characters, and capitalize
let s = [...S.replace(/\W/g, '').toUpperCase()]
let pos = 1
//from end of array add '-' for every K
for (let i = s.length - 1; i > 0; i--) {
if (pos === K) {
s.splice(i, 0, '-')
pos = 0
}
pos++
}
return s.join("") // <- added join for cleaner console output
}
console.log(licenseKeyFormatting("5F3Z-2e-9-w", 4)) //5F3Z-2E9W
console.log(licenseKeyFormatting("2-5g-3-J", 2)) //2-5G-3J
console.log(licenseKeyFormatting("a-a-a-a-", 1)) // this test case fails should be A-A-A-A, I am getting AAA-A
my way....
function licenseKeyFormatting( S, K )
{
let arr = [...S.replace(/\W/g, '').toUpperCase()]
, p = 0
;
for (let i=arr.length;i--;)
{
p = ++p % K // p = (p+1) % K
if (!p&&i) arr.splice(i,0,'-') // if p===0 and i>0
}
return arr.join('')
}
console.log(licenseKeyFormatting("5F3Z-2e-9-w", 4)) // 5F3Z-2E9W
console.log(licenseKeyFormatting("2-5g-3-J", 2)) // 2-5G-3J
console.log(licenseKeyFormatting("a-a-a-a-", 1)) // A-A-A-A
OR: (more simple)
function licenseKeyFormatting( S, K )
{
let arr = [...S.replace(/\W/g, '').toUpperCase()];
for (let p=arr.length-K;p>0;p-=K) arr.splice(p,0,'-');
return arr.join('');
}
console.log( licenseKeyFormatting("5F3Z-2e-9-w", 4)) // 5F3Z-2E9W
console.log( licenseKeyFormatting("2-5g-3-J", 2)) // 2-5G-3J
console.log( licenseKeyFormatting("a-a-a-a-", 1)) // A-A-A-A

Reverse string in alternating chunks of size k

Given a string and an integer k, you need to reverse the first k characters for every segment of length 2k characters counting from the start of the string. If there are less than k characters left, reverse all of them. If there are less than 2k but greater than or equal to k characters, then reverse the first k characters and left the other as original.
Example
Input: s = "abcdefg", k = 2
Output: "bacdfeg"
In the above example, the first chunk of two "ab" was reversed to "ba" and the third chunk of two "ef" was reversed to "fe".
This is my approach:
var reverseStr = function(s, k) {
for (let i = 0; i < k; i++) {
let temp = s[i];
s[i] = s[k - i - 1];
s[k - i - 1] = temp
}
return s
};
console.log(reverseStr("abcdefg", 2))
How do I produce the desired output?
One option is to use a regular expression to match up to k characters, followed by up to 2k characters - then use a replacer function to reverse only the initial k characters:
var reverseStr = function(s, k) {
const pattern = new RegExp(`(.{1,${k}})(.{0,${k}})`, 'g');
return s.replace(pattern, (_, g1, g2) => [...g1].reverse().join('') + g2);
};
console.log(reverseStr("abcdefg", 2))
Strings in JavaScript are immutable, you can't assign to their indexes to modify the string in place. You need to build a new string and return it.
You need a loop for each 2k group. Extract that substring, reverse the first k characters, then concatenate them to the result.
function reverseStr(s, k) {
let result = "";
for (let i = 0; i < s.length; i += 2*k) {
let chunk1 = s.substr(i, k);
// reverse first half
chunk1 = chunk1.split("").reverse().join("");
let chunk2 = s.substr(i+k, k);
result += chunk1 + chunk2;
}
return result;
}
console.log(reverseStr("12345678", 2));
You can realize it in three steps. First separate the string into k group. Then reverse the string of each group at an even index. Last join all the group together.
function reverseStr(s, k) {
return s
.replace(new RegExp('\\w{' + k + '}', 'g'), $2 => $2 + '|')
.split('|')
.map((item, i) =>
i % 2 !== 0
? item
: item
.split('')
.reverse()
.join('')
)
.join('');
}
console.log(reverseStr('abcdefg', 2));
Reverse string in chunks of size k
//make a variable string with some letters to test.
let str = "abcdefg";
//k represents is the number of letters to reverse starting at 0 to position k
let k = 4;
//select the first k letters and split on blankstring, converting to a list
//then reversing that list, collapsing it to a string with join, then
//re-append the original string at position k to the end.
result = (str.substring(0,k).split('')).reverse().join('') + str.substring(k);
console.log({result});
Prints:
{ result: 'dcbaefg' }

Converting Infix to prefix notation in JavaScript

(I did ask a similar question in the past, but the documentation was wrong, so this is the correct version of that past question)
Please help me in JavaScript: The program that I am coding is one that takes in an expression in prefix notation and outputs the same expression in infix notation. The idea behind this program is as follows:
if the user enters + 1 2 the expected output is 1 + 2. All valid symbols are +, -, *, /, and %. The amount of numbers that the user can enter should be limitless (so for example, if I enter + + + + + + + + + 1 2 3 4 5 6 7 8 9 10, the program should return 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10).
Could someone please help me fill in the comment out portion of the loop, and if you think there's a better approach to the problem entirely, I am open to that!
function infix(input) {
var x = input.split(''); // splits each variable and stores it in an array
var output = [];
var final = " "; // will be used to store our infix expression
for (var i = 0; i < x.length; i++) {
//if x[i] is any of the following : "+, -, *, /, or %" , store it in array output at index 0
//else if x[i] is a number : store it in an index of array output that is >= 1
}
for (var j = 0; j < output.length; j++) {
var final = x[0] + x[j];
}
console.log(final);
}
infix("1 + 2 + 3") // should output "+ + 1 2 3"
infix("1 - 2 % 3 + 1 * 4") // should output "- % + * 1 2 3 1 4"
Your code is mostly there.
The main logic of your program needs to split your input string into two sections (arrays). One for symbols/operands, and another for numbers. Checking if x[i] is "+, -, ..." will allow us to check if the current symbol is an operand, which is a good start. However, we don't want to store it at index 0 of the output array. If you did this, then all of the symbols would be inputted in reverse order (which isn't what you want). Instead, you can add your symbols to the end of your array. In the code snippet below, I have pushed all symbols into the operands array.
So, how can we check if x[i] is a symbol? One way is to check x[i] against every symbol like so:
if(x[i] === "+" || x[i] === "-" || ... ) {
operands.push(x[i]); // add the symbol to the end of the array
}
Or, you could write this a little more cleanly using .includes(), which removes the need to multiple ||:
var symbols = ['+', '-', '*', '/', '%'];
...
if(symbols.includes(x[i])) {
operands.push(x[i]);
}
If the current character (x[i]) isn't a symbol then it must be a number (as we split on spaces). So, you can then add an else, and push the current character into the numbers array.
After you've looped through all characters, you'll have an array of operands, in the order that they appeared in the input string, and an array of numbers, also in the order they appeared in the input string. Our last step is to convert the operands and numbers array into strings, where each element in these arrays are separated by a space. The easiest way to do this is by using the .join() method, which will join every element in your array into a string, separating each element by the argument you give it.
By doing all of this, you can get your desired output:
function infix(input) {
var x = input.split(' '); // splits each character and stores it in an array
var operands = [];
var numbers = [];
var symbols = ['+', '-', '/', '*', '%'];
for (var i = 0; i < x.length; i++) {
if(symbols.includes(x[i])) {
operands.push(x[i]);
} else {
numbers.push(x[i]);
}
}
var final = operands.join(' ') +' ' +numbers.join(' ');
return final;
}
console.log(infix("1 + 2 + 3")); // "+ + 1 2 3"
console.log(infix("1 - 2 % 3 + 1 * 4")); // "- % + * 1 2 3 1 4"
Or, you can do this more concisely by using the .match() method to get the non-numeric (or space) characters and another to get the numeric characters:
const infix = input =>
[...input.match(/[^\d\s]/g), ...input.match(/\d+/g)].join(' ');
console.log(infix("1 + 2 + 3")); // "+ + 1 2 3"
console.log(infix("1 - 2 % 3 + 1 * 4")); // "- % + * 1 2 3 1 4"

adding a space to every space in a string, then cycling back around until length is met

I have the following while loop as part of my text justify function. The idea is that I have text strings (str) that need to be justified (spaces added to existing spaces in between words) to equal to a given length (len)
The catch is I can only add one space to an existing space at a time before I iterate over to the next space in the string and add another space there. If that's it for all spaces in the string and it's still not at the required length, I cycle back over to the original space (now two spaces) and add another. Then it goes to the next space between words and so on and so on. The idea is that any spaces between words in the string should not have a differential of more than one space (i.e. Lorem---ipsum--dolor--sit, not Lorem----ipsum--dolor-sit)
From my research, I decided that using a substring method off the original string to add that first extra space, then I will increment the index and move to the next space in the string and repeat the add. Here's my code:
var indexOf = str.indexOf(" ", 0);
if ( indexOf > -1 ) {
while ( indexOf > -1 && str.length < len ) {
//using a regexp to find a space before a character
var space = /\s(?=\b)/.exec(str);
str = str.substring(0, indexOf + 1) + " " + str.substring(indexOf + 1);
//go to next space in string
indexOf = str.indexOf(space, indexOf + 2);
if ( indexOf === -1 ) {
//loops back to beginning of string
indexOf = str.indexOf(space, 0);
}
}
}
finalResults.push(str);
This code works most of the time, but I noticed that there are instances where the cycle of spacing is not correct. For example, it generates the following string:
sit----amet,--blandit
when the correct iteration would be
sit---amet,---blandit
Any assistance in making this code properly iterate over every space (to add one space) in the string once, then cycling back around to the beginning of the string to start over until the desired length is achieved would be most appreciated.
I think it's more efficient to compute the number spaces required in the beginning.
var s = "today is a friday";
var totalLength = 40;
var tokens = s.split(/\s+/);
var noSpaceLength = s.replace(/\s+/g,'').length;
var minSpace = Math.floor((totalLength - noSpaceLength)/(tokens.length-1));
var remainder = (totalLength - noSpaceLength) % (tokens.length-1);
var out = tokens[0];
for (var i = 1; i < tokens.length; i++) {
var spaces = (i <= remainder ? minSpace+1 : minSpace);
out += "-".repeat(spaces) + tokens[i];
}
$('#out').text(out);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="out"></div>
This solution
splits the string (s) into words in an array (a)
finds the number of spaces to be added between all words (add)
finds the remainder of spaces to be added between first words (rem)
then sticks the words with add spaces + one if rem is not exhausted
Code
var s = "Caballo sin Nombre"; // assume one space between words
var len = 21; // desired length
var need = len - s.length;
var a = s.split(/ /); // split s
// need>0 and at least two words
if (need > 0 && a.length>1) {
var add = Math.floor(need / (a.length-1)) + 1; // all spaces need that (+existing)
var rem = need % (a.length-1); // remainder
var sp = '';
while (add-- > 0) sp += ' ';
// replace
var i,res = ''; // result
for (i=0 ; i<a.length-1 ; i++) {
res += a[i] + sp;
if (rem-- > 0) res += ' '; // remainder
}
res += a[i];
s = res;
}
console.log("'" + s + "' is " + s.length + " chars long.");
This function adds the spaces using a global replace, carefully limiting the text size.
function expand (txt, colwidth) {
txt = txt.replace (/\s\s+/, ' '); // Ensure no multiple spaces in txt
for (var spaces = ' ', // Spaces to check for
limit = colwidth - txt.length; // number of additional spaces required
limit > 0; // do while limit is positive
spaces += ' ') // add 1 to spaces to search for
txt = txt.replace (RegExp (spaces, 'g'),
function (r) {
// If limit > 0 then add a space else do not.
return limit > 0 && --limit ? r + ' ' : r
});
return txt;
}
for (var w = 21; w--;) console.log (expand ('this is a test.', w));
Shows this on console:
this is a test.
this is a test.
this is a test.
this is a test.
14 this is a test.

Categories

Resources