I really want to like node.js with redis, but I can't conquer asynchronicity. Again I have what is a simple task in a traditional database and language. My question is more about accomplishing control flow and logic in asynchronous database fetching than whether my problem-solving approach is optimal or not.
Here's what I'm trying to do: I have redis keys made up of words, let's just say car
and card
. Now, given an input string, I want to know what the longest substring is, that matches a key in redis. I only need to check substrings starting from position 0 of the given string, so the complexity is low.
Example: cardinal
has the key card
in it, and also car
, but card
is longer. Cape
does not match either key.
My approach is: start with the whole string and check if it matches a key. If yes, return that key. Otherwise, repeat the same process with the string minus the last character.
How can I accomplish this task? Different approaches are welcome.
I know a little about the async
library and it looks like waterfall
is best for what I'm doing. However, it appears that I need to type all the functions from string.length, string.length-1, etc. until the last single character. What I'm looking for is a good replacement for a for loop with a break.
Below I test with an input I assume is always 3 characters or more (because it's already ugly and more nesting seems pointless for testing). It works, carde
resulting in card
, and care
-> car
. Nonsense gives no match
.
var http = require("http");
var redis = require("redis");
http.createServer(function(request, response) {
client = redis.createClient();
word = "carde";
client.keys(word, function(err, reply) {
if(err) { response.end(err); client.end(); }
else {
if(reply.length > 0) {
response.end(word);
client.end();
}
else {
client.keys(word.slice(0,-1), function(err, reply) {
if(err) { response.end(err); client.end(); }
else {
if(reply.length > 0) {
response.end(word.slice(0, -1));
client.end();
}
else {
client.keys(word.slice(0, -2), function(err,reply) {
if(err) { response.end(err); client.end(); }
else {
if(reply.length > 0) {
response.end(word.slice(0, -2));
client.end();
}
else {
response.end("no match");
}
}
});
}
}
});
}
}
});
}).listen(8000);
I also tried recursion and it might be the best method. (Thanks to Timonthy Strimple for correcting a mistake).
http.createServer(function(request, response) {
client = redis.createClient();
recursiveKeys("cardinalsin", client, response);
}).listen(8000);
function recursiveKeys(word, client, response) {
if (word.length == 0) {
response.write("0");
response.end();
client.end();
}
else {
client.keys(word, function(err, reply) {
if(err) {
response.write("0");
response.end();
client.end();
}
else {
if(reply.length > 0) {
response.write(word);
response.end();
client.end();
}
else {
return recursiveKeys(word.slice(0,-1), client, response);
}
}
});
}
}
I agree that a recursive solution is probably the best. I approached this problem before studying your code (so as to not affect the experiment) and arrived at a very similar solution. Search for a word with http://localhost:3000/?word=cardinal
var http = require('http');
var redis = require('redis');
var url = require('url');
var findWord = function(needle, client, callback) {
if (needle.length <= 0) { return callback("Word not found") }
client.keys(needle, function(err, reply) {
if (err) { callback("Word not found"); }
else {
if (reply.length > 0) {
callback(needle);
} else {
findWord(needle.slice(0, -1), client, callback);
}
}
});
};
var server = http.createServer(function(request, response) {
var query = url.parse(request.url, true).query;
var word = query.word || "";
var client = redis.createClient();
findWord(word, client, function(found) {
client.end();
response.writeHead(200, {"Content-Type": "text/plain"});
response.end(found);
});
});
server.listen(3000);