Assume I have the following array:
const data = [{
label: 'Östra Halmgatan, Stockholm',
value: 'Östra Halmgatan, Stockholm'
},
{
label: 'Västra Vägen, Stockholm',
value: 'Västra Vägen, Stockholm'
},
{
label: 'Cykelvägen, Göteborg',
value: 'Cykelvägen, Göteborg'
},
{
label: 'Servicevägen, Köpenhamn',
value: 'Servicevägen, Köpenhamn'
}
]}
I want to have a search where the user can search for an item with fuzzy search.
The first step I took was to implement a filter levenshtein algorithm that checks the distance. I filter out all the results with a distance above 2.
The first problem was that when the users start typing, the distance will be way off. If they write ’Serv’ it gives a distance of 19, but it should obviously show the item with Servicevägen, Köpenhamn. I fixed this by only using a substring (from 0 to userInput.length) for the labels instead. This leads to the correct functionality for most cases. So if they write Vöstra (with a typo) it will only show Östra Halmgatan, Stockholm and Västra Vägen, Stockholm (since the distance for those two would be 2 or less).
However, this leads to another issue. If someone would write Stockholm, the first two items should of course be displayed. I fixed this by adding a fix for the filter function, which also checks if the user input value is included in the label string (with all strings set to lowercase).
This works surprisingly well, but I do still have one issue I was wondering if someone can help me figure out.
If someone would be on a non-swedish keyboard for example, I would need to show Göteborg if they were to enter Goteborg. The include doesn't take into account a typo (or a distance) when using the include, only from the start.
Basically, I was wondering if there was a way to write an includes function for strings that takes into account a distance? That would solve all the use cases for my case. Then I could use that function only since Vöstra would return true for the first two (given that the distance is set to 2 or lower). It would also work for Goteborg and Stockholm.
What I'm thinking is you write a function in the style of
String.prototype.includesWithDistance(value: string, maxDistance: number) {
//... how would you approach this?
return substring (with a distance) is included
}
I see a lot of possible cases where this could be used so any ideas are appreciated.
You might want to try out Smith-Waterman Gotoh or Jaro-Winkler algorithm for this kind of calculation. It is more precise and capable of handling typo.
You can test your comparison here:
Test Similarity
Related
I'm making a bot for a gaming chatroom with some friends, but I've hit an impasse. Is there a reliable way to get numbers from inside a string of text that won't completely break an inexperienced script kiddy's brain? Here's the best I've been able to come up with so far, variables simplified slightly for illustration's sake:
var k = [0];
function dieRoll(m,n) {
for(i = 0; i < m; i++) {
k[i] = Math.floor(Math.random()*n)+1;
}
}
var m = text[5];
var n = text[7];
if (text === 'roll '+m+'d'+n) {
dieRoll(m,n)
console.log(k);
}
The biggest problem as-is is that it's limited to single-digit input.
EDIT: Looping through the text looking for integers is exactly the kind of thing I'm looking for. I don't have much experience with programming, so I probably tend to end up with overly complicated and confusing messes of spaghetti code that would embarrass anyone remotely professional. As for the format of the input I'm looking for, "roll [number of dice]d[highest number on the dice]". For anyone who doesn't know, it's the notation most tabletop rpgs use. For example, "roll 2d6" for two normal six-sided dice.
EDIT: It's not that I'm necessarily against regex, I just want to be able to understand what's going on, so that if and when I need to edit or reuse the code it I can do so without going completely insane.
EDIT: Thank you all very much! split() seems to be exactly what I was looking for! It'll probably take some trial and error, but I think I'll be able to get her working how she's supposed to this weekend (Yes I call my bots 'she').
Basically, you need to look at the format of the input you're using, and identify certain facts about it. Here are the assumptions I've taken based on your question.
1) The "roll" command comes first followed by a space, and
2) After the command, you are provided with dice information in the form xdy.
Here's something that should work given those constraints:
function getRollParameters(inputCommand) {
var inputWords = inputCommand.split(' '); //Split our input around the space, into an array containing the text before and after the space as two separate elements.
var diceInfo = inputWords[1]; //Store the second element as "diceInfo"
var diceDetails = diceInfo.split('d'); //Split this diceInfo into two sections, that before and after the "d" - ie, the number of dice, and the sides.
//assign each part of the dicedetails to an appropriate variable
var dice = diceDetails[0];
var sides = diceDetails[1];
//return our two pieces of information as a convenient object.
return {
"dice": dice,
"sides": sides
};
}
//a couple of demonstrations
console.log(getRollParameters("roll 5d8"));
console.log(getRollParameters("roll 126d2"));
Effectively, we're first splitting the string into the "command", and the "arguments" - the information we want. Then, we split our arguments up using the "d" as a midpoint. That gives us two numbers - the one before and the one after the d. Then we assign those values to variables, and can use them however we like.
This obviously won't deal with more creative or flexible inputs, and isn't tested beyond the examples shown but it should be a decent starting point.
Let's say I have a value somewhere in a list items, whose value is a range from 3-10.
Then let's say I search using a range from say 5-15.
Since the lower end of the search range (5) falls within the range of the entry in the list (3-10), then it should match.
To do this I have to check if either range value in the search falls between the range values of the entry, and vice-verse.
While I have a working function for this, I was wondering if there is a common pattern or built-in way to do this kind of "range matrix" filtering in JavaScript. I don't even know if there is some actual nomenclature for this sort of thing.
Expected behavior: https://repl.it/Jz6c/0
Perhaps this is more of a Code Review question than a Stack Overflow question, but since we're here...
Based on your repl.it demo, it looks like you are asking if there is a simpler way to write this code:
var matchesRange = function(min, max, value) {
return value >= min && value <= max;
};
var matchesRangeMatrix = function(searchRange, targetRange) {
return matchesRange(searchRange.min, searchRange.max, targetRange.min) ||
matchesRange(searchRange.min, searchRange.max, targetRange.max) ||
matchesRange(targetRange.min, targetRange.max, searchRange.min) ||
matchesRange(targetRange.min, targetRange.max, searchRange.max);
};
where you call matchesRangeMatrix() with two object arguments, each of which has a min and max property.
This code makes a total of eight comparisons (four calls to matchesRange with two comparisons each).
You can do the whole thing with only two comparisons. Let's take out the matrix nomenclature, since that seems to make it sound more complicated than it is. Instead, how about a function called rangesOverlap():
function rangesOverlap( one, two ) {
return one.min < two.max && two.min < one.max;
}
That's all you need! Try this updated version of your repl.it and compare the results with your original.
If you're wondering how something so simple could work, I invite you to read this Hacker News discussion where I and a few other people debated this very topic. (I'm "Stratoscope" over there, but in particular look for a comment by "barrkel" about a third of the way down the page that lists a truth table for this problem.)
The context of that discussion was whether two appointments conflict or not. For example, appointments from 1-2pm and 2-3pm would not conflict even though the first one ends at the same time the second one begins. If your definition of overlapping ranges is different, so 1-2 and 2-3 would be considered to overlap, you should be able to do this by using <= instead of <:
function rangesOverlap( one, two ) {
return one.min <= two.max && two.min <= one.max;
}
But fair warning, I have not tested this version of the code.
Note that this isn't anything specific to JavaScript. The same question and the same solutions would apply to pretty much any programming language.
I am trying to figure out the best logic for grouping parameters within a certain tolerance. It's easier to explain with an example...
Task1: parameter1=140
Task2: parameter1=137
Task3: parameter1=142
Task4: parameter1=139
Task5: parameter1=143
If I want to group tasks if they are within 2 of each other, I think I need to do several passes. For the example, the desired result would be this:
Task4 covers Task1, Task2, and Task4
Task3 covers Task3 and Task5
There are multiple possibilities because Task1 could also cover 3 and 4 but then 2 and 5 would be two additional tasks that are by themselves. Basically, I would want the fewest number of tasks that are within 2 of each other.
I am currently trying to do this in excel VBA but I may port the code to php later. I really just don't know where to start because it seems pretty complex.
You'l need a clustering algorithm i'd assume. Consider the following parameters-
Task1: parameter1=140
Task2: parameter1=142
Task3: parameter1=144
Task4: parameter1=146
Task5: parameter1=148
Depending on your logic the clustering will get weird here. If you simply check each number for numbers near it all of these will be clustered. But does 140 and 148 deserve to be in the same cluster? Try kmeans clustering. There will be some gray area but the result will be relatively accurate.
http://en.wikipedia.org/wiki/K-means_clustering
You can group tasks in a single pass if you decide the group boundaries before looking at the tasks. Here's a simple example using buckets of width 4, based on your goal to group tasks within +/-2 of each other:
Dim bucket As Integer
For Each parameter In parameters
bucket = Round(parameter / 4, 0)
' ... do something now that you know what bucket the task is in
Next parameter
If the groups provided by fixed buckets don't fit the data closely enough for your needs, you will need to use an algorithm that makes multiple passes. Since the data in your example is one-dimensional, you can (and should!) use simpler techniques than k-means clustering.
A good next place to look might be Literate Jenks Natural Breaks and How The Idea Of Code is Lost, with a very well commented Jenks Natural Breaks Optimization in JavaScript.
I've got a basic Space Invaders type game going, and I can't get it to recognise when the shot from the player hits the alien. (I'm only checking Alien2 atm, the one second from the left). Since they're both moving, I've decided the only way to check for collisions is with either a range-based if statement (with 2 top coordinates and one left coordinate), or directly comparing the positions along the Y axis with Jquery.
I'm using the range-based solution at the moment, but so far it hasn't worked (not sure why).
My code so far:
if (key == "87"/*&& document.getElementById('BarrelOne').id=='BarrelOne'*/){
var Invader2 = document.getElementById('Alien2');
var Shot1 = document.getElementById('ShortShot');
Shot1.style.webkitAnimationPlayState="running";
setTimeout(function(){
Shot1.style.webkitAnimationPlayState="paused";
}, 1200);
if(document.elementFromPoint(625.5, 265.5) == Shot1){
Invader2.style.visibility="hidden";
}
};
Jsfiddle:
http://jsfiddle.net/ZJxgT/2/
I did something similar, and i found that it was much easier to achieve using gameQuery.
to test for collisions:
var collided = $("#div1").collision("#div2");
you can see full working example here
EDIT
If you're having trouble check out the API. For example, to find out how to use collisions check this part of the API.
the collision works in the following way:
This method returns the list of elements collisioning with the selected one but only those that match with the filter given as parameter. It takes two optional arguments (their order is not important). The filter is a string filtering element used to detect collision, it should contain all the elements the function should search collision into. For example if you look for collision with element of the class ‘foo’ that may be contained in a group you will use the filter ".group,.foo".
So, write something like this:
$("#ShortShot").collision("#Alien2").hide();
// will return the alien if it collides with ShortShot
or, to hide them both:
if (($("#ShortShot").collision("#Alien2")).length) {
$("#ShortShot").remove();
$("#Alien2").remove();
}
Instead of losing hours reinventing the wheel, I would suggest to switch (if still possible, depending on your time deadline) to a real 2D game engine for Javascript, with easy collision detection.
Check as well: 2D Engines for Javascript
UP-FRONT NOTE: I am not using jQuery or another library here because I want to understand what I’ve written and why it works (or doesn’t), so please don’t answer this with libraries or plugins for libraries. I have nothing against libraries, but for this project they’re inimical to my programming goals.
That said…
Over at http://meyerweb.com/eric/css/colors/ I added some column sorting using DOM functions I wrote myself. The problem is that while it works great for, say, the simple case of alphabetizing strings, the results are inconsistent across browsers when I try to sort on multiple numeric terms—in effect, when I try to do a sort with two subsorts.
For example, if you click “Decimal RGB” a few times in Safari or Firefox on OS X, you get the results I intended. Do the same in Chrome or Opera (again, OS X) and you get very different results. Yes, Safari and Chrome diverge here.
Here’s a snippet of the JS I’m using for the RGB sort:
sorter.sort(function(a,b){
return a.blue - b.blue;
});
sorter.sort(function(a,b){
return a.green - b.green;
});
sorter.sort(function(a,b){
return a.red - b.red;
});
(sorter being the array I’m trying to sort.)
The sort is done in the tradition of another StackOverflow question “How does one sort a multi dimensional array by multiple columns in JavaScript?” and its top answer. Yet the results are not what I expected in two of the four browsers I initially tried out.
I sort (ha!) of get that this has to do with array sorts being “unstable”—no argument here!—but what I don’t know is how to overcome it in a consistent, reliable manner. I could really use some help both understanding the problem and seeing the solution, or at least a generic description of the solution.
I realize there are probably six million ways to optimize the rest of the JS (yes, I used a global). I’m still a JS novice and trying to correct that through practice. Right now, it’s array sorting that’s got me confused, and I could use some help with that piece of the script before moving on to cleaning up the code elsewhere. Thanks in advance!
UPDATE
In addition to the great explanations and suggestions below, I got a line on an even more compact solution:
function rgbSort(a,b) {
return (a.red - b.red || a.green - b.green || a.blue - b.blue);
}
Even though I don’t quite understand it yet, I think I’m beginning to grasp its outlines and it’s what I’m using now. Thanks to everyone for your help!
OK. So, as you've discovered, your problem is that the default JavaScript sort is not guaranteed to be stable. Specifically, I think that in your mind it works like this: I'll sort by blueness, and then when I sort by greenness the sorter will just move entries in my array up and down but keep them ordered by blueness. Sadly, the universe is not so conveniently arranged; the built-in JS sort is allowed to do the sort how it likes. In particular, it's allowed to just throw the contents of the array into a big bucket and then pull them out sorted by what you asked for, completely ignoring how it was arranged before, and it looks like at least some browsers do precisely that.
There are a couple of ways around this, for your particular example. Firstly, you could still do the sort in three separate calls, but make sure those calls do the sort stably: this would mean that after sorting by blueness, you'd stably sort by greenness and that would give you an array sorted by greenness and in blueness order within that (i.e., precisely what you're looking for). My sorttable library does this by implementing a "shaker sort" or "cocktail sort" method (http://en.wikipedia.org/wiki/Cocktail_sort); essentially, this style of sorting walks through the list a lot and moves items up and down. (In particular, what it does not do is just throw all the list items into a bucket and pull them back out in order.) There's a nice little graphic on the Wikipedia article. This means that "subsorts" stay sorted -- i.e., that the sort is stable, and that will give you what you want.
However, for this use case, I wouldn't worry about doing the sort in three different calls and ensuring that they're stable and all that; instead, I'd do all the sorting in one go. We can think of an rgb colour indicator (255, 192, 80) as actually being a big number in some strange base: to avoid too much math, imagine it's in base 1000 (if that phrase makes no sense, ignore it; just think of this as converting the whole rgb attribute into one number encompassing all of it, a bit like how CSS computes precedences in the cascade). So that number could be thought of as actually 255,192,080. If you compute this number for each of your rows and then sort by this number, it'll all work out, and you'll only have to do the sort once: so instead of doing three sorts, you could do one: sorter.sort(function(a,b) { return (a.red*1000000 + a.green*1000 + a.blue) - (b.red*1000000 + b.green*1000 + b.blue) } and it'll all work out.
Technically, this is slightly inefficient, because you have to compute that "base 1000 number" every time that your sort function is called, which may be (is very likely to be) more than once per row. If that's a big problem (which you can work out by benchmarking it), then you can use a Schwartzian transform (sorry for all the buzzwords here): basically, you work out the base-1000-number for each row once, put them all in a list, sort the list, and then go through the sorted list. So, create a list which looks like [ [255192080, <table row 1>], [255255255, <table row 2>], [192000000, <table row 3>] ], sort that list (with a function like mylist.sort(function(a,b) { return a[0]-b[0]; })), and then walk through that list and appendChild each of the s onto the table, which will sort the whole table in order. You probably don't need this last paragraph for the table you've got, but it may be useful and it certainly doesn't hurt to know about this trick, which sorttable.js also uses.
I would approach this problem in a different manner. It appears you're trying to reconstruct all the data by extracting it from the markup, which can be a perilous task; a more straightforward approach would be to represent all the data you want to render out to the page in a format your programs can understand from the start, and then simply regenerate the markup first on page load and then on each subsequent sort.
For instance:
var colorsData = [
{
keyword: 'mediumspringgreen',
decimalrgb: {
r: 0,
g: 250,
b: 154
},
percentrgb: {
r: 0,
g: 98,
b: 60.4
},
hsl: {
h: 157,
s: 100,
l: 49
}
hex: '00FA9A',
shorthex: undefined
},
{
//next color...
}
];
That way, you can run sorts on this array in whatever way you'd like, and you're not trying to rip data out from markup and split it and reassign it and all that.
But really, it seems you're maybe hung up on the sort functions. Running multiple sorts one after the other will get unintended results; you have to run a single sort function that compares the next 'column' in the case the previous one is found to be equal. An RGB sort could look like:
var decimalRgbForwards = function(a,b) {
var a = a.decimalrgb,
b = b.decimalrgb;
if ( a.r === b.r ) {
if ( a.g === b.g ) {
return a.b - b.b;
} else {
return a.g - b.g;
}
} else {
return a.r - b.r;
}
};
So two colors with matching r and g values would return for equality on the b value, which is just what you're looking for.
Then, you can apply the sort:
colorsData.sort(decimalRgbForwards);
..and finally iterate through that array to rebuild the markup inside the table.
Hope it helps, sir-