I am trying to figure out how I can apply two different conditions to one output in a switch statement. For example, I have a function to style a string depending on what arguments that 2nd parameter gets. The first and second output is okay since it only has one argument for styling but the third output does. I cannot find a way to apply both uppercase and reversed styling to a string. I've tried to loop a switch statement. I'd like to know if there are any good solutions for this.
function caseStyle(string, style) {
function toUpper(string) {
string = string.toUpperCase();
return string;
}
function toReversed(string) {
string = string.split("").reverse().join("");
return string;
}
switch (style) {
case "upper":
string = toUpper(string);
break;
case "reversed":
string = toReversed(string);
break;
}
return string;
}
console.log(caseStyle("hello", "upper")); //output: HELLO
console.log(caseStyle("hello", "reversed")); //output: olleh
console.log(caseStyle("hello", ["upper", "reversed"])); // expected output: OLLEH
you'd need to check if style is an array, and handle accordingly
Or, force it to be an Array style = [style].flat() and then iterate that array
The .flat() will flatten the array in the case when an array is passed in
as follows
function caseStyle(string, style) {
style = [style].flat();
function toUpper(string) {
string = string.toUpperCase();
return string;
}
function toReversed(string) {
string = string.split("").reverse().join("");
return string;
}
style.forEach(style => {
switch (style) {
case "upper":
string = toUpper(string);
break;
case "reversed":
string = toReversed(string);
break;
}
});
return string;
}
console.log(caseStyle("hello", "upper")); //output: HELLO
console.log(caseStyle("hello", "reversed")); //output: olleh
console.log(caseStyle("hello", ["upper", "reversed"])); // expected output: OLLEH
You can use switch (true) in the following way:
function caseStyle(string, style) {
function toUpper(string) {
string = string.toUpperCase();
return string;
}
function toReversed(string) {
string = string.split("").reverse().join("");
return string;
}
switch (true) {
case style === "upper":
string = toUpper(string);
break;
case style === "reversed":
string = toReversed(string);
break;
case style.includes("upper") && style.includes("reversed"):
string = toUpper(toReversed(string))
break;
default:
break;
}
return string;
}
console.log(caseStyle("hello", "upper"));//output: HELLO
console.log(caseStyle("hello", "reversed"));//output: olleh
console.log(caseStyle("hello", ["upper", "reversed"]));// expected output: OLLEH
Besides some to-the-point answers in here...
I'd present a different approach:
Move the inner functions toUpper and toReversed outside of your caseStyle function. There's no need to have them in there
Use Arrow Functions and implicit return instead of doing twice(!) str = str... in the method functions and again in the switch case
Group your functions into an Object called methods - and name your functions exactly like their string names-references
Instead of a switch-case, use Array.prototype.reduce()
Instead of wrapping your String/or/Array logic into an if statement, always force the second array (btw, rename it to styles, plural) to always be an Array by converting the string into Array. Even more, by using .split(/\W+/) (split by one or more non-word characters) you can now also pass a string like "upper,reversed"
Thanks to this logic you're decoupling your methods and you can add more at any point in time, an you can use in the future the expected methods (transformation) names you have available like i.e: caseStyle("hello", ["snake", "capitalize", "reverse"]) or more.
const methods = {
upper: str => str.toUpperCase(),
reversed: str => [...str].reverse().join(""),
};
const caseStyle = (str, styles) => {
if (typeof styles === "string") styles = styles.split(/\W+/);
return styles.reduce((acc, style) => methods[style](acc), str);
};
console.log(caseStyle("hello", "upper")); //output: HELLO
console.log(caseStyle("hello", "reversed")); //output: olleh
console.log(caseStyle("hello", ["upper", "reversed"])); // expected output: OLLEH
console.log(caseStyle("stack", "reversed upper")); // expected output: KCATS
As you can notice from the above, I've used the preferred string Spread syntax [...str] to convert a String to Array. One might run into problems when using .split("") on a string that uses more than one code point to represent a glyph, like i.e: Emoji:
Issue when using .split("")
console.log("ab📆🔞".split(""))
will give: ["a","b","�","�","�","�"]
Better string split: using Spread syntax:
console.log([..."ab📆🔞"])
as you can see the emojis are correctly split and preserved.
Why don't you just run a simple for-of-loop? You'll also need to check if the input isn't an array, then turn it into an array if it isn't already.
function caseStyle(string, styles) {
function toUpper(string) {
string = string.toUpperCase();
return string;
}
function toReversed(string) {
string = string.split("").reverse().join("");
return string;
}
if (!(styles instanceof Array)) styles = [styles];
for (const style of styles) {
switch (style) {
case "upper":
string = toUpper(string);
break;
case "reversed":
string = toReversed(string);
break;
}
}
return string;
}
console.log(caseStyle("hello", "upper")); //output: HELLO
console.log(caseStyle("hello", "reversed")); //output: olleh
console.log(caseStyle("hello", ["upper", "reversed"])); // expected output: OLLEH
Related
I was wondering if there is way to type the return of a function based on the input given to it. Here is an example of what I am thinking:
function toEnum(...strings: string[]) {
const enumObject = {};
strings.forEach((str) => {
enumObject[str.toUpperCase()] = str;
});
return enumObject;
}
const myEnum = toEnum('one', 'two', 'three')
Is there a way to type this function so that we know that myEnum looks like:
{
ONE: 'one',
TWO: 'two',
THREE: 'three'
}
edit:
as #dariosicily mentioned, we could type enumObject using a Record<string, string> or index signatures, but I am wondering if there is a way to know the actual keys present in the return object based on the params passed in.
There is an intrinsic Uppercase<T> string manipulation utility type which, when given a string literal type as input, produces an uppercase version as output. So Uppercase<"abc"> and "ABC" are the same type. Using this type we can create a mapped type with remapped keys to express the output type of toEnum(), given the union of the string literal types of its arguments:
function toEnum<K extends string>(...strings: K[]): { [P in K as Uppercase<P>]: P } {
const enumObject: any = {};
strings.forEach((str) => {
enumObject[str.toUpperCase()] = str;
});
return enumObject;
}
Note that toEnum() is generic in K, the union of the element types of the strings array. Note that K is constrained to string so that strings is indeed an array of strings, and because this constraint gives the compiler a hint that we want to infer string literal types for its elements instead of just string. You definitely need to use generic here, otherwise you'd just get Record<string, string> out of the function.
The type {[P in K as Uppercase<P>]: P} iterates over every string P in the original K union and remaps it to an uppercase version as the key, and then uses just the same type P as the value. That's the type you wanted.
Also note that I gave enumObject the any type so as to opt out of strict type checking inside the implementation of toEnum(); the compiler is unable to follow the logic that enumObject[str.toUpperCase()]=str will be an appropriate operation on a value of type {[P in K as Uppercase<P>]: P}, so we won't even make it try.
Anyway you can test that it does what you want:
const myEnum = toEnum('one', 'two', 'three', "fortyFive");
/* const myEnum: {
ONE: "one";
TWO: "two";
THREE: "three";
FORTYFIVE: "fortyFive";
} */
console.log(myEnum.THREE) // "three" both at compile and runtime
In the comments you mentioned that for something like fortyFive, you'd like the key to be FORTY_FIVE instead of FORTYFIVE. That is, you don't just want the key to be an uppercase version of the input. You want the input to be interpreted as lower camel case and the output to be all-upper snake case (also known as SCREAMING_SNAKE_CASE).
This is also possible in TypeScript, using template literal types to split a string literal type into characters, and recursive conditional types to operate on these characters programmatically.
First let's do it at the type level:
type LowerPascalToUpperSnake<T extends string, A extends string = ""> =
T extends `${infer F}${infer R}` ? LowerPascalToUpperSnake<R,
`${A}${F extends Lowercase<F> ? "" : "_"}${Uppercase<F>}`
> : A;
Note that it is useful to have a function that does the same thing at the value level:
function lowerPascalToUpperSnake<T extends string>(str: T) {
return str.split("").map(
c => (c === c.toLowerCase() ? "" : "_") + c.toUpperCase()
).join("") as LowerPascalToUpperSnake<T>
}
Both the type and the function behave similarly; the idea is to iterate over each character of the string, insert an underscore if and only if the current character is not lowercase, and then insert an uppercase version of the current character.
You can verify that this works:
const test = lowerPascalToUpperSnake("abcDefGhiJklmNop");
// const test: "ABC_DEF_GHI_JKLM_NOP"
console.log(test); // "ABC_DEF_GHI_JKLM_NOP"
The value at runtime and the type computed by the compiler agree.
And now we can use the "lower-Pascal-to-upper-snake" operation in toEnum() instead of the original uppercase operation:
function toEnum<K extends string>(...strings: K[]):
{ [P in K as LowerPascalToUpperSnake<P>]: P } {
const enumObject: any = {};
strings.forEach((str) => {
enumObject[lowerPascalToUpperSnake(str)] = str;
});
return enumObject;
}
And see it in action:
const myEnum = toEnum('one', 'two', 'three', "fortyFive");
/* const myEnum: {
ONE: "one";
TWO: "two";
THREE: "three";
FORTY_FIVE: "fortyFive";
} */
console.log(myEnum.FORTY_FIVE) // "fortyFive"
Looks good!
Playground link to code
The problem is due to the fact that while in the javascript language the line enumObject[str.toUpperCase()] = str; assignment is legitimate with typescript the same line causes an error because you have not explicitely declared the index signature of the enumObject or explicitely declared it as any.
In this cases one way to solve the issue is use the builtin Record utility type applying it to your enumObject like below:
function toEnum(...strings: string[]) {
const enumObject: Record<string, string> = {};
strings.forEach((str) => {
enumObject[str.toUpperCase()] = str;
});
return enumObject;
}
const myEnum = toEnum('one', 'two', 'three')
//it will print { ONE: 'one', TWO: 'two', THREE: 'three' }
console.log(myEnum);
Edit: answering to the question
there is a way to know the actual keys present in the return object
based on the params passed in
You can use the Object.keys method and return the keys as an Array<string>:
const myEnum = toEnum('one', 'two', 'three')
//it will print [ONE, TWO, THREE]
const keys = (Object.keys(myEnum) as Array<string>);
If you want to create a new type from keys you can use typeof:
const keys = (Object.keys(myEnum) as Array<string>);
//type KeysType = 'ONE' | 'TWO' | 'THREE'
type KeysType = typeof keys[number];
function identity<String>(arg: String): String {
return arg;
}
let myIdentity = identity([2]);
console.log()
Hi could someone help me to understand why this doesn't throw any type error even if i am passing a array of numbers ?
Is it because the type is "String" instead of "string" , which looks for objects than primitive ?
If answer is "yes" , if i change everything to string i get error saying string is never used
function identity<string>(arg: string): string {
return arg;
}
let myIdentity = identity([2]);
console.log(myIdentity )
'string' is declared but its value is never read.
You are shadowing the builtin types String and string by using their names as your generic parameter. Just use a parameter name that is not a built-in to solve it:
TS Playground link
function identity<T extends string>(value: T): T {
return value;
}
identity([2]); // error [2] doesn't extend string
identity('2'); // ok
If you want a generic identity function without any kind of constraint, you can use an unconstrained type parameter:
TS Playground link
function identity<T>(value: T): T {
return value;
}
const numberArrayValue = identity([2]); // number[]
const objectValue = identity({b: 2}); // { b: number; }
const stringLiteralValue = identity('2'); // "2"
const numberLiteralValue = identity(2); // 2
This is a double question because I can just post once every 90 minutes.
First I have to write a function that replaces a character of a string.
//====================== EXAMPLE ========================
var str = "I,Really,Like,Pizza";
characterRemover(str, ",");
"I Really Like Pizza"; // <====== EXPECTED OUTPUT
//=========================================================
And puts a space in place of the chosen character. I tried this but is not working.
function chracterRemover(str, cha){
var replaced = str.split('cha').join(' ');
return replaced;
}
It returns just the same string.
And the second thing is that I have to write a function that returns true if the data type introduced is an arrat and false for the rest.
//====================== EXAMPLE ========================
var one = { name: "antonello" };
false; // <====== EXPECTED OUTPUT
var two = ["name", "antonello"];
true; // <====== EXPECTED OUTPUT
var three = [[], [], {}, "antonello", 3, function() {}];
true; // <====== EXPECTED OUTPUT
//=========================================================
I've tried this.
function isArrayFun(array){
if {
typeof array = 'array';
return "Array";
} else {
return "Not an array"
}
}
But as well, it doesnt work.
I get this error:
Uncaught SyntaxError: Unexpected token '{'
I don't know why.
Thanks in advance for the help.
// First One
const str = "I,Really,Like,Pizza";
console.log(str.split(',').join(' '));
// Second One
function isArrayFun(array){
return Array.isArray(array);
}
const one = { name: "antonello" };
console.log(isArrayFun(one));
const two = ["name", "antonello"];
console.log(isArrayFun(two));
const three = [[], [], {}, "antonello", 3, function() {}];
console.log(isArrayFun(three));
Problem 1:
You quoted cha so it's a literal string, not the variable. Use
function characterRemover(str, cha) {
var replaced = str.split(cha).join(' ');
return replaced;
}
var str = "I,Really,Like,Pizza";
console.log(characterRemover(str, ","));
Problem 2:
typeof returns object for arrays. The way to tell if something is an array is by calling Array.isArray().
You also have syntax errors in your if statement. The condition has to be inside (), not after {.
function isArrayFun(array) {
if (Array.isArray(array)) {
return "Array";
} else {
return "Not an array"
}
}
var one = { name: "antonello" };
console.log(isArrayFun(one));
var two = ["name", "antonello"];
console.log(isArrayFun(two));
var three = [[], [], {}, "antonello", 3, function() {}];
console.log(isArrayFun(three));
First question.
The function name is different than the function you called
you should use .split(cha) and not 'cha'. split cha will actually split your string by the string you passed into that parameter. And 'cha' looks for the string 'cha'
Working example:
var str = "I,Really,Like,Pizza";
function chracterRemover(str, cha){
var replaced = str.split(cha).join(' ');
return replaced;
}
console.log(chracterRemover(str, ","));
You could also use a simple RegExp instead of using split and join and take the function to another level by making it globally useful via a 3rd parameter, in which you could define the replacement:
var str = "I,Really,Like,Pizza";
function chracterRemover(str, cha, chaTo){
var reg = new RegExp(cha, "g");
return str.replace(reg, chaTo);
}
console.log(chracterRemover(str, ",", " "));
console.log(chracterRemover(str, ",", "."));
Second question:
There is already a function like that
Array.isArray(value)
you can pass any type of data into that value, if it is an array it returns true
working example:
let type1 = [1,5,6];
let type2 = {a: 47};
let type3 = 5;
let type4 = "hello";
console.log(Array.isArray(type1))
console.log(Array.isArray(type2))
console.log(Array.isArray(type3))
console.log(Array.isArray(type4))
if objects are mutable by default why in this case it dosen't work?
How to make mutation value of the key "a" in the object "s"?
var s = {
a: "my string"
};
s.a[0] = "9"; // mutation
console.log(s.a); // doesn't work
You are trying to change a primitive String, which is immutable in Javascript.
For exmaple, something like below:
var myObject = new String('my value');
var myPrimitive = 'my value';
function myFunc(x) {
x.mutation = 'my other value';
}
myFunc(myObject);
myFunc(myPrimitive);
console.log('myObject.mutation:', myObject.mutation);
console.log('myPrimitive.mutation:', myPrimitive.mutation);
Should output:
myObject.mutation: my other value
myPrimitive.mutation: undefined
But you can define a function in primitive String's prototype, like:
String.prototype.replaceAt=function(index, replacement) {
return this.substr(0, index) + replacement+ this.substr(index + replacement.length);
}
var hello="Hello World"
hello = hello.replaceAt(2, "!!")) //should display He!!o World
Or you can just assign another value to s.a, as s.a = 'Hello World'
Strings in JavaScript are immutable. This means that you cannot modify an existing string, you can only create a new string.
var test = "first string";
test = "new string"; // same variable now refers to a new string
You try to mutate a string which not possible, because strings are immutable. You need an assignment of the new value.
Below a fancy style to change a letter at a given position.
var s = { a: "my string" };
s.a = Object.assign(s.a.split(''), { 0: "9" }).join('');
console.log(s.a);
You are trying to mutate the string using element accessor, which is not possible. If you apply a 'use strict'; to your script, you'll see that it errors out:
'use strict';
var s = {
a: "my string"
};
s.a[0] = '9'; // mutation
console.log( s.a ); // doesn't work
If you want to replace the character of the string, you'll have to use another mechanism. If you want to see that Objects are mutable, simply do s.a = '9' instead and you'll see the value of a has been changed.
'use strict';
var s = {
a: "my string"
};
s.a = s.a.replace(/./,'9')
console.log(s.a);
I am learning ES 6 online and I have test:
Implement the findLargestString function, which has a single argument
strings (an array of strings), which will set the largestString
variable defined at the top to the string which has the longest length
of the array of strings passed in as the strings parameter.
On start I have method:
let largestString;
function findLargestString(strings) {
// set largestString to point to the
// longest string found in the strings array passed in
}
So I am tried:
let largestString;
function findLargestString(strings) {
let largestString = '';
strings.forEach((string) => {
if (string.length > largestString.length) {
largestString = string;
}
});
return largestString;
}
But this return error:
largestString should be set to the largest string passed in
Expected undefined to equal 'computers'.
How can I make it?
Your code seems to be working fine for me
let largestString;
function findLargestString(strings) {
let largestString = '';
strings.forEach((string) => {
if (string.length > largestString.length) {
largestString = string;
}
});
return largestString;
}
var arr = new Array('', 'aa', 'test')
console.log(findLargestString(arr));
Use the return value of your function and take a look at how 'let' works
let largestString = findLargestString(strings);