How to find similarities between a text string and an array? - javascript

I have a string array, which I want to compare with a text string, if it finds similarities I want to be able to mark in black the similarity found within the string
example:
context.body: 'G-22-6-04136 - PatientName1'
newBody: ['G-22-6-04136 - PatientName1' , 'G-22-6-04137 - PatientName2']
When finding the similarity between the two the result would be something like this
newBody: [**'G-22-6-04136 - PatientName1'** , 'G-22-6-04137 - PatientName2']
How could I do it? Is this possible to do? In advance thank you very much for the help
const totalSize: number = this.getSizeFromAttachments(attachments);
const chunkSplit = Math.floor(isNaN(totalSize) ? 1 : totalSize / this.LIMIT_ATTACHMENTS) + 1;
const attachmentsChunk: any[][] = _.chunk(attachments, chunkSplit);
if ((totalSize > this.LIMIT_ATTACHMENTS) && attachmentsChunk?.length >= 1) {
const result = attachment.map(element => this.getCantidad.find(y => element.content === y.content))
const aux = this.namePatient
const ans = []
result.forEach(ele => {
const expected_key = ele["correlative_soli"];
if (aux[expected_key]) {
const newItem = { ...ele };
newItem["name_Patient"] = aux[expected_key]
newItem["fileName"] = `${expected_key}${aux[expected_key] ? ' - ' + aux[expected_key] : null}\n`.replace(/\n/g, '<br />')
ans.push(newItem)
}
});
let newBody: any;
const resultFilter = attachment.map(element => element.content);
const newArr = [];
ans.filter(element => {
if (resultFilter.includes(element.content)) {
newArr.push({
fileNameC: element.fileName
})
}
})
newBody = `• ${newArr.map(element => element.fileNameC)}`;
const date = this.cleanDateSolic;
const amoung= attachmentsChunk?.length;
const getCurrent = `(${index}/${attachmentsChunk?.length})`
const header = `${this.cleanDateSolic} pront ${attachmentsChunk?.length} mail. This is the (${index}/${attachmentsChunk?.length})`;
console.log('body', context.body);
// context.body is the body I receive of frontend like a string
const newContext = {
newDate: date,
amoung: amoung,
getCurrent: getCurrent,
newBody: newBody,
...context
}
return this.prepareEmail({
to: to,
subject: ` ${subject} (Correo ${index}/${attachmentsChunk?.length})`,
template: template,
context: newContext,
}, attachment);
}

I have a string array, which I want to compare with a text string, if it finds similarities I want to be able to mark in black the similarity found within the string
First of all the text will be in black by default, I think you are talking about to make that bold if matched. But again it depends - If you want to render that in HTML then instead of ** you can use <strong>.
Live Demo :
const textString = 'G-22-6-04136 - PatientName1';
let arr = ['G-22-6-04136 - PatientName1', 'G-22-6-04137 - PatientName2'];
const res = arr.map(str => str === textString ? `<strong>${str}<strong>` : str);
document.getElementById('result').innerHTML = res[0];
<div id="result">
</div>

Based on your example, it looks like the string needs to be an exact match, is that correct?
Also, "mark it in bold" is going to depend on what you're using to render this, but if you just want to wrap the string in ** or <b> or whatever, you could do something like this:
newBody.map(str => str === context.body ? `**${str}**` : str);

You can just see if string exists in the array by doing
if arr.includes(desiredStr) //make text bold

Related

Add string content to array of dates using map

How can I add strings that are separated using colon present in two input fields with the format hh:mm:ss to a range of dates selected that is stored something like this, ["11.10.2022 23:43:24","28.10.2022 23:43:24"] . I am using dayjs for date related actions. Help will be appreciated
Code that I tried
const from = "12:49:55";
const to = "10:45:55";
const range = ["11.10.2022 23:43:24","28.10.2022 23:43:24"];
const timeAdded = range?.map(
(value: Dayjs, index: number) => {
if (index === 0) {
return dayjs(from, "hour").add(from, "minute").add(from, "second");
}
if (index === 1) {
return dayjs(to, "hour").add(to, "minute").add(to, "second");
}
}
);
How can I add both the values in one go. Output should look like
["11.10.2022 12:49:55","28.10.2022 10:45:55"]
You can do it without days.js:
const from="12:49:55", to="10:45:55";
const times=[from,to];
const range = ["11.10.2022 23:43:24","28.10.2022 23:43:24"];
const timeAdded = range.map((r,i)=>r.replace(/ .*/," "+times[i]));
console.log(timeAdded);

