Split string using elements of array JavaScript - javascript

Hello i'm working on dynamic search for my first web application
and i have a huge problem i've been working on this for several hours using diffrent solutions in the end i think this might be the closest to resolving my problems.
Search is not static so hard coding this is not an option.
const search = 'someone loves some thing';
const teams = ['some thing', 'someone', 'help'];
const twoTeams = [];
if (teams.some(el => search.includes(el))) {
teams.forEach(word => {
if (search.includes(word)) {
twoTeams.push(word);
}
})
}
console.log(twoTeams); // ['some thing','someone']
console.log(search) // 'someone loves some thing'
// looking for // console.log(twoTeams)// 'someone','some thing'
And here im stuck i have array of items that i need to split string with to access data from API i just need it in that order i cant reverse order because in the app theres too many elements in array and user can search anything so you dont know which one should will be index[0] and which one should be index[1] and thats crucial to my dynamic search.

First of all, I'd start with removing the outer if as we check every word anyway in the inner condition.
teams.forEach(word => {
// Get the position of word in the search text. Negative value means that there is no match.
let index = search.indexOf(word);
// If the search text contains the word
if (index >= 0) {
// Save both the matching word and its index
twoTeams.push( { 'word': word, 'index': index});
}
})
// Sort the results by the index and select the word only
twoTeams = results.sort((a,b)=> a.index - b.index).map(result => result.word);

You can simplify your code to built twoTeams object with filter like below.
const twoTeams = teams.filter(el => search.includes(el));
As you want preserve order then you can sort your results with indexOf like below.
twoTeams.sort((a, b) => search.indexOf(a) - search.indexOf(b));
Try it below.
const search = 'someone loves some thing';
const teams = ['some thing', 'someone', 'help'];
const twoTeams = teams.filter(el => search.includes(el));
twoTeams.sort((a, b) => search.indexOf(a) - search.indexOf(b));
console.log(twoTeams); // looking for ['someone', 'some thing']
console.log(search) // 'someone loves some thing'

Related

Cypress get content of several DIVs, and compose one long string

