Recursive function returning empty arrray - javascript

I am having issue with my recursive function getPath, as it is returning an empty array, when it should be returning an array that looks something like this:
['main', 'children', 'name']
I am not sure if the logic pare is right, as that isn't what the question is about, the question is, why is my array empty? It is pushing data onto the array, but the final result is an empty array.
let dataScope = [{
"name": "main",
"location": [":data"]
}, {
"name": "child",
"location": ["main", "children"]
}]
function getLocation(key) {
let val = dataScope.find(i => i.name == key)
return val ? val.location : []
}
function getPath(items) {
let path = []
let item = items.shift()
if (item) {
let loc = getLocation(item)
if (loc.length > 0 && loc.join('.') != ':data') {
path.push(...getPath(loc))
console.log('added to array')
}
}
return path
}
console.log(getPath(['child', 'name']))

You don't do anything with loc so, it seems nothing gets pushed to the array
Note: I'm still trying to get to grips with why your original code results in an empty array - however, this code produces the expected result :p
let dataScope = [{
"name": "main",
"location": [":data"]
}, {
"name": "child",
"location": ["main", "children"]
}]
function getLocation(key) {
let val = dataScope.find(i => i.name == key);
return val ? val.location : []
}
function getPath(items, indent = 0) {
let z = items.join(',');
console.log(`${' '.repeat(indent)}called with ${z}`);
let path = [];
let item = items.shift();
let loc = [];
if (item) {
loc = getLocation(item);
if (loc.length > 0 && loc.join('.') != ':data') {
path.push(...getPath(loc.slice(), indent + 4)); // .slice() so loc isn't mutated
console.log(`${' '.repeat(indent)}${z} has path [${path.join(',')}]`);
}
path.push(...loc); // add loc to the path - comment this out to see the difference
}
console.log(`${' '.repeat(indent)}${z} returns [${path.join(',')}]`);
return path
}
console.log(`[${getPath(['child', 'name'])}]`)

First youre passing an array of names to getPath but then later youre passing the location array. Which one should it be? Logic needs tweaking. And also there's nothing in the dataset using the value "name" so your test is incorrect as well.

Its because you're doing recursion sending dataScope location but you implemented getPath expecting dataScope keys:
let dataScope = [{
"name": "main",
"location": [":data"]
}, {
"name": "child",
"location": ["main", "children"]
}]
function getLocation(key) {
let val = dataScope.find(i => i.name == key)
return val ? val.location : []
}
function getPath(keys) { // changing items name to keys for clarification
let path = []
let key = keys.shift()
if (key) {
let loc = getLocation(key);
if (loc.length > 0 && loc.join('.') != ':data') {
path.push(...loc) // push locs into array
getPath(keys) // call getPath with remaining keys
console.log('added to array')
}
}
return path
}
console.log(getPath(['child', 'main']))
You will not have :data into your path result because of this statement: loc.join('.') != ':data'. If you remove it you will get your expected output.

Related

Convert a list object into another structure