How to filter values of an array inside an array?

I will describe the situation for more clarity. I have an array
like this
animalsArray = {
animalID : number,
animalName: string,
animalDescription: string,
animalChild: animalsArray[]
}
Now I have to filter these animals using animalName from user input by textfield. Some animals can have n number of animalChild or non at all or animalChild can have another animalChild inside it.
I already have a code like this
public animalsArray:animalsArray[]
this.filteredanimalsArray.next(
this.animalsArray.filter(item => (item.animalName.toLowerCase().indexOf(search) > -1))
);
To filter the main animalsArray and it works fine but the user input doesn't go through the child arrays.
How can I solve this problem? Thanks in advance
Maybe you could use recursivity like this :
function filterAnimals(animals, name) {
return animals.filter(animal => {
const matching = animal.animalName.toLowerCase() === name.toLowerCase();
const hasChildMatch = Array.isArray(animal.animalChild) && filterAnimals(animal.animalChild, name).length > 0;
return matching || hasChildMatch;
});
}
const search = 'leon';
const filterdAnimals = filterAnimals(animalsArray, search);

Typescript react -Object is possibly nil

I'm super new to react and I've been asked to help out a react typescript website. At the moment my matches const is returning an Object is possibly 'null'. I've tried making my match constant null but for the life of me I cannot figure out how to work around this not to get this error. Any help would be much appreciated.
case "text":
return (
<div>
{" "}
{note.text.map((content: string, idx: number) => {
const result = [];
const matches = content.match(
/(.*?)(<a href=")?(https?|www)((".*?>.*?<\/a>)|[^\s>]?)*(.*?)/gi
);
if (!!matches) {
matches.forEach((match) => {
let link;
if (/href="/i.test(match)) {
const url = match
.match(/<a href="(.*?)(?=">)/i)[0]
.replace('<a href="', "");
const linkText = match
.match(/(?:<a href=".*?">)(.*)(?=<\/a>)/)[0]
.replace(/(?:<a href=".*?">)/i, "");
link = <a href={url}>{linkText}</a>;
} else {
const url = match.match(/(https?|www)[^\s]*/gi).join("");
link = <a href={url}>{url}</a>;
}
const splitter = match.match(
/(<a href=")?(https?|www)[^\s]*(.*<\/a>)?/gi
)[0];
const paredPlainText = match.split(new RegExp(splitter));
result.push(paredPlainText[0]);
result.push(link);
result.push(paredPlainText[1]);
});
} else {
result.push(content);
}
return <p>{result}</p>;
})}
</div>
);
I'm not fully sure I understand your problem, the description sounds a bit confuse, but I believe you mean that line:
if (!!matches) {
right?
This converts the result of the match call (which can be an array with results or null) to a bool. Just use
if (matches) {
instead, which checks for a truthy expression (null represents a falsy result).
If this answer is not what you are after then please add more description to your question.
Thanks everyone for the response! This was my answer using non-null assertion operator ! after the .match as below
case "text":
return (
<div>
{" "}
{note.text.map((content: string, idx: number) => {
const result = [];
const matches = content.match(
/(.*?)(<a href=")?(https?|www)((".*?>.*?<\/a>)|[^\s>]?)*(.*?)/gi
);
if (!!matches) {
matches.forEach((match) => {
let link;
if (/href="/i.test(match)) {
const url = match
.match(/<a href="(.*?)(?=">)/i)![0]
.replace('<a href="', "");
const linkText = match
.match(/(?:<a href=".*?">)(.*)(?=<\/a>)/)![0]
.replace(/(?:<a href=".*?">)/i, "");
link = <a href={url}>{linkText}</a>;
} else {
const url = match.match(/(https?|www)[^\s]*/gi)!.join("");
link = <a href={url}>{url}</a>;
}
const splitter = match.match(
/(<a href=")?(https?|www)[^\s]*(.*<\/a>)?/gi
)![0];
const paredPlainText = match.split(new RegExp(splitter));
result.push(paredPlainText[0]);
result.push(link);
result.push(paredPlainText[1]);
});
} else {
result.push(content)
}
console.log(result);
return <p>{result}</p>;
})}
</div>
);

React: Highlight text between two indexes

So for every prediction the google places autocomplete API also returns matched substrings for each one.
Input: San 68
Prediction: San Francisco 68
Matched substrings: [{ offset: 0, length: 3 }, { offset: 15, length: 2 }]
Expectation: San Francisco 68
My goal is to highlight parts of the prediction using the matched substrings. Now there is a few challenges. I could use the replace function and replace every substring with <b>str</b>, but it returns a string which means unless I use dangerouslySetInnerHTML this method doesn't work.
I also don't think there is a way to replace multiple substrings. I tried to use the reduce function but after the first loop it wouldn't really work because the indexes would be wrong.
const highlight = (text, matched_substrings) => {
return matched_substrings.reduce((acc, cur) => {
return acc.replace(
acc.substring(cur.offset, cur.length),
(str) => `<b>${str}</b>`
)
}, text)
}
So is there a way to do this? I think React makes this more complicated.
probably not best solution, but definitely working one :)
Prerequisite is, that matched_substrigs array has to be sorted, by offsets
export const highlightText = (text, matched_substring, start, end) => {
const highlightTextStart = matched_substring.offset;
const highlightTextEnd = highlightTextStart + matched_substring.length;
// The part before matched text
const beforeText = text.slice(start, highlightTextStart);
// Matched text
const highlightedText = text.slice(highlightTextStart, highlightTextEnd);
// Part after matched text
// Till the end of text, or till next matched text
const afterText = text.slice(highlightTextEnd, end || text.length);
// Return in array of JSX elements
return [beforeText, <strong>{highlightedText}</strong>, afterText];
};
export const highlight = (text, matched_substrings) => {
const returnText = [];
// Just iterate through all matches
for (let i = 0; i < matched_substrings.length; i++) {
const startOfNext = matched_substrings[i + 1]?.offset;
if (i === 0) { // If its first match, we start from first character => start at index 0
returnText.push(highlightText(text, matched_substrings[i], 0, startOfNext))
} else { // If its not first match, we start from match.offset
returnText.push(highlightText(text, matched_substrings[i], matched_substrings[i].offset, startOfNext))
}
}
return returnText.map((text, i) => <React.Fragment key={i}>{text}</React.Fragment>)
};
here is a solution if you don't mind using dangerouslySetInnerHTML
const Highlight = ({ text, substrings }) => {
const html = substrings.reduce((acc, cur, idx) => {
return acc.replace(
new RegExp(
'(?<=^.{' + (cur.offset + idx * 17) + '})(.{' + cur.length + '})',
),
(str) => `<strong>${str}</strong>`,
)
}, text)
return <span dangerouslySetInnerHTML={{ __html: html }} />
}
use it like this
<Highlight text={...} substrings={...} />

