How do I split multiline cells into arrays for Google Sheets? - javascript

I keep a spreadsheet of the titles and issue numbers I've read for various comics. I want to have a column that provides the count of the issues read for each title. Some titles have issue numbers in multiple lines. For example, the run of Avengers starting in 2016 has the following issue numbers listed in one cell, each range on a new line within the cell:
#1-11
#1.1-5.1
#1MU
#672-676
I tried to write a script that would separate each line into an array item, and then for each item in the array, extract the numbers using regular expressions to perform calculations to determine the total count. (The count of the above issues is 22, but the problem is getting the script to determine that for me and for the other titles as I update issue numbers.)
Here's what I have so far:
function calcIssueCount(x) {
// Set initial value to 0:
var issueCount = 0;
// Make an array from the lines of the cell
// split by the line break:
var box = x.split("\n");
for (var line in box) {
// Determine if the line includes a
// range of issue numbers, indicated
// by presence of a hyphen:
if ("-" in line === True) {
// Remove the # symbol from the string:
line = line.replace("#","");
// Extract the number before the hyphen
// and delete the hyphen:
var a = line(/[0-9}+\.|[0-9]+-/);
a = a.replace("-","");
// Extract the number after the hyphen
// and delete the hyphen:
var b = line(/-[0-9}+\.|[0-9]+/);
b = b.replace("-","");
// Turn a and b into numbers:
a = number(a)
b = number(b)
// Subtract the range start from the
// range end:
var c = b - a;
// Add 1 because a range of 1-5 includes 5
// issues, while 5-1 is only 4:
c += 1;
// Update the count for the number of
// issues in the cell by adding the
// line's value:
issueCount += c;
}
else {
// If there's no hyphen, there's no
// range; the count of issues on the
// line is 1:
issueCount += 1;
}
}
}
calcIssueCount(x) would have x as the cell name (e.g., D15).
I'm likely making mistakes all over the place, but I especially think I'm not understanding something about getting the cell data into the array into the first place. What am I missing here?

