This question already has an answer here:
Correct substring position after replacement
(1 answer)
Closed 5 years ago.
TL;DR
I have function that replace text, a string and cursor position (a number) and I need to get corrected position (a number) for new string that is created with replace function if the length of the string changes:
input and cursor position: foo ba|r text
replacement: foo -> baz_text, bar -> quux_text
result: baz_text qu|ux_text text
input and cursor position: foo bar| text
replacement: foo -> baz_text, bar -> quux_text
result: baz_text quux_text| text
input and cursor position: foo bar| text
replacement: foo -> f, bar -> b
result: f b| text
input and cursor position: foo b|ar text
replacement: foo -> f, bar -> b
result: f b| text
the problem is that I can use substring on original text but then the replacement will not match whole word so it need to be done for whole text but then substring will not match the replacement.
I'm also fine with solution that cursor is always at the end of the word when original cursor is in the middle of the replaced word.
and now my implementation, in jQuery Terminal I have a array of formatters functions in:
$.terminal.defaults.formatters
they accept a string and it should return new string it work fine except this case:
when I have formatter that change length if break the command line, for instance this formatter:
$.terminal.defaults.formatters.push(function(string) {
return string.replace(/:smile:/g, 'a')
.replace(/(foo|bar|baz)/g, 'text_$1');
});
then the cursor position was wrong when command line get new string.
I've try to fix this but it don't work as expected, the internal of the terminal look like this,
when I change position I'm crating another variable formatted_position that's use in command line to display the cursor. to get that value I use this:
formatted_position = position;
var string = formatting(command);
var len = $.terminal.length(string);
var command_len = $.terminal.length(command);
if (len !== command_len) {
var orig_sub = $.terminal.substring(command, 0, position);
var orig_len = $.terminal.length(orig_sub);
var formatted = formatting(orig_sub);
var formatted_len = $.terminal.length(formatted);
if (orig_len > formatted_len) {
// if formatting make substring - (text before cursor)
// shorter then subtract the difference
formatted_position -= orig_len - formatted_len;
} else if (orig_len < formatted_len) {
// if the formatted string is longer add difference
formatted_position += formatted_len - orig_len;
}
}
if (formatted_position > len) {
formatted_position = len;
} else if (formatted_position < 0) {
formatted_position = 0;
}
$.terminal.substring and $.terminal.length are helper functions that are terminal formatting aware (text that look like this [[b;#fff;]hello]) if you will write solution you can use normal text and use string methods.
the problem is that when I move the cursor in the middle of the word that is changed
it kind of work when text is longer, but for shorter string the cursor jump to the right when text is in the middle of the word that got replaced.
I've try to fix this as well using this code:
function find_diff(callback) {
var start = position === 0 ? 0 : position - 1;
for (var i = start; i < command_len; ++i) {
var substr = $.terminal.substring(command, 0, i);
var next_substr = $.terminal.substring(command, 0, i + 1);
var formatted = formatting(next_substr);
var substr_len = $.terminal.length(substr);
var formatted_len = $.terminal.length(formatted);
var diff = Math.abs(substr_len - formatted_len);
if (diff > 1) {
return diff;
}
}
return 0;
}
...
} else if (len < command_len) {
formatted_position -= find_diff();
} else if (len > command_len) {
formatted_position += find_diff();
}
but this I think make it even worse becuase it find diff when cursor is before or in the middle of replaced word and it should find diff only when cursor is in the middle of replaced word.
You can see the result of my attempts in this codepen https://codepen.io/jcubic/pen/qPVMPg?editors=0110 (that allow to type emoji and foo bar baz get replaced by text_$1)
UPDATE:
I've make it kind of work with this code:
// ---------------------------------------------------------------------
// :: functions used to calculate position of cursor when formatting
// :: change length of output text like with emoji demo
// ---------------------------------------------------------------------
function split(formatted, normal) {
function longer(str) {
return found && length(str) > length(found) || !found;
}
var formatted_len = $.terminal.length(formatted);
var normal_len = $.terminal.length(normal);
var found;
for (var i = normal_len; i > 1; i--) {
var test_normal = $.terminal.substring(normal, 0, i);
var formatted_normal = formatting(test_normal);
for (var j = formatted_len; j > 1; j--) {
var test_formatted = $.terminal.substring(formatted, 0, j);
if (test_formatted === formatted_normal &&
longer(test_normal)) {
found = test_normal;
}
}
}
return found || '';
}
// ---------------------------------------------------------------------
// :: return index after next word that got replaced by formatting
// :: and change length of text
// ---------------------------------------------------------------------
function index_after_formatting(position) {
var start = position === 0 ? 0 : position - 1;
var command_len = $.terminal.length(command);
for (var i = start; i < command_len; ++i) {
var substr = $.terminal.substring(command, 0, i);
var next_substr = $.terminal.substring(command, 0, i + 1);
var formatted_substr = formatting(substr);
var formatted_next = formatting(next_substr);
var substr_len = length(formatted_substr);
var next_len = length(formatted_next);
var test_diff = Math.abs(next_len - substr_len);
if (test_diff > 1) {
return i;
}
}
}
// ---------------------------------------------------------------------
// :: main function that return corrected cursor position on display
// :: if cursor is in the middle of the word that is shorter the before
// :: applying formatting then the corrected position is after the word
// :: so it stay in place when you move real cursor in the middle
// :: of the word
// ---------------------------------------------------------------------
function get_formatted_position(position) {
var formatted_position = position;
var string = formatting(command);
var len = $.terminal.length(string);
var command_len = $.terminal.length(command);
if (len !== command_len) {
var orig_sub = $.terminal.substring(command, 0, position);
var orig_len = $.terminal.length(orig_sub);
var sub = formatting(orig_sub);
var sub_len = $.terminal.length(sub);
var diff = Math.abs(orig_len - sub_len);
if (false && orig_len > sub_len) {
formatted_position -= diff;
} else if (false && orig_len < sub_len) {
formatted_position += diff;
} else {
var index = index_after_formatting(position);
var to_end = $.terminal.substring(command, 0, index + 1);
//formatted_position -= length(to_end) - orig_len;
formatted_position -= orig_len - sub_len;
if (orig_sub && orig_sub !== to_end) {
var formatted_to_end = formatting(to_end);
var common = split(formatted_to_end, orig_sub);
var re = new RegExp('^' + $.terminal.escape_regex(common));
var to_end_rest = to_end.replace(re, '');
var to_end_rest_len = length(formatting(to_end_rest));
if (common orig_sub !== common) {
var commnon_len = length(formatting(common));
formatted_position = commnon_len + to_end_rest_len;
}
}
}
if (formatted_position > len) {
formatted_position = len;
} else if (formatted_position < 0) {
formatted_position = 0;
}
}
return formatted_position;
}
it don't work for one case when you type emoji as first character and the cursor is in the middle of :smile: word. How to fix get_formatted_position function to have correct fixed position after replace?
UPDATE: I've ask different and simple question and got the solution using trackingReplace function that accept regex and string, so I've change the API for formatters to accept array with regex and string along the function Correct substring position after replacement
So I was able to accomplish the given task, however I wasn't able to implement it into the library as I am not sure how to implements many things there.
I made it in vanilla javascript so there shouldn't be any hiccups while implementing into the library. The script is mostly dependant on the selectionStart and selectionEnd properties available on textarea, input or similar elements. After all replacement is done, the new selection is set to the textarea using setSelectionRange method.
// sel = [selectionStart, selectionEnd]
function updateSelection(sel, replaceStart, oldLength, newLength){
var orig = sel.map(a => a)
var diff = newLength - oldLength
var replaceEnd = replaceStart + oldLength
if(replaceEnd <= sel[0]){
// Replacement occurs before selection
sel[0] += diff
sel[1] += diff
console.log('Replacement occurs before selection', orig, sel)
}else if(replaceStart <= sel[0]){
// Replacement starts before selection
if(replaceEnd >= sel[1]){
// and ends after selection
sel[1] += diff
}else{
// and ends in selection
}
console.log('Replacement starts before selection', orig, sel)
}else if(replaceStart <= sel[1]){
// Replacement starts in selection
if(replaceEnd < sel[1]){
// and ends in seledtion
}else{
// and ends after selection
sel[1] += diff
}
console.log('Replacement starts in selection', orig, sel)
}
}
Here is whole demo: codepen.
PS: From my observations the format script runs way to often.
Related
I have a large text from which I read data according to the scheme. Key words are placed in the "smallArtName" array. The scheme looks like this:
(key word) xxx (cordX|cordY)
I can't convert the string I received to a number. It seems to me that the reason is white space, visible in the terminal in the picture. I tried to use the replace method which works for sample text, but not for my value.
I'm a beginner and I could probably do it simpler, but the code I wrote works, and this is the most important thing for now.
for (i = 0; i < smallArtName.length; i++) {
var n = art.artPrintScreen.indexOf(smallArtName[i]);
if (n > 0) {
var tempString = art.artPrintScreen.substring(n, n + 100);
betweenChar = tempString.indexOf('|');
for (k = betweenChar - 10; k <= betweenChar + 10; k++) {
if (tempString[k] == '(') {
xStart = k;
}
if (tempString[k] == ')') {
yEnd = k;
}
}
cordX = tempString.slice(xStart + 1, betweenChar);
cordY = tempString.slice(betweenChar + 1, yEnd);
strTest = " t est".replace(/\s/g, '')
var cordY2 = cordY.replace(/\s/g, '')
console.log(typeof (cordY))
console.log(cordY2)
console.log(cordY2[0])
console.log(cordY2[1])
console.log(cordY2[2])
console.log(cordY2[3])
console.log(cordY2[4])
console.log(cordY2[5])
console.log(strTest)
var cordYtest = parseInt(cordY2, 10);
console.log(cordYtest)
}
}
You just need to change the regex so that you replace everything except digits and the negative sign - rather than just whitespace. i.e.
change
var cordY2 = cordY.replace(/\s/g, '')
to
var cordY2 = parseInt(cordY.replace(/[^0-9-]/g, ''), 10);
So that the variable cordY2 contains the number you require.
I am making a javascript function that will input a string, and output a "spongebob mocking text"
basically, you input "Hello, this is a message to the world" and you would get "HeLlO, ThIS iS a MeSsAGe tO tHE wORlD"
basically, randomly decide wheather to capitalize a letter or not. I made a function which i thought would do that, but it didn't work. here is the code that I tested in the js console:
function memify(input) { // function called memify()
var il = input.length; // gets the length of the input
var newinput = input; // creates a new variable that will be changed from input.
for (var i=0;i>il;i++) {
var rng = Math.floor((Math.random()*2)); // random number between 0 and 1. 0 = upper 1 = lower
if (rng === 0) {
newinput.charAt(i).toUpperCase();
}
else {
newinput.charAt(i).toLowerCase();
}
}
return newinput;
}
var text = prompt();
var textmeme = memify(text);
alert(textmeme);
Why is this not working? Do I have an error in my code? Any help will be greatly appreciated.
When you do
newinput.charAt(i).toUpperCase();
you're creating a new uppercase character, but you aren't doing anything with it; it's just an unused expression, so there's no visible change. Primitives (including strings) are immutable - you should explicitly reassign a string to something else (eg newString += newinput.charAt(i).toUpperCase();) to see an effect.
You also need to use
for (var i = 0; i < il; i++) {
// ^
instead of
for (var i = 0; i > il; i++) {
// ^
else, no iterations will run at all.
function memify(input) { // function called memify()
var il = input.length; // gets the length of the input
let changedStr = '';
for (var i = 0; i < il; i++) {
var rng = Math.floor((Math.random() * 2)); // random number between 0 and 1. 0 = upper 1 = lower
if (rng === 0) {
changedStr += input.charAt(i).toUpperCase();
} else {
changedStr += input.charAt(i).toLowerCase();
}
}
return changedStr;
}
var text = prompt();
var textmeme = memify(text);
console.log(textmeme);
Another option, using .map, which looks much cleaner IMO:
const memify = input => [...input]
.map(char => Math.random() < 0.5 ? char.toUpperCase() : char.toLowerCase())
.join('');
console.log(memify(prompt()));
Or more concise, safer and generally better solution :). It does not require for loop, checking length of string and other error prone stuff.
function memify(input) {
var rng = () => Math.random() > 0.5;
var res = input.split('').map( letter =>
rng() ? letter.toUpperCase() : letter.toLowerCase()
).join('');
return res;
}
var textmeme = memify("Hello World");
console.log(textmeme);
Please up-vote if it was helpful :)
Say you have the following string:
FJKAUNOJDCUTCRHBYDLXKEODVBWTYPTSHASQQFCPRMLDXIJMYPVOHBDUGSMBLMVUMMZYHULSUIZIMZTICQORLNTOVKVAMQTKHVRIFMNTSLYGHEHFAHWWATLYAPEXTHEPKJUGDVWUDDPRQLUZMSZOJPSIKAIHLTONYXAULECXXKWFQOIKELWOHRVRUCXIAASKHMWTMAJEWGEESLWRTQKVHRRCDYXNT
LDSUPXMQTQDFAQAPYBGXPOLOCLFQNGNKPKOBHZWHRXAWAWJKMTJSLDLNHMUGVVOPSAMRUJEYUOBPFNEHPZZCLPNZKWMTCXERPZRFKSXVEZTYCXFRHRGEITWHRRYPWSVAYBUHCERJXDCYAVICPTNBGIODLYLMEYLISEYNXNMCDPJJRCTLYNFMJZQNCLAGHUDVLYIGASGXSZYPZKLAWQUDVNTWGFFY
FFSMQWUNUPZRJMTHACFELGHDZEJWFDWVPYOZEVEJKQWHQAHOCIYWGVLPSHFESCGEUCJGYLGDWPIWIDWZZXRUFXERABQJOXZALQOCSAYBRHXQQGUDADYSORTYZQPWGMBLNAQOFODSNXSZFURUNPMZGHTAJUJROIGMRKIZHSFUSKIZJJTLGOEEPBMIXISDHOAIFNFEKKSLEXSJLSGLCYYFEQBKIZZTQQ
XBQZAPXAAIFQEIXELQEZGFEPCKFPGXULLAHXTSRXDEMKFKABUTAABSLNQBNMXNEPODPGAORYJXCHCGKECLJVRBPRLHORREEIZOBSHDSCETTTNFTSMQPQIJBLKNZDMXOTRBNMTKHHCZQQMSLOAXJQKRHDGZVGITHYGVDXRTVBJEAHYBYRYKJAVXPOKHFFMEPHAGFOOPFNKQAUGYLVPWUJUPCUGGIXGR
AMELUTEPYILBIUOCKKUUBJROQFTXMZRLXBAMHSDTEKRRIKZUFNLGTQAEUINMBPYTWXULQNIIRXHHGQDPENXAJNWXULFBNKBRINUMTRBFWBYVNKNKDFR
I'm trying to find the smallest substring containing the letters ABCDA.
I tried a regex approach.
console.log(str.match(/[A].*?[B].*?[C].*?[D].*?[A]/gm).sort((a, b) => a.length - b.length)[0]);
This works, but it only find strings where ABCDA appear (in that order). Meaning it won't find substring where the letters appear in a order like this: BCDAA
I'm trying to change my regex to account for this. How would I do that without using | and type out all the different cases?
You can't.
Let's consider a special case: Assume the letters you are looking for are A, A, and B. At some point in your regexp there will certainly be a B. However, the parts to the left and to the right of the B are independent of each other, so you cannot refer from one to the other. How many As are matched in the subexpression to the right of the B depends on the number of As being already matched in the left part. This is not possible with regular expressions, so you will have to unfold all the different orders, which can be many!
Another popular example that illustrates the problem is to match opening brackets with closing brackets. It's not possible to write a regular expression asserting that in a given string a sequence of opening brackets is followed by a sequence of closing brackets of the same length. The reason for this is that to count the brackets you would need a stack machine in contrast to a finite state machine but regular expressions are limited to patterns that can be matched using FSMs.
This algorithm doesn't use a regex, but found both solutions as well.
var haystack = 'FJKAUNOJDCUTCRHBYDLXKEODVBWTYPTSHASQQFCPRMLDXIJMYPVOHBDUGSMBLMVUMMZYHULSUIZIMZTICQORLNTOVKVAMQTKHVRIFMNTSLYGHEHFAHWWATLYAPEXTHEPKJUGDVWUDDPRQLUZMSZOJPSIKAIHLTONYXAULECXXKWFQOIKELWOHRVRUCXIAASKHMWTMAJEWGEESLWRTQKVHRRCDYXNTLDSUPXMQTQDFAQAPYBGXPOLOCLFQNGNKPKOBHZWHRXAWAWJKMTJSLDLNHMUGVVOPSAMRUJEYUOBPFNEHPZZCLPNZKWMTCXERPZRFKSXVEZTYCXFRHRGEITWHRRYPWSVAYBUHCERJXDCYAVICPTNBGIODLYLMEYLISEYNXNMCDPJJRCTLYNFMJZQNCLAGHUDVLYIGASGXSZYPZKLAWQUDVNTWGFFYFFSMQWUNUPZRJMTHACFELGHDZEJWFDWVPYOZEVEJKQWHQAHOCIYWGVLPSHFESCGEUCJGYLGDWPIWIDWZZXRUFXERABQJOXZALQOCSAYBRHXQQGUDADYSORTYZQPWGMBLNAQOFODSNXSZFURUNPMZGHTAJUJROIGMRKIZHSFUSKIZJJTLGOEEPBMIXISDHOAIFNFEKKSLEXSJLSGLCYYFEQBKIZZTQQXBQZAPXAAIFQEIXELQEZGFEPCKFPGXULLAHXTSRXDEMKFKABUTAABSLNQBNMXNEPODPGAORYJXCHCGKECLJVRBPRLHORREEIZOBSHDSCETTTNFTSMQPQIJBLKNZDMXOTRBNMTKHHCZQQMSLOAXJQKRHDGZVGITHYGVDXRTVBJEAHYBYRYKJAVXPOKHFFMEPHAGFOOPFNKQAUGYLVPWUJUPCUGGIXGRAMELUTEPYILBIUOCKKUUBJROQFTXMZRLXBAMHSDTEKRRIKZUFNLGTQAEUINMBPYTWXULQNIIRXHHGQDPENXAJNWXULFBNKBRINUMTRBFWBYVNKNKDFR';
var needle = 'ABCDA'; // the order of letters doesn't matter
var letters = {};
needle.split('').forEach(function(ch) {
letters[ch] = letters[ch] || 0;
letters[ch]++;
});
var shortestSubstringLength = haystack.length;
var shortestSubstrings = []; // storage for found substrings
var startingPos = 0;
var length;
var currentPos;
var notFound;
var letterKeys = Object.keys(letters); // unique leters
do {
lettersLeft = JSON.parse(JSON.stringify(letters)); // copy letters count object
notFound = false;
posStart = haystack.length;
posEnd = 0;
letterKeys.forEach(function(ch) {
currentPos = startingPos;
while (!notFound && lettersLeft[ch] > 0) {
currentPos = haystack.indexOf(ch, currentPos);
if (currentPos >= 0) {
lettersLeft[ch]--;
posStart = Math.min(currentPos, posStart);
posEnd = Math.max(currentPos, posEnd);
currentPos++;
} else {
notFound = true;
}
}
});
if (!notFound) {
length = posEnd - posStart + 1;
startingPos = posStart + 1; // starting position for next iteration
}
if (!notFound && length === shortestSubstringLength) {
shortestSubstrings.push(haystack.substr(posStart, length));
}
if (!notFound && length < shortestSubstringLength) {
shortestSubstrings = [haystack.substr(posStart, length)];
shortestSubstringLength = length;
}
} while (!notFound);
console.log(shortestSubstrings);
Maybe not as clear as using regex could be (well, for me regex are never really clear :D ) you can use brute force (not so brute)
Create an index of "valid" points of your string (those with the letters you want) and iterate with a double loop over it getting substrings containing at least 5 of those points, checking that they are valid solutions. Maybe not the most efficient way, but easy to implement, to understand, and probably to optimize.
var haystack="UGDVWUDDPRQLUZMSZOJPSIKAIHLTONYXAULECXXKWFQOIKELWOHRVRUCXIAASKHMWTMAJEWGEESLWRTQKVHRRCDYXNTLDSUPXMQTQDFAQAPYBGXPOLOCLFQNGNKPKOBHZWHRXAWAWJKMTJSLDLNHMUGVVOPSAMRUJEYUOBPFNEHPZZCLPNZKWMTCXERPZRFKSXVEZTYCXFRHRGEITWHRRYPWSVAYBUHCERJXDCYAVICPTNBGIODLYLMEYLISEYNXNMCDPJJRCTLYNFMJZQNCLAGHUDVLYIGASGXSZYPZKLAWQUDVNTWGFFYFFSMQWUNUPZRJMTHACFELGHDZEJWFDWVPYOZEVEJKQWHQAHOCIYWGVLPSHFESCGEUCJGYLGDWPIWIDWZZXRUFXERABQJOXZALQOCSAYBRHXQQGUDADYSORTYZQPWGMBLNAQOFODSNXSZFURUNPMZGHTAJUJROIGMRKIZHSFUSKIZJJTLGOEEPBMIXISDHOAIFNFEKKSLEXSJLSGLCYYFEQBKIZZTQQXBQZAPXAAIFQEIXELQEZGFEPCKFPGXULLAHXTSRXDEMKFKABUTAABSLNQBNMXNEPODPGAORYJXCHCGKECLJVRBPRLHORREEIZOBSHDSCETTTNFTSMQPQIJBLKNZDMXOTRBNMTKHHCZQQMSLOAXJQKRHDGZVGITHYGVDXRTVBJEAHYBYRYKJAVXPOKHFFMEPHAGFOOPFNKQAUGYLVPWUJUPCUGGIXGR";
var needle="ABCD";
var size=haystack.length;
var candidate_substring="";
var minimal_length=size;
var solutions=new Array();
var points=Array();
for(var i=0;i<size;i++){
if(needle.indexOf(haystack[i])>-1) points.push(i);
}
var limit_i= points.length-4;
var limit_k= points.length;
for (var i=0;i<limit_i;i++){
for(var k=i;k<limit_k;k++){
if(points[k]-points[i]+1<=minimal_length){
candidate_substring=haystack.substr(points[i],points[k]-points[i]+1);
if(is_valid(candidate_substring)){
solutions.push(candidate_substring);
if(candidate_substring.length < minimal_length) minimal_length=candidate_substring.length;
}
}
}
}
document.write('<p>Solution length:'+minimal_length+'<p>');
for(var i=0;i<solutions.length;i++){
if(solutions[i].length<=minimal_length) document.write('<p>Solution:'+solutions[i]+'<p>');
}
function is_valid(candidate_substring){
//verify we've got all characters
for(var j=0;j<candidate_substring.length;j++){
if(candidate_substring.indexOf(needle.charAt(j))<0) return false;
}
//...and verify we have two "A"
if(candidate_substring.indexOf("A")==candidate_substring.lastIndexOf("A")) return false;
return true;
}
Just had this problem in an interview as a coding assignment and came up with another solution, (it's not as optimal as the one above but maybe it's easier to understand).
function MinWindowSubstring(strArr) {
const N = strArr[0];
const K = strArr[1];
const letters = {};
K.split('').forEach( (character) => {
letters[character] = letters[character] ? letters[character] + 1 : 1;
});
let possibleSequencesList = [];
const letterKeys = Object.keys(letters);
for(let i=0; i< N.length; i++) {
const char = N[i];
if (new String(letterKeys).indexOf(char) !== -1) {
// found a character in the string
// update all previus sequences
possibleSequencesList.forEach((seq) => {
if(!seq.sequenceComplete) {
seq[char] = seq[char]-1;
seq.lastIndex = i;
// check if sequence is complete
var sequenceComplete = true;
letterKeys.forEach( (letter) => {
if(seq[letter] > 0) {
sequenceComplete = false;
}
});
seq.sequenceComplete = sequenceComplete
}
})
// create a new sequence starting from it
const newSeq = {
startPoint: i,
lastIndex: i,
sequenceComplete: false,
...letters
}
newSeq[char] = newSeq[char]-1;
possibleSequencesList.push(newSeq);
}
}
// cleanup sequences
let sequencesList = possibleSequencesList.filter(sequence => sequence.sequenceComplete);
let output = [];
let minLength = N.length;
// find the smalles one
sequencesList.forEach( seq => {
if( (seq.lastIndex - seq.startPoint) < minLength) {
minLength = seq.lastIndex - seq.startPoint;
output = N.substring(seq.startPoint, seq.lastIndex + 1);
}
})
return output;
}
if this type character '這' = NonEnglish each will take up 2 word space, and English will take up 1 word space, Max length limit is 10 word space; How to get the first 10 space.
for below example how to get the result This這 is?
I'm trying to use for loop from first word but I don't know how to get each word in string...
string = "This這 is是 English中文 …";
var NonEnglish = "[^\u0000-\u0080]+",
Pattern = new RegExp(NonEnglish),
MaxLength = 10,
Ratio = 2;
If you mean you want to get that part of the string where it's length has reached 10, here's the answer:
var string = "This這 is是 English中文 …";
function check(string){
// Length of A-Za-z characters is 1, and other characters which OP wants is 2
var length = i = 0, len = string.length;
// you can iterate over strings just as like arrays
for(;i < len; i++){
// if the character is what the OP wants, add 2, else 1
length += /\u0000-\u0080/.test(string[i]) ? 2 : 1;
// if length is >= 10, come out of loop
if(length >= 10) break;
}
// return string from the first letter till the index where we aborted the for loop
return string.substr(0, i);
}
alert(check(string));
Live Demo
EDIT 1:
Replaced .match with .test. The former returns a whole array while the latter simply returns true or false.
Improved RegEx. Since we are checking only one character, no need for ^ and + that were before.
Replaced len with string.length. Here's why.
I'd suggest something along the following lines (assuming that you're trying to break the string up into snippets that are <= 10 bytes in length):
string = "This這 is是 English中文 …";
function byteCount(text) {
//get the number of bytes consumed by a string
return encodeURI(text).split(/%..|./).length - 1;
}
function tokenize(text, targetLen) {
//break a string up into snippets that are <= to our target length
var result = [];
var pos = 0;
var current = "";
while (pos < text.length) {
var next = current + text.charAt(pos);
if (byteCount(next) > targetLen) {
result.push(current);
current = "";
pos--;
}
else if (byteCount(next) == targetLen) {
result.push(next);
current = "";
}
else {
current = next;
}
pos++;
}
if (current != "") {
result.push(current);
}
return result;
};
console.log(tokenize(string, 10));
http://jsfiddle.net/5pc6L/
I try to create a more realistic typewriting, which makes typing errors and corrects them by removing/adding chars.
Yet, I made a simple solution which can add and remove chars via slice. But I don't know how to implement the misstyping-feature.
Here's what I've done so far:
var str = 'Foo bar is not equal to bar foo.';
var len = str.length;
var elem = document.body;
var rem = len;
var add = 0;
(function type(){
if(!rem || add === len) return;
// write
elem.innerHTML = str.slice(0, ++add);
// remove
// elem.innerHTML = str.slice(0, --rem);
setTimeout(type, 100);
}());
http://jsbin.com/upufaf/6/edit
Any ideas or already finished solutions how to get this to work?
BTW: There's already something called realistic typewriter, however I think this solution is a bit too bloated for that simple problem.
var str = 'Foo bar is not equal to bar foo.' ;
var alphabet = 'abcdefghijklmnopqrstuvwxyz' ;
var pos = 0 ;
var tmpStr = '' ;
var doBackspace = false ;
function startTyping (str) {
if(doBackspace) {
tmpStr = tmpStr.slice(0,tmpStr.length-1);
doBackspace = false ;
} else {
if(Math.ceil((Math.random()*100)) < 80) {
tmpStr += str[pos] ;
pos++ ;
} else {
tmpStr += alphabet[Math.floor(Math.random()*(alphabet.length))] ;
doBackspace = true ;
}
}
$('body').html(tmpStr) ;
if(pos < str.length) {
setTimeout(function(){
startTyping(str)
}, 300) ;
}
}
$(document).ready(function(){
startTyping(str) ;
});
Can change interval 300 to higher value to reduce typing speed, also add characters to alphabet to increase number of possible mistaken chars, 80 to lower value to increase chance for a mistake