Matching multiple substrings to a string

I have an application where the user types in text, then this text is sent to the server and it returns an array of words that contain this text.
But as you can see the problem starts when there are more then 1 match. Here's my current code:
state.input !== '' && vocabularyItems && (vocabularyItems.map((vocabularyItem, index) => {
const regex = new RegExp(input, 'gi');
const results = vocabularyItem.matchAll(regex);
const tmp = [];
console.log(vocabularyItem);
for (const match of results) {
console.log(match);
let currentLoop = vocabularyItem.slice(0, match.index);
currentLoop += '<strong className="tt-highlight">';
currentLoop += vocabularyItem.slice(match.index, match.index + input.length);
currentLoop += '</strong>';
currentLoop += vocabularyItem.slice(match.index + input.length, vocabularyItem.length);
tmp.push(currentLoop);
}
console.table(tmp);
return (
<div
id={index}
className={`override-strong tt-suggestion tt-selectable ${cursor === index && 'tt-cursor'}`}
onMouseDown={handleClick}
key={index}
dangerouslySetInnerHTML={{ __html: tmp }}
/>
);
}))
and here are some examples in HTML code
1.
<strong className="tt-highlight">En</strong>kelkind
2.
<strong className="tt-highlight">En</strong>gagement
Engagem<strong className="tt-highlight">en</strong>t
as you can see, it works when there is only one occurence, but duplicates the word when more then one match is present. How can I end up with just something like
<strong>en</strong>gagem<strong>en</strong>t?
engagement?
I forgot to add that I need the case preserved
First off, I would recommend to use, something, like:
const results = vocabularyItems.filter(word => word.toLowerCase().includes(input.toLowerCase()))
for case insensitive vocabulary lookup.
Next, I would highlight the match in a bit different way. I'd split suggested option into parts (matching search input and non-matching ones) , then style those respectively:
const parts = suggestion.split(new RegExp(`(?=${match})|(?<=${match})`, 'gi'))
...
parts.map((part,key) => {
const style = part.toLowerCase() == match.toLowerCase() ? 'bold' : 'normal'
return <span style={{fontWeight:style}} {...{key}}>{part}</span>
})
I think it's safe enough to assume that you build autocomplete search input, thus you might find of use the quick demo below (excluding all the styling):
//dependencies
const { render } = ReactDOM,
{ useState } = React
//vocabulary
const vocabulary = ['engagement', 'Bentley', 'English', 'seven', 'Engagement']
//suggested option component
const SuggestedOption = ({suggestion, match}) => {
const parts = suggestion.split(new RegExp(`(?=${match})|(?<=${match})`, 'gi'))
return (
<div>
{
parts.map((part,key) => {
const style = part.toLowerCase() == match.toLowerCase() ? 'bold' : 'normal'
return <span style={{fontWeight:style}} {...{key}}>{part}</span>
})
}
</div>
)
}
//autocomplete component
const SearchBar = () => {
const [suggestions, setSuggestions] = useState([]),
[inputValue, setInputValue] = useState(''),
onInput = input => {
setInputValue(input)
setSuggestions(vocabulary.filter(word => input.length && word.toLowerCase().includes(input.toLowerCase())))
}
return (
<div>
<input onKeyUp={e => onInput(e.target.value)} />
<div >
{
suggestions.map((suggestion,key) => <SuggestedOption {...{key,suggestion,match:inputValue}} />)
}
</div>
</div>
)
}
render(
<SearchBar />,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>
Here's a way to do it with string.replace:
const list = [
'end',
'England',
'engagement'
]
const boldify = (search) => {
return (string) =>
string.replace(new RegExp(search, 'gi'), '<strong>$&</strong>')
}
document.body.innerHTML = list.map(boldify('en')).join('<br>')
EDIT: After some times thinking, and seeing the accepted answer, I wanted to push the vanilla JS version to have something more complete. And moreover, the React version seemed laggy to me, this one is faster!
Faster because:
it uses vanilla JavaScript
it uses correct dom manipulation (no innerHTML)
'use strict'
const list = [
'end',
'England',
'engagement',
'Ken Bogard',
'Venom',
'Engagement'
]
const boldify = (search) => {
return (string) => {
const div = document.createElement('div')
const parts = string.split(new RegExp(`(?=${search})|(?<=${search})`, 'gi'))
div.append(
...parts.map(part => {
if (part.toUpperCase() !== search.toUpperCase()) return part
const strong = document.createElement('strong')
strong.innerText = part
return strong
})
)
return div
}
}
const contains = (search) => {
search = search.toUpperCase()
return (string) => string.toUpperCase().indexOf(search) >= 0
}
const refreshSuggestions = () => {
// Fast removal of children.
while (suggestions.firstChild) suggestions.removeChild(suggestions.firstChild)
// nothing to do
if (searchInput.value.length == 0) return
const newSuggestions =
list.filter(contains(searchInput.value))
.map(boldify(searchInput.value))
suggestions.append(...newSuggestions)
}
searchInput.addEventListener('input', refreshSuggestions)
<input id="searchInput" list="suggestions">
<div id="suggestions">
</div>
EDIT - Yevgen's answer is much nicer than this one.
A simple loop can achieve this if i understand correctly what you want:
var array = ["end","engagement","Engagement","england","enough","not this","or this"];
function filterArray(array, id) {
var returnArray = [];
for (var i = 0; i < array.length; i++) {
value = array[i];
if (value.includes(id)) {
returnArray.push(value);
}
}
return returnArray;
}
var filteredArray = filterArray(array,"en");
console.log(filteredArray);
If you wanted to ensure duplicates (where case on Engagement causes a duplicate) you could set the string to lowercase before pushing to the array, and check the array for the existence of the string before pushing again.

Categories

Resources