I have a list, converted into js array. Several rows has a Tab prefixes:
var data = [
"2",
" 2.1",
" 2.1.1",
" 2.2",
"3",
"4"
]
What I'm trying to do, is to get following structure:
var data = [
"2",
"2->2.1",
"2->2.1->2.1.1",
"2->2.2",
"3",
"4"
]
Tried (Produce wrong result):
for (var i = 0; i < data.length; i++) {
var current = data;
var length = data[i].length - data[i].replaceAll(" ", "").length;
if (!length) {
console.log(current);
} else {
console.log(data[i-1] + '->' + data[i].trim());
}
}
Update (#MustSeeMelons) - your solution produce wrong results on test data attached below:
flat to tree
I solved this problem in this Q&A. We can reuse the same functions on your data -
const data = `
2
2.1
2.1.1
2.2
3
4
`
// using makeChildren and sanitize from the linked Q&A
console.log(makeChildren(sanitize(data)))
[
{
"value": "2",
"children": [
{
"value": "2.1",
"children": [
{
"value": "2.1.1",
"children": []
}
]
},
{
"value": "2.2",
"children": []
}
]
},
{
"value": "3",
"children": []
},
{
"value": "4",
"children": []
}
]
tree to flat
All that remains now is to convert the tree to flat list of paths -
function* paths(t) {
switch (t?.constructor) {
case Array:
for (const child of t)
yield* paths(child)
break
case Object:
yield [t.value]
for (const path of paths(t.children))
yield [t.value, ...path]
break
}
}
const result =
Array.from(paths(makeChildren(sanitize(data))), path => path.join("->"))
[
"2",
"2->2.1",
"2->2.1->2.1.1",
"2->2.2",
"3",
"4"
]
advantages
Decomposing the problem into smaller parts makes it easier to solve and yields reusable functions but those are not the only advantages. The intermediate tree representation gives you the ability to make other modifications in the context of the tree that the flat representation does not permit. Additionally, the paths function yields arrays of paths segments, allowing the caller to decide which final effect is desired, ie path.join("->"), or otherwise.
demo
Run the demo below to verify the result in your own browser -
const sanitize = (str = "") =>
str.trim().replace(/\n\s*\n/g, "\n")
const makeChildren = (str = "") =>
str === ""
? []
: str.split(/\n(?!\s)/).map(make1)
const make1 = (str = "") => {
const [ value, children ] = cut(str, "\n")
return { value, children: makeChildren(outdent(children)) }
}
const cut = (str = "", char = "") => {
const pos = str.search(char)
return pos === -1
? [ str, "" ]
: [ str.substr(0, pos), str.substr(pos + 1) ]
}
const outdent = (str = "") => {
const spaces = Math.max(0, str.search(/\S/))
const re = new RegExp(`(^|\n)\\s{${spaces}}`, "g")
return str.replace(re, "$1")
}
function* paths(t) {
switch (t?.constructor) {
case Array: for (const child of t) yield* paths(child); break
case Object: yield [t.value]; for (const path of paths(t.children)) yield [t.value, ...path]; break
}
}
const data = `\n2\n\t2.1\n\t\n\t2.1.1\n\t2.2\n3\n4`
console.log(
Array.from(paths(makeChildren(sanitize(data))), path => path.join("->"))
)
.as-console-wrapper { min-height: 100%; top: 0; }
remarks
outdent is generic and works whether you use literal tabs, \t \t\t \t\t\t..., or some number of spaces. What matters is the whitespace is consistent. View the original Q&A for more insight on how each part works.
There are many approaches to this.
Approach #1:
In case you're allowed to use auxiliary space, create an array that will keep track of the latest level of the rows. It is not based on the separator.
let data = ["2","\t2.1","\t\t2.1.1","\t2.2","3","4"], tab="\t", latest = [];
let output = data.map(x => {
let noTabs = x.split(tab).length-1;
latest = [...latest.slice(0, noTabs), x.replaceAll(tab, '')];
return latest.join('->');
})
console.log(output)
This will almost do what you wish, not sure how you want to group them:
const mapped = data.reduce((acc, curr, idx) => {
if (idx !== 0) {
// Get leading digit of current & previous element
const current = curr.trim()[0];
const previous = acc[idx - 1].trim()[0];
// If leading match, aggregate them
if (current === previous) {
acc.push(`${acc[idx - 1].trim()}->${curr.trim()}`);
} else {
acc.push(curr.trim());
}
} else {
acc.push(curr.trim());
}
return acc;
}, []);
Don't use for loops unless you need to break out of the loop at some point. Transforming arrays usually should be done with the map function.
I used reduce because this problem required me to access the new, already mapped element.

API Call Before Next Iteration Starts in Loop

I would like to send a POST request to a certain app through their API. What I am trying to do is to process the input data (called data) and send a POST request on one record by one record in the loop. Then, I delete the corresponding object in data for optimization purpose. I know that because of the asynchronous feature of JavaScript, the loop finishes before the function gets called. However, even though I wrap the api function in IIFE or wrap it in an async function with await(the code is below), the compiler still gives me function calls with the same parameter which is the last object. So, when I see created records on the app, David's information was generated three times. The screenshot below is each record object after being processed. If you could tell me ways of triggering the api call before the next iteration in the loop, that would be greatly appreciated.
const obj = [];
var record = {};
var data = [
{
"userId": "123",
"name": "John",
"phoneNumber": "123-456-6789"
},
{
"userId": "345",
"name": "Summer",
"phoneNumber": "535-631-9742"
},
{
"userId" : "789",
"name": "David",
"phoneNumber": "633-753-1352"
}
]
var dataLen = data.length;
var people = data;
createKeyValue = ((key, value) => {
var temp = {};
temp["value"] = value;
obj[key] = temp;
});
apiCall = ((record) => {
clientInformation.record.addRecord.then((resp) => {
console.log(resp);
}).catch((err) => {
console.log(err);
});
});
async function asyncFunction(record) {
let promise = new Promise((resolve, reject) => {
setTimeout(() => apiCall(record), 1000)
});
let result = await promise;
console.log(result);
}
while (dataLen > 0) {
for (let [key, value] of Object.entries(data[0])) {
switch(key) {
case 'userId':
createKeyValue(key, value);
break;
case 'name':
createKeyValue(key, value);
break;
default:
}
}
record["record"] = obj;
asyncFunction(record);
data.shift();
dataLen -= 1;
}
Here is the screenshot of how each processed data looks like.
I think you haven't understand how the for loop inside the while works. The data should be incremented each time to get the next array inside data.
The data[0] => { userId: 123 ... }, data[1] => { userId: 345 ... } and so on .
At each for loop iteration checks the 3 elements of each sub array, so each time temp stores the key values for userId and name. So when the loop finishes, the temp contains as key => userId, name and the corresponding values.
var data = [
{
"userId": "123",
"name": "John",
"phoneNumber": "123-456-6789"
},
{
"userId": "345",
"name": "Summer",
"phoneNumber": "535-631-9742"
},
{
"userId" : "789",
"name": "David",
"phoneNumber": "633-753-1352"
}
]
var dataLen = data.length;
let i = 0 ;
while ( i < dataLen) {
let temp = [];
for (let [key, value] of Object.entries(data[i])) {
if(key == 'userId' || key == 'name'){
temp[key] = value;
}
}
//Just to print the values and understand
for(let k in temp){
console.log(k+" -> "+temp[k]);
}
//here you will pass the temp values to functions
console.log(" At each iteration execute the required functions ");
//asyncFunction(temp);
i += 1;
}

return value after filtering array inside an object of arrays

I am trying to create an autocomplete which returns an array of objects using a function. My Object is something like:
this.vehiclesList =
[
{
"additionalDriverContacts": [9929929929, 9992992933, 9873773777],
"id": 1
},
{
"additionalDriverContacts": [8388388388, 8939939999],
"id": 2
}
]
I want to filter the array based on additionalDriverContacts .
My function goes like this:
filterVehicleAdditionalMobile(val: string) {
if (typeof val != 'string') {
return [];
}
let value= val? this.vehiclesList.filter((item) => {
if(item.additionalDriverContacts)
item.additionalDriverContacts.forEach((option)=> {
String(option).toLowerCase().indexOf(val.toLowerCase()) != -1
})
}
}) : this.vehiclesList;
console.log(value)
return value;
}
But in the console value is coming empty array. Where did I go wrong. I tried looking for the solution in this question How do i filter an array inside of a array of objects?
but it didnot help as my usecase is different.
My desired Result should be like:
If 99299 is passed as an argument to the function , then additionalDriverContacts matching that number should be return as an array.
for input 99299, result = [9929929929,9992992933] should be returned
for input 99299, result = [9929929929,9992992933] should be returned
We can use array .map() to extract contacts, then filter down with string .search():
const vehiclesList = [
{"id": 1, "additionalDriverContacts": [9929929929, 9992992933, 9873773777]},
{"id": 2, "additionalDriverContacts": [8388388388, 8939939999]}]
result = getMatchingContacts(vehiclesList, 99299) // run test
console.log(result) // show result
function getMatchingContacts(list, key) {
const arrayOfContacts = list.map(item => item.additionalDriverContacts)
const contacts = [].concat(...arrayOfContacts) // flatten the nested array
.filter(contact => contact.toString().search(key.toString()) >= 0) // find matches
return contacts
}
Hope this helps.
Cheers,
So what you need to do here is first transform each of the items in vehiclesList into an array of matching results, and then concatenate those together.
Give this a try:
var vehiclesList = [{
"additionalDriverContacts": [9929929929, 9992992933, 9873773777],
"id": 1
},
{
"additionalDriverContacts": [8388388388, 8939939999],
"id": 2
}
];
function filterVehicleAdditionalMobile(val) {
if (typeof val != 'string') {
return [];
}
// array of arrays
const values = vehiclesList.map((item) => {
if (!item.additionalDriverContacts) { return []; }
return item.additionalDriverContacts.filter((option) =>
String(option).toLowerCase().indexOf(val.toLowerCase()) != -1
);
});
console.log(values);
// flatten
return Array.prototype.concat.apply([], values);
}
console.log(filterVehicleAdditionalMobile('99'));
Alternatively, you could concatenate all of the items together and then filter them. This is less efficient, but simpler and less code:
var vehiclesList = [{
"additionalDriverContacts": [9929929929, 9992992933, 9873773777],
"id": 1
},
{
"additionalDriverContacts": [8388388388, 8939939999],
"id": 2
}
];
function flatten(values) {
return Array.prototype.concat.apply([], values);
}
function filterVehicleAdditionalMobile(val) {
if (typeof val != 'string') {
return [];
}
return flatten(vehiclesList.map(v => v.additionalDriverContacts || []))
.filter(option => String(option).toLowerCase().indexOf(val.toLowerCase()) != -1);
}
console.log(filterVehicleAdditionalMobile('99'));
Updated : With the last edit of the question
try to change by :
filterVehicleAdditionalMobile(val: string) {
if (typeof val !== 'string') {
return [];
}
let driverContacts = [];
this.vehiclesList.forEach((vehicule) => {
if (vehicule.additionalDriverContacts) {
if (val) {
driverContacts = driverContacts.concat(vehicule.additionalDriverContacts.filter((driverContact) => {
return String(driverContact).toLowerCase().indexOf(val.toLowerCase()) !== -1;
}));
} else {
driverContacts = driverContacts.concat(vehicule.additionalDriverContacts);
}
}
});
return driverContacts;
}
Test :
const driver = this.filterVehicleAdditionalMobile('8');
console.log(driver);
Display :
0: 9873773777
1: 8388388388
2: 8939939999

