I'm learning lodash. Is it possible to use lodash to find a substring in an array of strings?
var myArray = [
'I like oranges and apples',
'I hate banana and grapes',
'I find mango ok',
'another array item about fruit'
]
is it possible to confirm if the word 'oranges' is in my array?
I've tried _.includes, _.some, _.indexOf but they all failed as they look at the full string, not a substring
You can easily construct an iteratee for some() using lodash's higher-order functions. For example:
_.some(myArray, _.unary(_.partialRight(_.includes, 'orange')));
The unary() function ensures that only one argument is passed to the callback. The partialRight() function is used to apply the 'orange' value as the second argument to includes(). The first argument is supplied with each iteration of some().
However, this approach won't work if case sensitivity matters. For example, 'Orange' will return false. Here's how you can handle case sensitivity:
_.some(myArray, _.method('match', /Orange/i));
The method() function creates a function that will call the given method of the first argument passed to it. Here, we're matching against a case-insensitive regular expression.
Or, if case-sensitivity doesn't matter and you simply prefer the method() approach, this works as well for ES2015:
_.some(myArray, _.method('includes', 'orange'));
Two quick ways to do it - neither uses lodash (sorry)
var found = myArray.filter(function(el){
return el.indexOf('oranges') > -1;
}).length;
if (found) { // oranges was found }
or as I mentioned in the comment:
var found = myArray.join(',').indexOf('oranges') > -1;
if (found) { // oranges was found }
You can do this using lodash, but it's also very doable using native javascript methods:
function stringArrayContains(array, str) {
function contains(el) {
return (el.indexOf(str) !== -1) ? true : false;
}
return array.some(contains);
}
Testing the above function:
var a = ['hello', 'there'];
var b = ['see', 'ya', 'later'];
stringArrayContains(a, 'ell') // true
stringArrayContains(a, 'what') // false
stringArrayContains(b, 'later') // true
stringArrayContains(b, 'hello') // false
Array.prototype.some applies a function you define to every element of an array. This function (named contains in our case) must return true or false. While iterating through the array elements, if any of the elements returns true, the some method returns true.
Personally, I think in general that if you can use native JS methods for simple functions, it's preferable to loading an library just to do the same thing. Lodash absolutely does have performance benefits, but they aren't necessarily realized unless you're processing large amounts of data. Just my two cents.
Cheers!
The best way is to define a function to check the inclusion of a substring.
var contains = _.curry(function (substring, source) {
return source.indexOf(substring) !== -1;
});
I use _.curry here to get a curried function, which can be partially applied then.
_.some(myArray, contains('item'));
You can also find a substring in a joined string.
contains('item', _.join(myArray))
UPD:
I have not noticed that lodash already has a function to find value in a collection.
The function _.includes is quite the same to what I defined above. However, as everything in lodash, it uses the different order for arguments. In my example, I put a source as the latest argument for a curried function which makes my function useful for point-free style programming when lodash waits for the source as a first argument of the same function.
Check the Brian Lonsdorf's talk on this matter https://www.youtube.com/watch?v=m3svKOdZijA
Also take a chance to look into ramda. This library provides a better way for practical functional programming in JavaScript.
I ran into this Question / Answer thread while trying to figure out how to match a substring against each String in an Array and REMOVE any array item that contains that substring.
While the above answers put me on track, and while this doesn't specifically answer the original question, this thread DOES appear first in the google search when you are trying to figure out how to accomplish the above removal of an array item so I figured I would post an answer here.
I ended up finding a way to use Lodash's _.remove function to remove matching array strings as follows:
// The String (SubString) we want to match against array (for dropping purposes)
var searchSubString = "whatever"
// Remove all array items that contain the text "whatever"
_.remove(my_array, function(searchSubString) {
return n.indexOf(searchSubString) !== -1;
});
Basically indexOf is matching against the position of the substring within the string, if the substring is not found it will return -1, when indexOf returns a number other than -1 (the number is the SubString position in number of characters within the Array string).
Lodash removes that Array item via array mutation and the newly modified array can be accessed by the same name.
_.some(myArray, function(str){
return _.includes(str, 'orange')
})
let str1 = 'la rivière et le lapin sont dans le près';
let str2 = 'product of cooking class';
let str3 = 'another sentence to /^[analyse]/i with weird!$" chars#';
_.some(_.map(['rabbit','champs'], w => str1.includes(w)), Boolean), // false
_.some(_.map(['cook'], w => str2.includes(w)), Boolean), // true
_.some(_.map(['analyse'], w => str3.includes(w)), Boolean), // true
Related
I am new to JS and was trying to learn how to properly work with indexOf in JS, that is, if you look at the code below:
var sandwiches = ['turkey', 'ham', 'turkey', 'tuna', 'pb&j', 'ham', 'turkey', 'tuna'];
var deduped = sandwiches.filter(function (sandwich, index) {
return sandwiches.indexOf(sandwich) === index;
});
// Logs ["turkey", "ham", "tuna", "pb&j"]
console.log(deduped);
I am trying to remove duplicates but wanted to ask two questions. Firstly, in here return sandwiches.indexOf(sandwich) === index; why we need to use "== index;". Secondly, since indexOf returns index like 0, 1 or 2 ... then why when we console.log(deduped) we get array of names instead of array of indexes. Hope you got my points
You use a method of Javascript Array that is filter, this method take a function that returns a boolean.
The function filter returns a new Array based on the function passed applied to each entry.
If the function return true, then the entry is included in the new Array, otherwise is discarded.
As the functions check the indexOf an entry to be the current index is true for the first occurrency of the entry.
All the duplications will fail the expression as they are not the first index found by indexOf, so they are discarded.
since the logic is to remove the duplicates from the array,
in your example, you have "turkey" as duplicates.
the "turkey" exists in position 0,2,6
so whenever you call indexOf("turkey") always returns 0 because the indexOf function returns the first occurrence of a substring.
so for the elements in position 2 & 6 the condition fails. then it won't return that element.
That is how the filter works in javascript. it evaluates the condition and returns true or false that indicates whether an element to be included in the new array or not, in your example the condition is return sandwiches.indexOf(sandwich) === index;
Perhaps the basic logic is easier to see at a glance if you use arrow notation:
const deduped = myArray => myArray.filter((x, i) => myArray.indexOf(x) === i);
The key point is that indexOf returns the index of the first occurrence of x. For that occurrence the result of the comparison will be true hence the element will be retained by the filter. For any subsequent occurrence the comparison will be false and the filter will reject it.
Difference between === (identity) and == (equality): if type of compared values are different then === will return false, while == will try to convert values to the same type. So, in cases where you compare some values with known types it is better to use ===. (http://www.c-point.com/javascript_tutorial/jsgrpComparison.htm)
You get as result an array of names instead of array of indexes because Array.filter do not change the values, but only filter them. The filter function in your case is return sandwiches.indexOf(sandwich) === index; which return true or false. If you want get the indexes of your items after deduplication, then use map after filter:
a.filter(...).map(function(item, idx) {return idx;})
#Dikens, indexOf gives the index of the element if found. And if the element is not found then it returns -1.
In your case you are filtering the array and storing the values in the deduped. That's why it is showing an array.
If you console the indexOf in the filter function then it will log the index of the element.
For example :
var deduped = sandwiches.filter(function (sandwich, index) {
console.log(sandwiches.indexOf(sandwich));
return sandwiches.indexOf(sandwich) === index;
});
This is a working javascript code. However, it looks redundant to me. Is there any way to clean this up?
let text = 'Some search text';
const searchMatch =
entry.title.toLowerCase().includes(text.toLowerCase()) ||
entry.description.toLowerCase().includes(text.toLowerCase()) ||
entry.keywords.toLowerCase().includes(text.toLowerCase());
return searchMatch;
You could do something like this:
const text = 'Some search text'.toLowerCase();
return [entry.title, entry.description, entry.keywords].some(s => s.toLowerCase().includes(text));
You might use an array and a .some test instead:
const textLower = text.toLowerCase();
return ['title', 'description', 'keywords']
.map(prop => entry[prop].toLowerCase())
.some(s => s.includes(textLower));
If, by chance, entry contains only those properties, then you could use Object.values instead:
return Object.values(entry)
.map(s => s.toLowerCase())
.some(s => s.includes(textLower));
You could just use a one-line return statement involving an array composed of entry.description, entry.keywords and entry.title, and then using Array.prototype.some() to return a Boolean (true/false) value depending on whether any of the tests pass:
return [entry.description, entry.keywords, entry.title].some(string => string.toLowerCase().includes('Some search text'.toLowerCase());
Here's essentially a breakdown of each part:
[entry.description, entry.keywords, entry.title].some(...)
What this does is makes an anonymous array composed of entry.description, entry.keywords, and entry.title (the order does not matter) and iterates through it with the Array.prototype.some() method. According to the MDN page, .some():
The some() method tests whether at least one element in the array passes the test implemented by the provided function.
Essentially iterates through each element, and depending on the callback from the provided function, and provides a Boolean value (true if at least one element in the array passes the test, false if no elements pass the test).
string => string.toLowerCase().includes('Some search text'.toLowerCase())
This is the anonymous function contained within the .some() method, and it takes a single parameter string. Then it returns a Boolean value, depending on the outcome of the .includes() method. The .includes() method returns another Boolean value, depending on whether the lowercased string contains the lowercased 'Some search text'. It's a mouthful, but in a nutshell, the line of code above reads:
If string in lowercased form includes 'Some search text' in lowercased form, return true - otherwise, return false.
Hopefully this helps you!
So I have an interesting issue I am not sure how to follow, I need to use lodash to search two arrays in an object, looking to see if x already exists, lets look at a console out put:
There are two keys I am interested in: questChains and singleQuests, I want to write two seperate functions using lodash to say: find me id x in the array of objects where questChains questChainID is equal to x.
The second function would say: Find me a quest in the array of objects where singleQuests questTitle equals y
So if we give an example, you can see that questChainId is a 1 so if I pass in a 1 to said function I would get true back, I don't actually care about the object its self, else I would get false.
The same goes for singleQuests, If I pass in hello (case insensitive) I would get back true because there is a quest with the questTitle of 'Hello'. Again I don't care about the object coming back.
The way I would write this is something like:
_.find(theArray, function(questObject){
_.find(questObject.questChains, function(questChain){
if (questChain.questChainId === 1) {
return true;
}
});
});
This is just for the quest chain id comparison. This seems super messy, why? Because I am nesting lodash find, I am also nesting if. It gets a bit difficult to read.
Is this the only way to do this? or is there a better way?
Yeah it can be expressed more simply.
Try something like this:
var exampleArray = [{
questChains: [{
questChainId: 1,
name: 'foo'
}, {
questChainId: 2,
name: 'bar'
}],
singleQuests: [{
questTitle: 'hello world'
}]
}, {
questChains: [{
questChainId: 77,
name: 'kappa'
}]
}];
var result = _.chain(exampleArray)
.pluck('questChains')
.flatten()
.findWhere({ questChainId: 2 })
.value();
console.log('result', result);
<script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/3.5.0/lodash.min.js"></script>
Using chain and value is optional. They just let you chain together multiple lodash methods more succinctly.
pluck grabs a property from each object in an array and returns a new array of those properties.
flatten takes a nested array structure and flattens it into flat array structure.
findWhere will return the first element which matches the property name/value provided.
Combining all of these results in us fetching all questChain arrays from exampleArray, flattening them into a single array which can be more easily iterated upon, and then performing a search for the desired value.
Case-insensitive matching will be slightly more challenging. You'd either need to either replace findWhere with a method which accepts a matching function (i.e. find) or sanitize your input ahead of time. Either way you're going to need to call toLower, toUpper, or some variant on your names to standardize your search.
Here is the expression
_([1,2],[1,3],[1,2]).uniq().value()
The evaluated value is
[[1,2],[1,3],[1,2]]
Though what I expect is [[1,2],[1,3]]..
Does anyone have ideas about this?
Underscore uses strict equality on the list if you don't supply a predicate. So in your example underscore will be checking that a value is in the result array by comparing 2 arrays e.g. [1,2] === [1,2] which will always be false as they are two different arrays.
One quick 'n' dirty solution would be:
var result = _.uniq(data, function (a) {
return a.join(',');
});
If you want to compare uniqueness using isEqual(), which is a fairly versatile approach, I would do something like this:
_.reduce(coll, function(result, item) {
if (!_.any(result, _.ary(_.partial(_.isEqual, item), 1))) {
result.push(item);
}
return result;
}, []);
Using reduce(), you start off with an empty array. Then any() tells you if the current item is already in the results array. If not, it adds it.
Using toString() to compare compare complex values can lead to inconsistencies. However, most of the time it's sufficient - use this approach if you end up with unexpected results.
For searching elements of one array inside the other, one may use the indexOf() on the target of search and run a loop on the other array elements and in each step check existence. This is trivial and my knowledge on Javascript is too. Could any one please suggest a more efficient way? Maybe even a built-in method of the language could help? Although I couldn't hit to such a method using google.
You can use Array.filter() internally and implement a function on Array's prototype which returns elements which are common to both.
Array.prototype.common = function(a) {
return this.filter(function(i) {
return a.indexOf(i) >= 0;
});
};
alert([1,2,3,4,5].common([4,5,6])); // "4, 5"
Again as you mention in your post, this logic also works by taking each element and checking whether it exists in the other.
A hair more efficient way is convert one of the arrays into a hash table and then loop through the second one, checking the presence of elements at O(1) time:
a = [1,2,3,4,5]
b = [1,7,3,8,5]
map = {}
a.forEach(function(x) { map[x] = 1 })
intersection = b.filter(function(x) { return map[x] === 1 })
document.write(JSON.stringify(intersection))
This only works if elements in question are primitives. For arrays of objects you have to resort to the indexOf method.
ES6 ("Harmony") does support Set, but strangely not set operations (union, intersection etc), so these should be coded by hand:
// Firefox only
a = [1,2,3,4,5]
b = [1,7,3,8,5]
sa = new Set(a)
sb = new Set(b)
sa.forEach(function(x) {
if (!sb.has(x))
sa.delete(x);
});
document.write(uneval([...sa]))
JavaScript's arrays don't have a method that does intersections. Various libraries provide methods to do it (by looping the array as you describe), including Underscore and PrototypeJS (and others, I'm sure).