I am trying to split this string down into sections:
2 h 3 12 s
From this I am trying to create a longer string such as:
00020312
DDHHMMSS
I am getting an error on line 21:
Cannot read property split of undefined
//calculate which ticket is the oldest begining
jQuery('.table-list-search tr').each(function(){
var downTime = jQuery(this).find('.downTime').text();
downTime = String(downTime);
var hasDays = downTime.split("d");
var hasHours = downTime.split("h");
var hasMins = downTime.split("m");
var hasSeconds = downTime.split("s");
if(hasDays[1] === undefined){
var downTimeD = downTime.split(" d")[0];
downTime = downTime.split(" d ")[1];
if(downTimeD <=9){
downTimeD = downTimeD.toString(downTimeD);
downTimeD = '0' + downTimeD;
};
}else{
var downTimeD = '00';
};
if(hasHours[1] === undefined){
var downTimeH = downTime.split(" h")[0];
downTime = downTime.split(" h ")[1];
if(downTimeH <=9){
downTimeH = downTimeH.toString(downTimeD);
downTimeH = '0' + downTimeH;
};
}
Your task can be done by applying a regular expression:
const rx=/(?:(\d+) *d *)?(?:(\d+) *h *)?(?:(\d+) *m *)?(?:(\d+) *s *)?/;
const fmt=s=>(s||'0').padStart(2,'0');
console.log('DDHHMMSS')
console.log( rx.exec('2 h 3 m 12 s').slice(1).map(fmt).join('') )
console.log( rx.exec('1d24m' ).slice(1).map(fmt).join('') )
The expression looks for (optional) groups of numbers with the letters 'd', 'h', 'm' or 's' behind them, qualifying each number in turn as "days", "hours", "minutes" and "seconds".
Debuggex Demo
The result is returned as an array. From this array only the elements 1 to 4 being used, as the 0-th element would be the complete matched string.
The whole job can also be packed into a single function like:
fmta=dt=>/(?:(\d+) *d *)?(?:(\d+) *h *)?(?:(\d+) *m *)?(?:(\d+) *s *)?/
.exec(dt).slice(1).map(s=>(s||'0').padStart(2,'0')).join('');
console.log(fmta('12d6h56m 45s')); // '12065645'
Where does downTime come from? The error you're getting means that the variable does not exist. It might be a typo somewhere.
Edit - this was posted before the question contained a full code snippet, but the answer stands - if you get such an error, it usually is because the variable is not defined.
Edit 2 - There are a number of weird things in the logic. First if(hasDays[1] === undefined) essentially translates to "downTime has no 'd' in it", so all further attempts to break on 'd' do not split the string. So downTime = downTime.split(" d ")[1]; would never yield anything - downTime has no d's, so it can't be split on them.
Then there' a corner case in the line downTime = downTime.split(" d ")[1]; (and the similar one below). You're splitting on " d "while the above splits are on " d" and "d".
In summary, you would like to change if(hasDays[1] === undefined) to if(hasDays[1] !== undefined) and always split on "d" with no extra spaces, and the same for the things below.
Related
i found the following code at http://rosettacode.org for the Vigenère cipher and i would like to undestand it better.
Could someone explain me what the single lines of code in function ordA(a) and in function(a) do?
function ordA(a) {
return a.charCodeAt(0) - 65;
}
// vigenere
function vigenere2(text, key, decode) {
var i = 0, b;
key = key.toUpperCase().replace(/[^A-Z]/g, '');
return text.toUpperCase().replace(/[^A-Z]/g, '').replace(/[A-Z]/g, function(a) {
b = key[i++ % key.length];
return String.fromCharCode(((ordA(a) + (decode ? 26 - ordA(b) : ordA(b))) % 26 + 65));
});
}
I'm not sure if that is supposed to be example code, but it mainly shows how not to program. Smart decisions are being made, but obviously the problem decomposition, variable naming and documentation leave a lot to be desired. Repeated code, convoluted lines, unexplained code fragments, the list goes on. Decode is a boolean, but the opposite of encryption is decryption not decoding. This code was made to not understand what is going on; what it does on the Rosetta site is mind-boggling in that respect.
returns an index in the English alphabet or ABC, assuming uppercase characters, 0 to 25 instead of 1 to 26 (because you can do modular calculations with zero indexing, not with one based indexing)
return a.charCodeAt(0) - 65;
function definition that takes a plaintext or ciphertext, a key which may be smaller than the plaintext and a Boolean to indicate if we're encoding or decoding
function vigenere2(text, key, decode)
index in plaintext and variable b, which will hold a character of the key for the index
var i = 0, b;
converts the key to uppercase and removed all characters not in the uppercase alphabet as well
key = key.toUpperCase().replace(/[^A-Z]/g, '');
this line is too long obviously; it converts the text to uppercase and removes the non-alphabet characters again
then it replaces the characters in the string using the function defined in the second argument of replace
return text.toUpperCase().replace(/[^A-Z]/g, '').replace(/[A-Z]/g, function(a) {
take the next character of the key in round robin fashion, using the modulus operator, update the index afterwards
b = key[i++ % key.length];
too much going on here, very bad program decomposition; in order of execution:
(decode ? 26 - ordA(b) : ordA(b)): calculate a number in the range to update the index of the plaintext character; use the opposite value for decryption (wrongly called "decoding" here)
(ordA(a) + (decode ? 26 - ordA(b) : ordA(b))) % 26 perform the addition with the calculated number, reduce to 0 to 25 (i.e. when reaching Z continue with A and vice versa)
((ordA(a) + (decode ? 26 - ordA(b) : ordA(b))) % 26 + 65) add 65 so the index is converted back into the ASCII index of uppercase characters, using two completely spurious parentheses
finally, returns a string from one character code result, otherwise + will be addition instead of concatenation
return String.fromCharCode(((ordA(a) + (decode ? 26 - ordA(b) : ordA(b))) % 26 + 65));
well, it needed to end
});
}
Let's show another way of programming this, using well named variables, functions for reused code and regular expressions that badly need a name to explain what they do.
var ALPHABET_SIZE = 'Z'.charCodeAt(0) - 'A'.charCodeAt(0) + 1;
var encrypted = vigenere(false, "B", "Zaphod Breeblebox");
document.body.append('<div>' + encrypted + '</div>');
var decrypted = vigenere(true, "B", encrypted);
document.body.append('<div>' + decrypted + '</div>');
function vigenere(decrypt, key, text) {
key = toJustUppercase(key);
text = toJustUppercase(text);
var textOffset = 0;
// iterate over all characters, performing the function on each of them
return text.replace(/[A-Z]/g, function(textChar) {
var keyChar = key[textOffset++ % key.length];
var cryptedChar = substituteCharacter(decrypt, keyChar, textChar);
return cryptedChar;
});
}
function substituteCharacter(decrypt, keyChar, textChar) {
var keyIndex = charToABCIndex(keyChar);
if (decrypt) {
// create the opposite of the encryption key index
keyIndex = ALPHABET_SIZE - keyIndex;
}
var textIndex = charToABCIndex(textChar);
// the actual Vigenere substitution, the rest is just indexing and conversion
var substitutedIndex = (textIndex + keyIndex) % ALPHABET_SIZE;
var substitutedChar = abcIndexToChar(substitutedIndex);
return substitutedChar;
}
function toJustUppercase(text) {
return text.toUpperCase().replace(/[^A-Z]/g, '')
}
function charToABCIndex(charValue) {
return charValue.charCodeAt(0) - 'A'.charCodeAt(0);
}
function abcIndexToChar(index) {
return String.fromCharCode(index + 'A'.charCodeAt(0));
}
Too many functions you say? Not really, I've not implemented ord and chr, or vigenereEncrypt and viginereDecrypt to make it even easier to read.
Assume there are some strings containing names in different format (each line is a possible user input):
'Guilcher, G.M., Harvey, M. & Hand, J.P.'
'Ri Liesner, Peter Tom Collins, Michael Richards'
'Manco-Johnson M, Santagostino E, Ljung R.'
I need to transform those names to get the format Lastname ABC. So each surename should be transformed to its initial which are appended to the lastname.
The example should result in
Guilcher GM, Harvey M, Hand JP
Liesner R, Collins PT, Richards M
Manco-Johnson M, Santagostino E, Ljung R
The problem is the different (possible) input format. I think my attempts are not very smart, so I'm asking for
Some hints to optimize the transformation code
How do I put those in a single function at all? I think first of all I have to test which format the string has...??
So let me explain how far I tried to solve that:
First example string
In the first example there are initials followed by a dot. The dots should be removed and the comma between the name and the initals should be removed.
firstString
.replace('.', '')
.replace(' &', ', ')
I think I do need an regex to get the comma after the name and before the initials.
Second example string
In the second example the name should be splitted by space and the last element is handled as lastname:
const elm = secondString.split(/\s+/)
const lastname = elm[elm.length - 1]
const initials = elm.map((n,i) => {
if (i !== elm.length - 1) return capitalizeFirstLetter(n)
})
return lastname + ' ' + initals.join('')
...not very elegant
Third example string
The third example has the already the correct format - only the dot at the end has to be removed. So nothing else has to be done with that input.
It wouldn't be possible without calling multiple replace() methods. The steps in provided solution is as following:
Remove all dots in abbreviated names
Substitute lastname with firstname
Replace lastnames with their beginning letter
Remove unwanted characters
Demo:
var s = `Guilcher, G.M., Harvey, M. & Hand, J.P.
Ri Liesner, Peter Tom Collins, Michael Richards
Manco-Johnson M, Santagostino E, Ljung R.`
// Remove all dots in abbreviated names
var b = s.replace(/\b([A-Z])\./g, '$1')
// Substitute first names and lastnames
.replace(/([A-Z][\w-]+(?: +[A-Z][\w-]+)*) +([A-Z][\w-]+)\b/g, ($0, $1, $2) => {
// Replace full lastnames with their first letter
return $2 + " " + $1.replace(/\b([A-Z])\w+ */g, '$1');
})
// Remove unwanted preceding / following commas and ampersands
.replace(/(,) +([A-Z]+)\b *[,&]?/g, ' $2$1');
console.log(b);
Given your example data i would try to make guesses based on name part count = 2, since it is very hard to rely on any ,, & or \n - which means treat them all as ,.
Try this against your data and let me know of any use-cases where this fails because i am highly confident that this script will fail at some point with more data :)
let testString = "Guilcher, G.M., Harvey, M. & Hand, J.P.\nRi Liesner, Peter Tom Collins, Michael Richards\nManco-Johnson M, Santagostino E, Ljung R.";
const inputToArray = i => i
.replace(/\./g, "")
.replace(/[\n&]/g, ",")
.replace(/ ?, ?/g, ",")
.split(',');
const reducer = function(accumulator, value, index, array) {
let pos = accumulator.length - 1;
let names = value.split(' ');
if(names.length > 1) {
accumulator.push(names);
} else {
if(accumulator[pos].length > 1) accumulator[++pos] = [];
accumulator[pos].push(value);
}
return accumulator.filter(n => n.length > 0);
};
console.log(inputToArray(testString).reduce(reducer, [[]]));
Here's my approach. I tried to keep it short but complexity was surprisingly high to get the edge cases.
First I'm formatting the input, to replace & for ,, and removing ..
Then, I'm splitting the input by \n, then , and finally (spaces).
Next I'm processing the chunks. On each new segment (delimited by ,), I process the previous segment. I do this because I need to be sure that the current segment isn't an initial. If that's the case, I do my best to skip that inital-only segment and process the previous one. The previous one will have the correct initial and surname, as I have all the information I neeed.
I get the initial on the segment if there's one. This will be used on the start of the next segment to process the current one.
After finishing each line, I process again the last segment, as it wont be called otherwise.
I understand the complexity is high without using regexp, and probably would have been better to use a state machine to parse the input instead.
const isInitial = s => [...s].every(c => c === c.toUpperCase());
const generateInitial = arr => arr.reduce((a, c, i) => a + (i < arr.length - 1 ? c[0].toUpperCase() : ''), '');
const formatSegment = (words, initial) => {
if (!initial) {
initial = generateInitial(words);
}
const surname = words[words.length - 1];
return {initial, surname};
}
const doDisplay = x => x.map(x => x.surname + ' ' + x.initial).join(', ');
const doProcess = _ => {
const formatted = input.value.replace(/\./g, '').replace(/&/g, ',');
const chunks = formatted.split('\n').map(x => x.split(',').map(x => x.trim().split(' ')));
const peoples = [];
chunks.forEach(line => {
let lastSegment = null;
let lastInitial = null;
let lastInitialOnly = false;
line.forEach(segment => {
if (lastSegment) {
// if segment only contains an initial, it's the initial corresponding
// to the previous segment
const initialOnly = segment.length === 1 && isInitial(segment[0]);
if (initialOnly) {
lastInitial = segment[0];
}
// avoid processing last segments that were only initials
// this prevents adding a segment twice
if (!lastInitialOnly) {
// if segment isn't an initial, we need to generate an initial
// for the previous segment, if it doesn't already have one
const people = formatSegment(lastSegment, lastInitial);
peoples.push(people);
}
lastInitialOnly = initialOnly;
// Skip initial only segments
if (initialOnly) {
return;
}
}
lastInitial = null;
// Remove the initial from the words
// to avoid getting the initial calculated for the initial
segment = segment.filter(word => {
if (isInitial(word)) {
lastInitial = word;
return false;
}
return true;
});
lastSegment = segment;
});
// Process last segment
if (!lastInitialOnly) {
const people = formatSegment(lastSegment, lastInitial);
peoples.push(people);
}
});
return peoples;
}
process.addEventListener('click', _ => {
const peoples = doProcess();
const display = doDisplay(peoples);
output.value = display;
});
.row {
display: flex;
}
.row > * {
flex: 1 0;
}
<div class="row">
<h3>Input</h3>
<h3>Output</h3>
</div>
<div class="row">
<textarea id="input" rows="10">Guilcher, G.M., Harvey, M. & Hand, J.P.
Ri Liesner, Peter Tom Collins, Michael Richards
Manco-Johnson M, Santagostino E, Ljung R.
Jordan M, Michael Jackson & Willis B.</textarea>
<textarea id="output" rows="10"></textarea>
</div>
<button id="process" style="display: block;">Process</button>
In Python, there was good old firstline, rest = text.split("\n", 1). After some painful discovery, I realized that JavaScript gives a different meaning to the limit property, and returns that many "splits" (1 means it returns only the first line, 2 returns only the first two lines, and so forth).
What's the best way to get what I wanted? Do I have to make do with slice and indexOf?
Probably the most efficient way:
function getFirstLine(text) {
var index = text.indexOf("\n");
if (index === -1) index = undefined;
return text.substring(0, index);
}
Then:
// "Some string goes here"
console.log(getFirstLine("Some string goes here\nSome more string\nAnd more\n\nMore"));
// "asdfasdfasdf"
console.log(getFirstLine("asdfasdfasdf"));
Edit:
function newSplit(text, lineSplit) {
if (lineSplit <= 0) return null;
var index = -1;
for (var i = 0; i < lineSplit; i++) {
index = text.indexOf("\n", index) + 1;
if (index === 0) return null;
}
return { 0: text.substring(0, index - 1), 1: text.substring(index) }
}
Output:
newSplit("someline\nasdfasdf\ntest", 1);
> Object {0: "someline", 1: "asdfasdf↵test"}
newSplit("someline\nasdfasdf\ntest", 2);
> Object {0: "someline↵asdfasdf", 1: "test"}
newSplit("someline\nasdfasdf\ntest", 0);
> null
newSplit("someline\nasdfasdf\ntest", 3);
> null
You can use shift to remove the first item from an array.
var lines = text.split("\n"); // split all lines into array
var firstline = lines.shift(); // read and remove first line
var rest = lines.join("\n"); // re-join the remaining lines
This is perhaps idomatically closest to what you do in Python, but it's hardly the most efficient approach.
Another possibility, using a regex with String.match and Array.slice
Javascript
var text = "Simple Simon met a Pieman,\ngoing to a fair.\nSaid simple Simon to the Pieman,\n\n\"Let me taste your ware.\"";
console.log((text.match(/^([\s\S]*?)\n([\s\S]*)$/) || []).slice(1, 3));
Output
["Simple Simon met a Pieman,", "going to a fair.↵Said simple Simon to the Pieman,↵↵"Let me taste your ware.""]
On jsfiddle
The RegExp.exec() method will do the trick:
//some multiline text
var text='line1\nline2'
//exec puts the first line in an array
var firstline=/.*/.exec(text)[0]
REPL Output:
>>> firstline
line1
When the regex is saved as an object with global and multiline flags, exec can be used to step through each line in a loop, manually shown here in the jsc REPL:
>>> var reg = /^.*/gm
>>> var text='line1\nline2'
>>> reg.exec(text)
line1
>>> reg.exec(text)
line2
>>> reg.exec(text)
null
An example of what im trying to get:
String1 - 'string.co.uk' - would return 'string' and 'co.uk'
String2 - 'random.words.string.co.uk' - would return 'string` and 'co.uk'
I currently have this:
var split= [];
var tld_part = domain_name.split(".");
var sld_parts = domain_name.split(".")[0];
tld_part = tld_part.slice(1, tld_part.length);
split.push(sld_parts);
split.push(tld_part.join("."));
With my current code, it takes the split parameter from the beginning, i want to reverse it if possible. With my current code it does this:
String1 - 'string.co.uk' - returns 'string' and 'co.uk'
String2 - 'random.words.string.co.uk' - would return 'random` and 'words.string.co.uk'
Any suggestions?
To expand upon elclanrs comment:
function getParts(str) {
var temp = str.split('.').slice(-3) // grabs the last 3 elements
return {
tld_parts : [temp[1],temp[2]].join("."),
sld_parts : temp[0]
}
}
getParts("foo.bar.baz.co.uk") would return { tld_parts : "co.uk", sld_parts : "baz" }
and
getParts("i.got.99.terms.but.a.bit.aint.one.co.uk") would return { tld_parts : "co.uk", sld_parts : "one" }
try this
var str='string.co.uk'//or 'random.words.string.co.uk'
var part = str.split('.');
var result = part[part.length - 1].toString() + '.' + part[part.length - 1].toString();
alert(result);
One way that comes to mind is the following
var tld_part = domain_name.split(".");
var name = tld_part[tld_part.length - 2];
var tld = tld_part[tld_part.length - 1] +"."+ tld_part[tld_part.length];
Depending on your use case, peforming direct splits might not be a good idea — for example, how would the above code handle .com or even just localhost? In this respect I would go down the RegExp route:
function stripSubdomains( str ){
var regs; return (regs = /([^.]+)(\.co)?(\.[^.]+)$/i.exec( str ))
? regs[1] + (regs[2]||'') + regs[3]
: str
;
};
Before the Regular Expression Police attack reprimand me for not being specific enough, a disclaimer:
The above can be tightened as a check against domain names by rather than checking for ^., to check for the specific characters allowed in a domain at that point. However, my own personal perspective on matters like these is to be more open at the point of capture, and be tougher from a filtering point at a later date... This allows you to keep an eye on what people might be trying, because you can never be 100% certain your validation isn't blocking valid requests — unless you have an army of user testers at your disposal. At the end of the day, it all depends on where this code is being used, so the above is an illustrated example only.
I've done this in JavaScript but needless to say I can't just swap it over.
In Jscript I used this:
var estr = tx_val
index = 0
positions = []
while((index = estr.indexOf("e", index + 1)) != -1)
{
positions.push(index);
}
document.getElementById('ans6').innerHTML = "Locations of 'e' in string the are: "
+ positions;
I tried using the same logic with VBS terms, ie join, I also tried using InStr. I'm just not sure how to yank out that 'e'... Maybe I'll try replacing it with another character.
Here is what I tried with VBScript. I tried using InStr and replace to yank out the first occurance of 'e' in each loop and replace it with an 'x'. I thought that maybe this would make the next loop through give the location of the next 'e'. -- When I don't get a subscript out of range 'i' error, I only get one location back from the script and its 0.
(6) show the location of each occurence of the character "e" in the string "tx_val" in the span block with id="ans6"
countArr = array()
countArr = split(tx_val)
estr = tx_val
outhtml = ""
positions = array()
i=0
for each word in countArr
i= i+1
positions(i) = InStr(1,estr,"e",1)
estr = replace(estr,"e","x",1,1)
next
document.getElementById("ans6").innerHTML = "E is located at: " & positions
What can I do that is simpler than this and works? and thank you in advance, you all help a lot.
EDIT AGAIN: I finally got it working right. I'm not 100% how. But I ran through the logic in my head a few dozen times before I wrote it and after a few kinks it works.
local = ""
simon = tx_val
place=(InStr(1,simon,"e"))
i=(len(simon))
count = tx_val
do
local = (local & " " & (InStr((place),simon,"e")))
place = InStr((place+1),simon,"e")
count = (InStr(1,simon,"e"))
loop while place <> 0
document.getElementById("ans6").innerHTML= local
InStr has slightly different parameters to indexOf:
InStr([start, ]string, searchValue[, compare])
start: The index at which to start searching
string: The string to search
searchValue: The string to search for
Also note that Visual Basic indexes strings beginning at 1 so all the input and return index values are 1 more than the original JavaScript.
You can try split(). For example a simple string like this:
string = "thisismystring"
Split on "s", so we have
mystring = Split(string,"s")
So in the array mystring, we have
thi i my tring
^ ^ ^ ^
[0] [1] [2] [3]
All you have to do is check the length of each array item using Len(). For example, item 0 has length of 3 (thi), so the "s" is at position 4 (which is index 3). Take note of this length, and do for the next item. Item 1 has length of 1, so we add it to 4, to get 5, and so on.
#Update, here's an example using vbscript
thestring = "thisismystring"
delimiter="str"
mystring = Split(thestring,delimiter)
c=0
For i=0 To UBound(mystring)-1
c = c + Len(mystring(i)) + Len(delimiter)
WScript.Echo "index of s: " & c - Len(delimiter)
Next
Trial:
C:\test> cscript //nologo test.vbs
index of str is: 8