I have some divs that render this way:
<div class="customer-data-column">
<h3>Title:</h3>
<div>Name Lastname</div>
<div>123 Address xxx yyy</div>
<div>Chicago XY 33056</div>
<div>Country name</div>
</div>
This content is generated by:
{customerData.replaceAll("/r", "").split("\n").map(item => <div key={item}>{item}</div>)}
This data is coming from redux.
In console log (from redux data) the address appears this way:
Name Lastname\n123 Address xxx yyy\nChicago XY 33056\nCountry name
I want to check in Cypress if this address is correct, the same that is in redux.
I need some way that merges the content of the divs into one string, and adds the \n between each div.
I thought I could start this way:
cy.get('.customer-data-column').should($title => {
const store = Cypress.store.getState();
const reduxPath = store.customerData;
expect("not sure what to put here... how to merge").to.equals(reduxPath)
Can anyone please help?
===
EDIT
I made it almost work this way:
I added a class to the inner divs, so they render this way:
<div class="customer-data-column">
<h3>Title:</h3>
<div class="address-row">Name Lastname</div>
<div class="address-row">123 Address xxx yyy</div>
<div class="address-row">Chicago XY 33056</div>
<div class="address-row">Country name</div>
</div>
And the test:
cy.get('.address-row').then($divList => {
const textArray = Cypress.$.makeArray($divList).map(el => el.innerText)
const actual = textArray.join(textArray, '\n') // use join to array of strings into single string
expect(actual).to.eql(billToAddress)
})
However it still fails with such message:
assert expected Name LastnameName Lastname,23 Address xxx yyy,Chicago
XY 33056,Country name23 Address xxx yyyName Lastname,23 Address xxx
yyy,Chicago XY 33056,Country name7th FloorName Lastname,23 Address xxx
yyy,Chicago XY 33056,Country nameBrooklyn NY 11210Name Lastname,23
Address xxx yyy,Chicago XY 33056,Country nameCountry name to deeply
equal Name Lastname\n23 Address xxx yyy\n7th Floor\nBrooklyn NY
11210\nCountry name
Edit 2:
The solution that I found and works is this one:
.then(users => {
const billToAddress = users.response.body.filter(el => el.orderNumber === '3-331877')[0]
.billTo
cy.get('.address-row').each((item, index) => {
cy.log(cy.wrap(item).should('contain.text', billToAddress.split('\n')[index]))
})
})
Of course if somebody has a better way for achieving this test, I am open to learn more and code better.
If you make an array of the store data, an .each() loop can compare them.
const store = Cypress.store.getState()
const reduxData = store.customerData // expect 'Name Lastname\n123 Address xxx yyy\nChicago XY 33056\nCountry name'
const reduxDataArray = reduxData.split('\n')
cy.contains('.customer-data-column', 'Title:')
.find('div') // only divs inside '.customer-data-column'
.each(($div, index) => {
expect($div.text()).to.eq(reduxDataArray[index])
})
From other comment, it looks like cy.get('.custom-data-column') isn't strong enough to isolate this HTML you need to work on.
Perhaps cy.contains('.customer-data-column', 'Title:') is better.
All text at once
In this particular case you can test all text at once by globally removing \n
const store = Cypress.store.getState()
const reduxData = store.customerData // expect 'Name Lastname\n123 Address xxx yyy\nChicago XY 33056\nCountry name'
const reduxAllTexts = reduxData.replace(/\n/g, '')
cy.contains('.customer-data-column', 'Title:')
.find('div')
.invoke('text')
.should('eq', reduxAllTexts)
If the Cypress function yields multiple elements, we can join the text of those elements to create your string.
cy.get('.custom-data-column').find('div').then(($divList) => {
const store = Cypress.store.getState();
const reduxPath = store.customerData;
const textArray = $divList.map((x) => x.text()); // get the text values as an array
const actual = textArray.join(textArray, '\n'); // use join to array of strings into single string
expect(actual).to.eql(reduxPath);
});

Javascript Compare two arrays based on partial match and update to fully match

I'm working on a project in Node.js (Foundry Virtual Table Top), and I have two arrays. First is a list of entries, the second is the same list, but with indexing numbers prefixed to it. I need to match the two, based on the name, and update the first list to include the same numbering.
Example of 1st list:
Early Life
Circumstances of Birth
Family
Region
...
Example of 2nd list:
1.3. Early Life
1.3.1. Circumstances of Birth
1.3.2. Family
1.3.3. Region
So I want "Early Life" to be updated to "1.3. Early Life"
I have the base of it done, along with the regex I need, but I'm not sure how to actually compare, match, and then update based on the partial matches. Any help would be greatly appreciated, I have very little experience with javascript.
/** Takes a compendium of journals and matches then replaces names to those of an imported list prepended with index numbers
* New naming convention == "#.##... TEXT"
* Eg. "Special Abilities" -> "2.01.2. Special Abilities"
*
* 1. put the new name list in an array
* 2. iterate through the compendium, get index or contents
* 3. compare the entries name value vs new list(array), regex'd to remove the #s and . prior to the name, and set an update array with the new name
* 4. update the compendium
*/
//read text file of new names
let f = await fetch("/test_list.txt")
let txt = await f.text()
// Convert to array, split by line
let updatedNames = txt.toString().split("\n");
// Compendium to Update
const compendiumLabel = "Gamemastery";
(async ()=>{
const p = game.packs.entries.find(e => e.metadata.label === compendiumLabel)
// Array of current journal entries (filtering out Compendium Folders)
const currentNames = p.index.filter(x => x.name != '#[CF_tempEntity]').map(i=>{
return { name : i.name };
});
// Compare currentNames to updatedNames (regex'd to remove starting numbers and .)
// regex that excludes the numbering "#.#.#. " from updatedNames
let regex = new RegExp('[^(\d+.)+\s].*', 'g')
// update compendium
})();
Maybe something like this:
const notIndexed = `Early Life
Circumstances of Birth
Family
Region`.split('\n');
const indexed = `1.3. Early Life
1.3.1. Circumstances of Birth
1.3.2. Family
1.3.3. Region`.split('\n');
const regexp = /^((?:\d.)+) +(.+)/;
const mapping = new Map(
indexed.map((line) => {
const [, index, title] = line.match(regexp);
return [title, index];
})
);
const reIndexed = notIndexed.map(
(line) => {
if (mapping.has(line)) return `${mapping.get(line)} ${line}`;
return line;
}
).join('\n');
console.log(reIndexed);

How do I loop over a VERY LARGE 2D array without causing a major performace hit?

I am attempting to iterate over a very large 2D array in JavaScript within an ionic application, but it is majorly bogging down my app.
A little background, I created custom search component with StencilJS that provides suggestions upon keyup. You feed the component with an array of strings (search suggestions). Each individual string is tokenized word by word and split into an array and lowercase
For example, "Red-Winged Blackbird" becomes
['red','winged','blackbird']
So, tokenizing an array of strings looks like this:
[['red','winged','blackbird'],['bald','eagle'], ...]
Now, I have 10,000+ of these smaller arrays within one large array.
Then, I tokenize the search terms the user inputs upon each keyup.
Afterwards, I am comparing each tokenized search term array to each tokenized suggestion array within the larger array.
Therefore, I have 2 nested for-of loops.
In addition, I am using Levenshtein distance to compare each search term to each element of each suggestion array.
I had a couple drinks so please be patient while i stumble through this.
To start id do something like a reverse index (not very informative). Its pretty close to what you are already doing but with a couple extra steps.
First go through all your results and tokenize, stem, remove stops words, decap, coalesce, ects. It looks like you've already done this but i'm adding an example for completion.
const tokenize = (string) => {
const tokens = string
.split(' ') // just split on words, but maybe rep
.filter((v) => v.trim() !== '');
return new Set(tokens);
};
Next what we want to do is generate a map that takes a word as an key and returns us a list of document indexes the word appears in.
const documents = ['12312 taco', 'taco mmm'];
const index = {
'12312': [0],
'taco': [0, 1],
'mmm': [2]
};
I think you can see where this is taking us... We can tokenize our search term and find all documents each token belongs, to work some ranking magic, take top 5, blah blah blah, and have our results. This is typically the way google and other search giants do their searches. They spend a ton of time in precomputation so that their search engines can slice down candidates by orders of magnitude and work their magic.
Below is an example snippet. This needs a ton of work(please remember, ive been drinking) but its running through a million records in >.3ms. Im cheating a bit by generate 2 letter words and phrases, only so that i can demonstrate queries that sometimes achieve collision. This really doesn't matter since the query time is on average propionate to the number of records. Please be aware that this solution gives you back records that contain all search terms. It doesn't care about context or whatever. You will have to figure out the ranking (if your care at this point) to achieve the results you want.
const tokenize = (string) => {
const tokens = string.split(' ')
.filter((v) => v.trim() !== '');
return new Set(tokens);
};
const ri = (documents) => {
const index = new Map();
for (let i = 0; i < documents.length; i++) {
const document = documents[i];
const tokens = tokenize(document);
for (let token of tokens) {
if (!index.has(token)) {
index.set(token, new Set());
}
index.get(token).add(i);
}
}
return index;
};
const intersect = (sets) => {
const [head, ...rest] = sets;
return rest.reduce((r, set) => {
return new Set([...r].filter((n) => set.has(n)))
}, new Set(head));
};
const search = (index, query) => {
const tokens = tokenize(query);
const canidates = [];
for (let token of tokens) {
const keys = index.get(token);
if (keys != null) {
canidates.push(keys);
}
}
return intersect(canidates);
}
const word = () => Math.random().toString(36).substring(2, 4);
const terms = Array.from({ length: 255 }, () => word());
const documents = Array.from({ length: 1000000 }, () => {
const sb = [];
for (let i = 0; i < 2; i++) {
sb.push(word());
}
return sb.join(' ');
});
const index = ri(documents);
const st = performance.now();
const query = 'bb iz';
const results = search(index, query);
const et = performance.now();
console.log(query, Array.from(results).slice(0, 10).map((i) => documents[i]));
console.log(et - st);
There are some improvements you can make if you want. Like... ranking! The whole purpose of this example is to show how we can cut down 1M results to maybe a hundred or so canidates. The search function has some post filtering via intersection which probably isn't what you want you want but at this point it doesn't really matter what you do since the results are so small.

How to filter data with an array of strings matching at least one?

I'm quite new to ReactJS and work on a simple application which shows many different data (called apps). I want to implement a live free text search-filter. For that I'm using an Input Element, calling a JavaScript function if the value is changing. It's working quite good. But only for one string. If I want to filter for more words it's handled as an "AND" filter. But I want an "OR" filter.
I have the apps and filter them with a searchString. The User has to input at least three characters. If the user enters two words with three characters f.e. 'app' and 'red', I want to filter for the elements which has the words 'app' OR 'red' in the title. If min. one of the filter-strings matches, the app is shown. That's the plan.
I tried with .indexOf(), .includes() and nothing matches, even in the documentation I found nothing like an "OR" filter-search.
Here is my code, working for one string:
updateSearch(event) {
let searchString = event.target.value.toLowerCase()
let searchStringSplit = searchString.split(/(\s+)/).filter( function(e) { return e.trim().length >=3; } ); //Only words with at least 3 chars are allowed in the array
if (searchString.length >= 3) { //Check if the minimun amount of characters is fullfilled
let allApps = this.props.applications;
let apps = allApps.filter(app =>
app.title.toLowerCase().includes(searchString)
);
this.props.updateApplications(apps);
} else {
this.clearSearch()
}
}
my Input element:
<Input
id="freeTextSearch"
className="searchInput"
onChange={this.updateSearch.bind(this)}
autoComplete="off"
action={
<Button
id="freeTextSearchButton"
name="search"
icon="search"
onClick={() => this.clearSearch()}
/>
}
placeholder="Search"
/>
Thanks for the help
Yvonne
ANSWER:
Thank you 'Silvio Biasiol'. Your Solution gave me the right hint. Now I have an 'OR' filter-search matching at least one word. The function now looks like:
updateSearch(event) {
let searchString = event.target.value.toLowerCase()
let searchStringSplit = searchString.split(/(\s+)/).filter( function(e) { return e.trim().length >=3; } )
if (searchStringSplit.length >=1) { //Check if there is at least on word with tree letters
let allApps = this.props.applications
// If at least a word is matched return it!
let apps = allApps.filter(app => {
let containsAtLeastOneWord = false
searchStringSplit.forEach(searchWord => {
if (app.title.toLowerCase().includes(searchWord))
containsAtLeastOneWord = true;
})
if (containsAtLeastOneWord)
return app
}
);
this.props.updateApplications(apps)
} else { // user deletes manually every word
this.clearSearch()
}
}
Thanks at everyone
If you just want to match at least one word than it's pretty easy :)
let string = "My cool string"
let possibleStrings = [
'My cool string',
'My super cool string',
'Another',
'I am lon but sadly empty',
'Bruce Willis is better than Pokemon',
'Another but with the word string in it',
'Such string much wow cool']
// Split spaces
let searchString = string.toLowerCase().split(' ')
// Log the result, just wrap it in your react script
console.log(possibleStrings.filter(string => {
let containsAtLeastOneWord = false;
// If at least a word is matched return it!
searchString.forEach(word => {
if (string.toLowerCase().includes(word))
containsAtLeastOneWord = true;
})
if (containsAtLeastOneWord)
return string
}))
You are not using your searchStringSplit array. Using this array you could do the following:
const searchRegex = new RegExp(searchStringSplit.join("|"))
let apps = allApps.filter(app =>
searchRegex.test(app.title.toLowerCase())
);
You join your searchStringSplit array into a regex with the form term1|term2|term3... and match it aginst the title.
Another option would be to use the Array.prototype.some() function like this:
let apps = allApps.filter(app =>
searchStringSplit.some(searchString => app.title.toLowerCase().includes(searchString))
);
You fitler all your apps and for each app you check if it's title includes 'some' of the search strings.
trying to understand your code, suppose that searchString is "facebook twitter youtube"
let searchStringSplit = searchString.split(/(\s+)/).filter( function(e) { return e.trim().length >=3; } );
//searchStringSplit is like [ "facebook", "twitter", "youtube" ]
//allApps is like ["TWITTER","INSTAGRAM"]
allApps.filter(app=> searchStringSplit.includes(app.toLowerCase()))
//returning me all apps in allApps that are includes in searchStringSplit
// so return ["TWITTER"]
not sure if it's exactly what you need...if it's not please let me know so I can change the answer...

Return most recent time in series of creationTimes

Not entirely sure how I word the question but my problem is Im doing an api call that returns a bunch of messages that have a creation time, now what I want to do is only return the latest creationTime for the messages with the same date so say If I have 30 messages on the 15/03/2018 I want to grab the latest time, and discard the rest.. and do that for each set of messages with the same date
So what Ive done so far is..
using lodash I have gotten all the messages, filtered out all the ones with a certain type, and I have ordered them by creationTime so the latest being at the top and going down.. now my question is how can I then make an array of the latest times for each date??
this._activityServiceProxy.getAllItems(start, end).subscribe(result => {
// this.messages = result;
// console.log(result);
let loginUnfiltered = _.filter(result, {'title': 'LOGIN'});
let loginFiltered = _.orderBy(loginUnfiltered, {'creationTime': 'desc'});
console.log(loginFiltered);
});
any help would be appreciated!
Thanks
Use .map(...) to get at array of only the latest creationTime:
this._activityServiceProxy.getAllItems(start, end).subscribe(result => {
// this.messages = result;
// console.log(result);
let loginUnfiltered = _.filter(result, {'title': 'LOGIN'});
let loginFiltered = _.orderBy(loginUnfiltered, {'creationTime': 'desc'});
const creationTimes = loginFiltered.map(l => l.creationTime);
console.log(creationTimes);
const latestTime = creationTimes[0];
console.log(latestTime);
});
You can use Underscore's groupBy function to achieve this:
const groups = _.groupBy(loginFiltered, (login) => {
const asDate = new Date(login.creationTime);
asDate.setHours(0, 0, 0, 0);
return asDate;
});
Object.keys(groups).forEach((key) => {
console.log(groups[key][0]);
});
You group by the creationDate property but remove the time component so all days get grouped together. You then loop through the result and just take the first entry per day.
Note that this assumes your creationTime property is a string, as it came from an API. If it's already a date, you don't need the new Date line.

Categories

Resources