Search for a related json data

How can i find data that is related to the already known data?
( I'm a newb. )
For example here is my json :
[
{ "id": "1", "log": "1","pass": "1111" },
{ "id": 2, "log": "2","pass": "2222" },
{ "id": 3, "log": "3","pass": "3333" }
]
Now i know that "log" is 1 and i want to find out the data "pass" that is related to it.
i've tried to do it so :
The POST request comes with log and pass data , i search the .json file for the same log value and if there is the same data then i search for related pass
fs.readFile("file.json", "utf8", function (err, data) {
var jsonFileArr = [];
jsonFileArr = JSON.parse(data); // Parse .json objekts
var log = loginData.log; // The 'log' data that comes with POST request
/* Search through .json file for the same data*/
var gibtLog = jsonFileArr.some(function (obj) {
return obj.log == log;
});
if (gotLog) { // If there is the same 'log'
var pass = loginData.pass; // The 'pass' data that comes with POST request
var gotPass = jsonFileArr.some(function (obj) {
// How to change this part ?
return obj.pass == pass;
});
}
else
console.log("error");
});
The problem is that when i use
var gotPass = jsonFileArr.some(function (obj) {
return obj.pass == pass;
});
it searches through the whole .json file and not through only one objekt.
Your main problem is that .some() returns a boolean, whether any of the elements match your predicate or not, but not the element itself.
You want .find() (which will find and return the first element matching the predicate):
const myItem = myArray.find(item => item.log === "1"); // the first matching item
console.log(myItem.pass); // "1111"
Note that it is possible for .find() to not find anything, in which case it returns undefined.
The .some() method returns a boolean that just tells you whether there is at least one item in the array that matches the criteria, it doesn't return the matching item(s). Try .filter() instead:
var jsonFileArr = JSON.parse(data);
var log = loginData.log;
var matchingItems = jsonFileArr.filter(function (obj) {
return obj.log == log;
});
if (matchingItems.length > 0) { // Was at least 1 found?
var pass = matchingItems[0].pass; // The 'pass' data that comes with the first match
} else
console.log("error"); // no matches
Using ES6 Array#find is probably the easiest, but you could also do (among other things)
const x = [{
"id": "1",
"log": "1",
"pass": "1111"
}, {
"id": 2,
"log": "2",
"pass": "2222"
}, {
"id": 3,
"log": "3",
"pass": "3333"
}];
let myItem;
for (let item of x) {
if (item.log === '1') {
myItem = item;
break;
}
}
console.log(myItem);

Efficiently rename/re-map javascript/json object keys within array of objects

I have some structured JSON data like so. Let's assume this is interchangeable, via JSON.parse():
[
{
"title": "pineapple",
"uid": "ab982d34c98f"
},
{
"title": "carrots",
"uid": "6f12e6ba45ec"
}
]
I need it to look like this, remapping title to name, and uid to id with the result:
[
{
"name": "pineapple",
"id": "ab982d34c98f"
},
{
"name": "carrots",
"id": "6f12e6ba45ec"
}
]
The most obvious way of doing it is like this:
str = '[{"title": "pineapple","uid": "ab982d34c98f"},{"title": "carrots", "uid": "6f12e6ba45ec"}]';
var arr = JSON.parse(str);
for (var i = 0; i<arr.length; i++) {
arr[i].name = arr[i].title;
arr[i].id = arr[i].uid;
delete arr[i].title;
delete arr[i].uid;
}
str = '[{"title": "pineapple","uid": "ab982d34c98f"},{"title": "carrots", "uid": "6f12e6ba45ec"}]';
var arr = JSON.parse(str);
for (var i = 0; i<arr.length; i++) {
arr[i].name = arr[i].title;
arr[i].id = arr[i].uid;
delete arr[i].title;
delete arr[i].uid;
}
$('body').append("<pre>"+JSON.stringify(arr, undefined, 4)+"</pre>");
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
...or using something more complex (albeit not more efficient) like this.
This is all fine and dandy, but what if there were 200,000 objects in the array? This is a lot of processing overhead.
Is there a more efficient way to remap a key name? Possibly without looping through the entire array of objects? If your method is more efficient, please provide proof/references.
As I already mentioned in the comments, if you can make certain assumptions about the values of the objects, you could use a regular expression to replace the keys, for example:
str = str.replace(/"title":/g, '"name":');
It's not as "clean", but it might get the job done faster.
If you have to parse the JSON anyway, a more structured approach would be to pass a reviver function to JSON.parse and you might be able to avoid an additional pass over the array. This probably depends on how engine implement JSON.parse though (maybe they parse the whole string first and then make a second pass with the reviver function, in which case you wouldn't get any advantage).
var arr = JSON.parse(str, function(prop, value) {
switch(prop) {
case "title":
this.name = value;
return;
case "uid":
this.id = value;
return;
default:
return value;
}
});
Benchmarks, using the Node.js script below to test 3 times:
1389822740739: Beginning regex rename test
1389822740761: Regex rename complete
// 22ms, 22ms, 21ms
1389822740762: Beginning parse and remap in for loop test
1389822740831: For loop remap complete
// 69ms, 68ms, 68ms
1389822740831: Beginning reviver function test
1389822740893: Reviver function complete
// 62ms, 61ms, 60ms
It appears as if the regex (in this case) is the most efficient, but be careful when trying to parse JSON with regular expressions.
Test script, loading 100,230 lines of the OP's sample JSON:
fs = require('fs');
fs.readFile('test.json', 'utf8', function (err, data) {
if (err) {
return console.log(err);
}
console.log(new Date().getTime() + ": Beginning regex rename test");
var str = data.replace(/"title":/g, '"name":');
str = str.replace(/"uid":/g, '"id":');
JSON.parse(str);
console.log(new Date().getTime() + ": Regex rename complete");
console.log(new Date().getTime() + ": Beginning parse and remap in for loop test");
var arr = JSON.parse(data);
for (var i = 0; i < arr.length; i++) {
arr[i].name = arr[i].title;
arr[i].id = arr[i].uid;
delete arr[i].title;
delete arr[i].uid;
}
console.log(new Date().getTime() + ": For loop remap complete");
console.log(new Date().getTime() + ": Beginning reviver function test");
var arr = JSON.parse(data, function (prop, value) {
switch (prop) {
case "title":
this.name = value;
return;
case "uid":
this.id = value;
return;
default:
return value;
}
});
console.log(new Date().getTime() + ": Reviver function complete");
});
Asked this question a long time ago, and since then, I've grown acustomed to using Array.prototype.map() to get the job done, more for stability and cleanliness of code than performance. While it's certainly not the most performant, it looks great:
var repl = orig.map(function(obj) {
return {
name: obj.title,
id: obj.uid
}
})
If you need a more flexible (and ES6-compatible function), try:
let replaceKeyInObjectArray = (a, r) => a.map(o =>
Object.keys(o).map((key) => ({ [r[key] || key] : o[key] })
).reduce((a, b) => Object.assign({}, a, b)))
e.g.
const arr = [{ abc: 1, def: 40, xyz: 50 }, { abc: 1, def: 40, xyz: 50 }, { abc: 1, def: 40, xyz: 50 }]
const replaceMap = { "abc": "yyj" }
replaceKeyInObjectArray(arr, replaceMap)
/*
[
{
"yyj": 1,
"def": 40,
"xyz": 50
},
{
"yyj": 1,
"def": 40,
"xyz": 50
},
{
"yyj": 1,
"def": 40,
"xyz": 50
}
]
*/
Here's another take on the OP's suggestion to use map() for clarity (not performance).
var newItems = items.map(item => ({
name: item.title,
id: item.uid
}));
This uses ES6 arrow functions and the shortcut syntaxes that are possible when there's only one parm passed to the function and only one statement in the body of the function.
Depending on your history with lambda expressions in various languages, this form may or may not resonate with you.
Be careful when returning an object literal in the arrow function shortcut syntax like this. Don't forget the additional parens around the object literal!
If you want to make it a little more reusable. Maybe this is a decent approach.
function rekey(arr, lookup) {
for (var i = 0; i < arr.length; i++) {
var obj = arr[i];
for (var fromKey in lookup) {
var toKey = lookup[fromKey];
var value = obj[fromKey];
if (value) {
obj[toKey] = value;
delete obj[fromKey];
}
}
}
return arr;
}
var arr = [{ apple: 'bar' }, { apple: 'foo' }];
var converted = rekey(arr, { apple: 'kung' });
console.log(converted);
Using ES6:
const renameFieldInArrayOfObjects = (arr, oldField, newField) => {
return arr.map(s => {
return Object.keys(s).reduce((prev, next) => {
if(next === oldField) {
prev[newField] = s[next]
} else {
prev[next] = s[next]
}
return prev
}, {})
})
}
Using ES7:
const renameFieldInArrayOfObjects = (arr, oldField, newField) => {
return arr.map(s => {
return Object.keys(s).reduce((prev, next) => {
return next === oldField
? {...prev, [newField]: s[next]}
: {...prev, [next]: s[next]}
}, {})
})
}
You can use an npm package named node-data-transform.
Your data :
const data = [
{
title: 'pineapple',
uid: 'ab982d34c98f',
},
{
title: 'carrots',
uid: '6f12e6ba45ec',
},
];
Your mapping :
const map = {
item: {
name: 'title',
id: 'uid',
},
};
And use the package :
const DataTransform = require("node-json-transform").DataTransform;
const dataTransform = DataTransform(data, map);
const result = dataTransform.transform();
console.log(result);
Result :
[
{
name: 'pineapple',
id: 'ab982d34c98f'
},
{
name: 'carrots',
id: '6f12e6ba45ec'
}
]
Maybe it's not the best way for performance, but it's quite elegant.
var jsonObj = [/*sample array in question*/ ]
Based on different benchmarks discussed below, fastest solution is native for:
var arr = [];
for(var i = 0, len = jsonObj .length; i < len; i++) {
arr.push( {"name": jsonObj[i].title, "id" : jsonObj[i].uid});
}
I think alternatively without using a frameworks this will be option 2:
var arr = []
jsonObj.forEach(function(item) { arr.push({"name": item.title, "id" : item.uid }); });
There is always debate between using navite and non-navite functions. If I remember correctly lodash argued they were faster than underscore because the use non-native functions for key operations.
However different browsers will produce sometimes very different results. I always looked for the best average.
For benchmarks you can take a look at this:
http://jsperf.com/lo-dash-v1-1-1-vs-underscore-v1-4-4/8
function replaceElem(value, replace, str) {
while (str.indexOf(value) > -1) {
str = str.replace(value, replace);
}
return str;
}
call this from main
var value = "tittle";
var replace = "name";
replaceElem(value, replace, str);

Categories

Resources