I have an array of both objects (JSX elements) and strings, which I want to iterate over and perform logic on the items that are strings, but skip the objects.
const array= ['string', {...}, {...}, 'string', 'string', {...}];
for (let i = 0; i < array.length ; i++) { if( {/* This is an object */} ){continue;}else{
{/* perform logic */}}
Is is possible to continue a loop based on type?
The typeof operator should do what you want. However, if the logic you wish to perform only works on strings then you may want to consider inverting your logic to guard against the potential for your array to contain additional data types in the future.
Something like:
const array= ['string', {...}, {...}, 'string', 'string', {...}];
for (let i = 0; i < array.length ; i++) {
const str = array[i]
if( typeof str === 'string'){
/* perform logic on str */
}
See the typeof operator.
eg.
if (typeof array[i] !== 'string') {
continue;
}
Related
Not sure whats going on here, Im running my array of objects through a type guard, And if it is of length 3 then I want to assert it to be of a different type (tuple), But for some reason typescript is not recognising the new type I have assigned to it.
interface squareType {
value: string,
}
// Defining a tuple which will allow us to set a max length of each row
type rowTuple = [squareType, squareType, squareType]
// define a 3*3 grid
type gridTuple = [rowTuple, rowTuple, rowTuple]
function isRowTuple(possibleRow: squareType[]): possibleRow is rowTuple {
return (possibleRow.length === 3) ? true : false
}
// type guard to check if it is a grid tuple
function isGridTuple(possibleGrid: rowTuple[]): possibleGrid is gridTuple {
// check if the length is correct
return true
}
function App() {
const [appState, setAppState] = useState<gridTuple | undefined>()
// build a grid
useEffect(() => {
let grid: rowTuple[] = []
for (let y = 1; y <= 3; y++) {
let row: squareType[] = []
for (let x = 1; x <= 3; x++) {
let sq: squareType;
sq = {
value: 'kudos',
}
row.push(sq)
}
// change type of row to rowTuple
isRowTuple(row)
grid.push(row) // error is here
}
}, [])
The error is squareType[] is not assignable to row tuple, target requires 3 elements but source may have fewer.
However I have checked and 'row' does have 3 elements and the call to the type guard does return true
Imagine I have the below setup.
I am trying to sort [first, second, third] alphabetically and also by category.
I'd like featured items first and then everything else sorted by title.
I tried let output = [first, second, third].sort { $0.category == $1.category ? $0.category && $1.category == .featured : $0.title < $1.title }
But it was a complete disaster.
The end result should be that the sorted titles read foo bar baz
enum Category {
case featured, standard
}
struct Article {
var title: String
var category: Category
}
let first = Article(title: "bar", category: .standard)
let second = Article(title: "foo", category: .featured)
let third = Article(title: "boo", category: .standard)
let output = [first, second, third]
More Scalable Solution
When you have nested sorting, you need to group items first and then sort by those groups. For example we can define priority of the categories with an Int value just by constraint the enum to Int:
enum Category: Int {
case featured // Higher level(value) -> Higher priority in sort
case standard // Lower level(value) -> Lower priority in sort
}
Now you should:
Group the data before sort:
let grouped = Dictionary(grouping: input, by: { $0.category })
Sort groups: (So Category cases should have sortable value like an Int)
let sortedGroups = grouped.sorted { $0.key.rawValue < $1.key.rawValue }
Sort each group's data and map it back to the original array:
let result = sortedGroups.map { $0.value.sorted { $0.title < $1.title) } }
This approach is highly scalable and automated. You just need to define new categories in place of their priority. The rest of the code always works as expected.
If you want the Articles to be filtered inside the .featured category as well (in addition to listing featured before the rest):
let res = output.sorted {
if $0.category == $1.category {
return $0.title < $1.title
} else {
return $0.category == .featured
}
}
I want to take each element of an array of documents I queried and check if it is in an other array of documents I queried
I have this model :
var dataTypeSchema = new Schema({
name : String,
description : String,
// Personnel || Professionel || Intime
category : String,
provenance : {
type : Schema.ObjectId,
ref : 'Service'
}
});
and this one :
var dataUseSchema = new Schema({
purpose : {
type : Schema.ObjectId,
ref : 'Purposes'
},
dataTypes : [{
type : Schema.ObjectId,
ref : 'DataType'
}],
description : String,
service : {
type : Schema.ObjectId,
ref : 'Service'
},
user : {
type : Schema.ObjectId,
ref : 'User'
}
});
I basically query an array of dataTypes and then want to check for each one if it is in a specific dataUse.data
I tried several methods of comparison : includes, indexOf, even comparing each elements _ids, it always returns false
I have checked that both arrays indeed contain documents and that they have some documents in common
Here is my code (one attempt) for comparing, I am first getting an array of DataUses and then for each one I want to check which values it shares with the dataTypesArray
const dataTypesArray = await Service.getServiceDataTypes(req.params.serviceName)
DataUse.find({service : serviceID, user : serviceID})
.populate('purpose dataTypes service user')
.exec()
.then(dataUses => {
for(let j = 0; j < dataUses.length; j++) {
for(let k = 0; k < dataTypesIDs.length; k++) {
if(dataUses[j].dataTypes.indexOf(dataTypesIDs[k])==-1) {
dataUses[j]["otherData"].push(dataTypesIDs[k])
}
}
}
return dataUseArray
})
.catch(console.log)
For the last if (condition), everything sets equal to -1
Both dataTypes arrays are populated, but as I said I tried comparing just _id and still same result
thanks !
Here is how you can do it...
To compare an array with other of Mongoose Array.
See the IDs your are getting are mongoose ObjectID which is an object
To compare an Array of ObjectID with other ObjectID:
// converting dataTypes to String,
// you can also convert it by using `lean`
// i.e Model.find().lean().exec()
// this would return JavascriptObject instead of MongooseModel
dataTypeIDs = dataTypes.map(i => i.toString()); // converting to plain
// Array of string
dataTypeIDs.indexOf(someDataTypeID.toString()); // returns -1 if not found
You can use the last line to convert it anyway you like in your if (condition)
Change this block of code like this:
.then(dataUses => {
// assuming its an Array of ObjectID
dataTypesIDsArray = dataTypesIDs.map(i => i.toString())
for(let j = 0; j < dataUses.length; j++) {
usesDataTypes = dataUses[j].dataTypes.map(i => i.toString())
for(let k = 0; k < dataTypesIDsArray.length; k++) {
if(usesDataTypes.indexOf(dataTypesIDsArray[k]) == -1) {
dataUses[j]["otherData"].push(dataTypesIDsArray[k])
// use ObjectID from mongoDB if you want to cast it to "ObjectId"
}
}
}
return dataUseArray;
})
Its not tested, but I have taken reference of an existing tested code from my project.
I have a service which populates my associative array in typescript,
fun populateData(){
let tempArr;
tempArr = [];
this.service.get('Post', 1, 'true').subscribe(
(response) => {
this.loadingIcon = false;
for (let i = 0; i < response.results.length; i++) {
tempList = response.results[i]['tags'];
for ( let iter of tempList){
if ( iter in tempArr) {
tempArr[iter] = tempArr[iter] + 1;
}else {
tempArr[iter] = 1;
}
}
}
},
(error) => {
if (error['status'] === 401) {
localStorage.clear();
this.router.navigateByUrl('/login');
} else {
this.router.navigateByUrl('/error');
}
}
);
console.log(tempArr);
/*
This function is inside a class, once I iterate get access to tempArr I will be assigning the tempArr data to a class variable like
for (items in tempArr){
this.data.push(items, tempArr[items]);
}
*/
}
I'm able to populate my associative array with the service above which gives the following output in console,
I'm not able to iterate through this array, I tried a couple of methods like the following,
for ( const key in tempArr) {
console.log(key + ':' + tempArr[key]);
}
I want both they key and values from the array.
TypeScript generally assumes that the keys to arrays are numbers. What you were doing might work but it's not very idiomatic. I'm not going to rewrite your whole function but here are a few pointers:
When constructing your associative array (map for short from now on) you should try using an object instead of an array:
const tagCounts: { [key: string]: number } = {};
for (const result of response.results) {
for (const tag of result.tags) {
tagCounts[tag] = (tagCounts[tag] || 0) + 1;
}
}
Then you can iterate the result with:
for (const tag of Object.keys(tagCounts)) {
const count = tagCounts[tag];
// Other stuff here
}
Or if you have polyfills for Object.entries then with:
for (const [tag, count] of Object.entries(tagCounts)) {
// Other stuff here
}
Looking at your code, this.data.push also seems wrong: it will add a string and a number to your data array which is almost certainly not what you want. You might want to consider converting data to an object as well if you want to store key-value pairs.
public Update() {
this.Data = this.Items;
console.log(this.Data);
for (let value of this.Data) {
console.log(value);
}
}
console
[Object, Object, Object]
Object
CandidateName:"B"
ControlId:0
CreatedBy:null
CreationDateTime:null
ExamId1:2000
ExamName:" Safety"
Id:1292353
after last object it showing length:3
when i going to loop over this object,it is throwing error length is undefined,please help me.
If I understand correctly, this.Items is probably undefined in some cases and you cannot iterate.
So:
for (let value of (this.Data || [])) {
This guards against bad values
The for in or for of statement should be avoided for iterating over arrays. It has two "drawbacks":
1) The order is not guarantee
2) Also inherited properties will be listed/enumerated, if enumerable:false is not specified when defining the property.
If for example you add a property to your prototype, this loop will iterate also over that one.
Array.prototype.test = "test";
var a = ['a', 'b'];
for (let i in a) {
console.log(a[i]);
}
for (let i of a) {
console.log(i);
}
You should see also your property printed.
If you change your loop into a sequential for loop:
for (let i = 0; i < this.Data.length; i++ value of this.Data) {
console.log(this.Data[i]);
}
or a:
this.Data.forEach((el) => {
console.log(el);
});
you may not see your issue.
If you want to iterate over objects, You must use Object.keys(your_obj). Because object doesn't have length property. You can iterate through only of type 'Array' or 'String' using for of. You can use Object.keys(your_obj).length for sequential for loop for(var i=0; i<Object.keys(your_obj).length; i++)
public Update() {
this.Data = this.Items;
console.log(this.Data);
for (let obj of this.Data) {
console.log(obj);
//iterate over object here
for(let property of Object.keys(obj)){
// write your logic for specific object
}
}
}
Extension to quirimmo's answer, sequential for loop, use this
this.Data.forEach(function(obj){
console.log(obj);
//iterate over object here
for(var i=0; i<Object.keys(obj).length; i++){
// write your logic for specific object
}
})