You want to retrieve "22" from the following value in a cell.
#1-11
#1.1-5.1
#1MU
#672-676
As the logic for retrieving "22" from the value, I understood as follows.
Retrieve 1 and 11 from #1-11.
Retrieve 1.1 and 5.1 from #1.1-5.1.
Retrieve 672 and 676 from #672-676.
Subtract before number from after number for each line.
Add 1 to all lines.
For the line without -, add only 1.
From above logic, (11 - 1 + 1) + (5.1 - 1.1 + 1) + (1) + (676 - 672 + 1) = 22 can be obtained. If my understanding of your logic is correct, how about the modification like below?
Modification points :
When for (var line in box) { is used, each element can be retrieved by box[line].
In this modicication, I used forEach.
In Javascript, the boolean express "true" and "false".
Line is not an object. So "-" in line occurs an error.
In this modification, I used indexOf().
In Javascript, number() is Number().
When you want to use calcIssueCount(x) as a custom function, in your current script, no values are returned. So if you want to retrieve issueCount, please add return issueCount.
I couldn't understand about the function of line(/[0-9}+\.|[0-9]+-/). I'm sorry for this.
In my modification, I retrieved both the before and after numbers using a regex of ([0-9.]+)-([0-9.]+). I think that there are several solutions for your situation. So please think of this as one of them.
Modified script :
Pattern 1 :
In this pattern, your script was modified.
function calcIssueCount(x) {
// Set initial value to 0:
var issueCount = 0;
// Make an array from the lines of the cell
// split by the line break:
var box = x.split("\n");
box.forEach(function(e){
if (e.indexOf("-") > -1) {
var numbers = e.match(/([0-9.]+)-([0-9.]+)/);
// Extract the number before the hyphen
// and delete the hyphen:
var a = Number(numbers[1]);
// Extract the number after the hyphen
// and delete the hyphen:
var b = Number(numbers[2]);
// Subtract the range start from the
// range end:
var c = b - a;
// Add 1 because a range of 1-5 includes 5
// issues, while 5-1 is only 4:
c += 1;
// Update the count for the number of
// issues in the cell by adding the
// line's value:
issueCount += c;
} else {
// If there's no hyphen, there's no
// range; the count of issues on the
// line is 1:
issueCount += 1;
}
});
return issueCount;
}
Pattern 2 :
This is other sample script. In this pattern, the regex is not used. The result is the same with pattern 1.
function calcIssueCount(x) {
return x.split("\n").reduce(function(c, e) {
if (e.indexOf("-") > -1) {
var numbers = e.slice(1).split("-");
c += (Number(numbers[1]) - Number(numbers[0])) + 1;
} else {
c += 1;
}
return c;
}, 0);
}
Result :
Note :
In this modified script, the regex of ([0-9.]+)-([0-9.]+) is used for the sample value. If you want to use values with other patterns, please tell me. At that time, can you show me more samples?
References :
Custom Functions in Google Sheets
Array.prototype.forEach()
String.prototype.indexOf()
Number
String.prototype.match()
If I misunderstand your question, I'm sorry.

Related

Getting the first ( i.e. lowest) value from a 'range' of value e.g. £25 - £30

So I have here some JS code for getting either a singular value or, if there's a 'range' of prices separated by ' - ', getting the highest value:
function getPrice() {
var str = ("£25 - £36");
var val;
if (str.indexOf('-') > -1) {
val = Number(str.replace(/[^0-9.-]+/g, "").split('-')[1], 10);
} else {
val = Number(str.replace(/[^0-9.-]+/g, "").split('-')[0], 10);
}
return val;
}
How would I modify this so that it gets the lowest value instead?
This 'range' will always be either in the format of £25 or £13 - £72 and it will always be in pounds. The highest value will always be the last; in this context they are functionally the same.
One way to achieve what you require would be to use a Regex to pull out all the figures in the string. You can then apply those matches to Math.min to find the lowest one.
The benefit of this approach is that it will retrieve the lowest number regardless of its position in the string.
function getLowestPrice(input) {
let amounts = input.match(/\d{1,3}(,\d{3})*(\.\d+)?/g);
amounts = amounts.map(v => v.replace(',', '')); // remove comma grouping
return Math.min.apply(this, amounts);
}
console.log(getLowestPrice('£25 - £36')); // = 25
console.log(getLowestPrice('£1,090.99 - £77.25')); // = 77.25
Edit - I just expanded the Regex to handle values containing thousands grouping and decimals, credit to this answer for that.

Hacker Rank Annagrams

I am trying to solve the problem described here with JavaScript...
https://www.hackerrank.com/challenges/ctci-making-anagrams
I have to output the number of letters that would need to be removed from two strings in order for there to only be matching letters (compare the two strings for matching letters and total the letters that don't match)
for example...
string a = cbe
string b = abc
the only matching letter is between both strings is the two c's so I would be removing 4 letters (beab).
My code works, but it seems to keep timing out. If I download the individual test case instances, I seem to fail when variables a and b are set to large strings. If I test these individually, I seem to get the right output but i still also get the message "Terminated due to timeout".
I'm thinking it might be obvious to someone why my code timesout. I'm not sure it's the most elegant way of solving the problem but I'd love to get it working. Any help would be much appreciated...
function main() {
var a = readLine();
var b = readLine();
var arraya = a.split('');
var arrayb = b.split('');
var arraylengths = arraya.length + arrayb.length;
//console.log(arraylengths);
if (arraya.length <= arrayb.length) {
var shortestarray = arraya;
var longestarray = arrayb;
} else {
var shortestarray = arrayb;
var longestarray = arraya;
}
var subtract = 0;
for (x = 0; x < shortestarray.length; x++) {
var theletter = shortestarray[x];
var thenumber = x;
if (longestarray.indexOf(theletter, 0) > -1) {
var index = longestarray.indexOf(theletter, 0);
longestarray.splice(index, 1);
subtract = subtract + 2;
}
}
var total = arraylengths - subtract;
console.log(total);
}
Your algorithm is good. It's straight forward and easy to understand.
There are certain things you can do to improve the performance of your code.
You don't have to calculate the indexOf operation twice. you can reduce it to one.
the splice operation is the costliest operation because the JS engine has to delete the element from an array and reassign the indexes of all the elements.
A point to be noted here is that the JS engine does an extra step of correcting the index of the array, which is not required for your purpose. So you can safely remove longestarray.splice(index, 1); and replace it with delete longestarray[index]
Here is a snippet which will increase your performance of the code without changing your logic
for (var x = 0; x < shortestarray.length; x++) {
var theletter = shortestarray[x];
var thenumber = longestarray.indexOf(theletter, 0); // <-- check only once
if (thenumber > -1) {
var index = thenumber;
delete longestarray[index]; // <-- less costlier than splice
subtract = subtract + 2;
}
}
Note: I am not suggesting you to use delete for all the cases. It's useful here because you are not going to do much with the array elements after the element is deleted.
All the best. Happy Coding
I would suggest you hashing. make the characters of string key and its numbers of occurrences value. Do the same for both strings. After that take string 1 and match the count of its every character with the count of same character in string then calculate the difference in the number of occurrences of the same character and delete that character till the difference becomes 0 and count that how many times you performed delete operation.
ALGORITHM:
step 1: Let arr1[255]= an integer array for storing the count of string1[i]
and initialized to zero
ex: string1[i]='a', then arr1[97]=1, because ASCII value of a is 97
and its count is 1. so we made hash table for arr1 where key is
ASCII value of character and value is its no of occurrences.
step 2: Now declare an another array of same type and same size for string 2
step 3: For i=0 to length(string1):
do arr1[string1[i]]++;
step 4: For i=0 to length(string2):
do arr2[string2[i]]++;
step 5: Declare an boolean char_status[255] array to check if the
character is visited or not during traversing and initialize it to
false
step 6: set count=0;
step 7: For i=0 to length(string1):
if(char_status[string1[i]]==false):
count=count+abs(arr1[string1[i]]-arr2[string1[i]])
char_status[string1[i]]=true
step 8: For i=0 to length(string2):
if(char_status[string2[i]]==false):
count=count+abs(arr1[string2[i]]-arr2[string2[i]])
char_status[string2[i]]=true
step 9: print count
I have applied this algo just now and passed all test cases. You may improve this algo more if you have time.

How do I grab user input after an # symbol and before a space?

I want to grab the user input from an input tag including everything after the # symbol and up to a space if the space exists. For example:
If the user input is "hello#yourname"
I want to grab "yourname"
If the user input is "hello#yourname hisname"
I want to grab "yourname" because it is after the # symbol and ends at the space.
I have some code written that attempts to grab the user input based on these rules, but there is a bug present that I can't figure out how to fix. Right now if I type "hello#yourname hisname"
My code will return "yourname hisn"
I don't know why the space and four characters "hisn" are being returned. Please help me figure out where the bug is.
Here is my function which performs the user input extraction.
handleSearch(event) {
let rawName, nameToSearch;
rawName = event.target.value.toLowerCase();
if (rawName.indexOf('#') >= 0 && rawName.indexOf(' ') >= 0) {
nameToSearch = rawName.substr(rawName.indexOf('#') + 1, rawName.indexOf(' ') - 1);
} else if (rawName.indexOf('#') >= 0 && rawName.indexOf(' ') < 0) {
nameToSearch = rawName.substr(rawName.indexOf('#') + 1);
} else {
nameToSearch = '';
}
return nameToSearch;
}
Working example:
handleSearch(event) {
let rawName = event.target.value.toLowerCase();
if (rawName.indexOf("#") === -1) {
return '';
}
return (rawName.split("#")[1].split(" "))[0];
}
You have to handle a lack of "#", but you don't need to handle the case where there is a space or not after the "#". The split function will still behave correctly in either of those scenarios.
Edit: The specific reason why OP's code doesn't work is because the substr method's second argument is not the end index, but the number of characters to return after the start index. You can use the similar SUBSTRING method instead of SUBSTR to make this easier. Change the line after the first if statement as follows:
nameToSearch = rawName.substring(rawName.indexOf('#') + 1, rawName.indexOf(' '));
const testCases = [
"hello#yourname",
"hello#yourname hisname"
];
for (let test of testCases) {
let re = /#(.*?)(?:\s|$)/g;
let result = re.exec(test);
console.log(result[1]);
}
Use regex instead if you know how the string will be created.
You could do something like this--
var string = "me#somename yourname";
var parts = string.split("#");
var parts2 = parts[1];
var yourPart = parts2.split(" ");
console.log(yourPart[0]);
NOTE:
I am suggesting it just because you know your string structure.
Suggestion
For your Piece of code I think you have some white space after hisn that is why it is returning this output. Try to replace all the white spaces with some character see if you are getting any white space after hisn.
I'm not sure of the language your code is in (there are several it 'could be', probably Javascript), but in most languages (including Javascript) a substring function 'starts at' the position of the first parameter, and then 'ends at' that position plus the second parameter. So when your second parameter is 'the position of the first space - 1', you can substitute 'the position of the first space - 1' with the number 13. Thus, you're saying 'get a substring by starting one after the position of the first # character i.e. position 6 in a zero-based system. Then return me the next 13 characters.'
In other words, you seem to be trying to say 'give me the characters between position 6 and position 12 (inclusive)', but you're really saying 'give me the characters between position 6 and position 18 (inclusive)'.
This is
y o u r n a m e h i s n
1 2 3 4 5 6 7 8 9 10 11 12 13
(For some reason I can't get my spaces and newlines to get preserved in this answer; but if you count the letters in 'yourname hisn' it should make sense :) )
This is why you could use Neophyte's code so long as you can presume what the string would be. To expand on Neophyte's answer, here's the code I would use (in the true branch of the conditional - you could also probably rename the variables based on this logic, etc.):
nameToSearch = rawName.substr(rawName.indexOf('#') + 1;
var nameFromNameToSearch = nameToSearch.substr(nameToSearch.indexof(' ') - 1;
nameFromNameToSearch would contain the string you're looking for. I haven't completely tested this code, but I hope it 'conceptually' gives you the answer you're looking for. Also, 'conceptually', it should work whether there are more than one '#' sign, etc.
P.S. In that first 'rawName.substr' I'm not giving a second parameter, which in Javascript et al. effectively says 'start at the first position and give me every character up to the end of the string'.

How do I look up the particular bits or digits of a hex value in HTML5 / javascript?

I want to encode many things in hex. Here are examples.
var LAST_DIGITS = 0x000000A7; // Last 2 digits represent something
var MID_DIGITS = 0x00000E00; // 5th and 6th digits represent something else
Let's say I added LAST_DIGITS and MID_DIGITS together. That's 0x00000EA7 and represents the two different things I want to encode.
Is there some way I can just check a subset of that independently, in javascript/HTML5? Or do I have to turn it into a string or other collection and then reference indices explicitly?
In the above example, here's what I'm looking for
function getThemDigits (inHexValue)
{
// Check for 5th and 6th digits through my dream (not real!) function
inHexValue.fakeGetHexValueFunction(4,5); // returns 0E
// Check for last two digits for something else
inHexValue.fakeGetHexValueFunction(6,7); // returns A7
}
The common Bit-Operators (| & >> << etc.) are also available in JavaScript.
Lets assume that you always want two hexadecimal digits from the hexadecimal representation of that integer. And lets count the index of those digits from the right rather than from the left:
function GetHex(hex, offset) {
// Move the two hex-digits to the very right (1 hex = 4 bit)
var aligned = hex >> (4 * offset);
// Strip away the stuff that might still be to the left of the
// targeted bits:
var stripped = aligned & 0xFF;
// Transform the integer to a string (in hex representation)
var result = stripped.toString(16);
// Add an extra zero to ensure that the result will always be two chars long
if (result.length < 2) {
result = "0" + result;
}
// Return as uppercase, for cosmetic reasons
return result.toUpperCase();
}
Usage:
var LAST_DIGITS = 0x000000A7;
var MID_DIGITS = 0x00000E00;
var a = GetHex(LAST_DIGITS, 0);
var b = GetHex(MID_DIGITS, 2); // offset of 2 hex-digits, looking from the right

Regex inside TableSorter parser deletes indices where the same regex works correctly in a separate console function

Using Jquery TableSorter, I am creating a custom parser to sort elapsed time <td>s that contain "'#' year(s) * '#' month(s)". When I use the function
$('.techtable td:nth-child(6)').each(function(){
// console.log($(this));
var that = $(this).text();
var myRegexp = /([\d]+) ([\w]+)(?: ([\d]+) ([\w]+))?/;
var match = myRegexp.exec($(this).text());
console.log(match);
});
from the command line, each index contains an array of length 5, looking like this:
["7 months", "7", "months", undefined, undefined]
to this:
["3 years 3 months", "3", "years", "3", "months"]
depending on whether or not the elapsed time has just a month or year element, and then the other. To parse the text, I use regex to gather each element, and then use JS to test whether there are multiple elements or not, and if 1 element only, then wheher it begins with "y" or "m", and return the number of months, so the parser can sort the <td>s by number of months in integer form.
The parser passes in each element into the function as parameter "s". when i try regex on "s" directly, it is not returning an array of length 5, it is truncating it to 3 (whether or not I am running the line that truncates it if index 3 is typeof 'undefined'). When I use the console to directly use this function:
$('.techtable td:nth-child(6)').each(function(){
// console.log($(this));
var that = $(this).text();
var myRegexp = /([\d]+) ([\w]+)(?: ([\d]+) ([\w]+))?/;
var match = myRegexp.exec($(this).text());
if (typeof match[3] == 'undefined') {match.length = 3;};
console.log(match);
});
the regex returns the arrays properly, with the array truncated if it only has 1 element (year or month). Here is the code for the custom parser:
var myRegexp = /([\d]+) ([\w]+)(?: ([\d]+) ([\w]+))?/;
var match = myRegexp.exec(s);
var order = [];
console.log(match);
if (typeof match[3] == 'undefined') {match.length = 3;};
// 1 element case:
// month
if (match.length = 3) {
if (match[2][0] == "m") {
order.push(match[1]);
}
// year
if (match[2][0] == "y") {
order.push(match[1]*12);
}
// both elements
} else {
order.push(match[1]*12 + match[3]);
}
s = order;
return s;
},
The fiddle is here. The Elapsed parser is second from the bottom of the JS panel. As you can see, since I can't get the months from the array (indices 4 and 5), I can not calculate the months, and thus the sorting only incorporates years, and the months are sorted by their original HTML placement. What am I missing? (I'm learning.... so direction is appreciated more than an fix, but I won't turn it down.)
Yes I realize the JS fiddle is loaded (first part is TableSorter, to maintain functionality for verification(click on headers to sort), but all you need to focus on is the last part of the code (reference the '//Table Sorter dateSorter' to see how a correct parser should look). The section '//Table Sorter elapsedSorter' is where my two attempts are, the first part is the working code I use in the console, and the seconde part is the parser, which is somehow deleting the last two indices in the array, thus loosing the month information to calculate.
Guess I'll have to add Regex, and a personal rating of 1, since I've wasted almost an entire day on this.
if (match.length = 3) {
You meant this?
if (match.length == 3) {
To help you further, when you write conditions with one constant and a variable, you can write them like this instead:
if (3 = match.length) {
This would now cause a JavaScript error instead of silently getting turned into an assignment that always yields true.
In JavaScript, 12 + '4' == '124', so you have to be careful with numbers and the + operator. In languages such as PHP you don't have this problem, because they have an operator for string concatenations ;-)
var myRegexp = /([\d]+) ([\w]+)(?: ([\d]+) ([\w]+))?/;
var match = myRegexp.exec(s);
var order = [];
if (typeof match[3] == 'undefined') {
if (match[2][0] == "m") {
order.push(parseInt(match[1]));
}
// year
if (match[2][0] == "y") {
order.push(parseInt(match[1])*12);
}
// both elements
} else {
order.push(parseInt(match[1])*12 + parseInt(match[3]));
}
s = order;
return s;
Btw use parseInt(x, 10) if you expect fields to have leading zeroes (which would otherwise result in 0 being returned). Thanks fudgey!

Categories