MongoDB - MapReduce zliczający ilość znaków oraz tylko litery

0

Załączony plik .tsv zawiera 4 książki z Project Gutenberg.
Ładujemy go do MongoDB
mongoimport --db gutenberg --collection books --type tsv --headerline < gutenberg_books.tsv

Powstaje piękna kolekcja books:

db.books.find().limit(10).pretty()

Próbuję napisać mapReduce, który zliczy znaki w p, dla każdej ksiązki.

m=function(){emit(this.title,this.p);};

r=function(key,values){
zlicz=0
for (x in values){
zlicz += values[x].length};
return zlicz};

db.books.mapReduce(m,r,{out:{inline:1}});

Tylko wynik jest taki:

{
	"results" : [
		{
			"_id" : "The Idiot",
			"value" : NaN
		},
		{
			"_id" : "The Man Who Knew Too Much",
			"value" : NaN
		},
		{
			"_id" : "The Sign of the Four",
			"value" : NaN
		},
		{
			"_id" : "War and Peace",
			"value" : NaN
		}
	],
	"timeMillis" : 117,
	"counts" : {
		"input" : 18786,
		"emit" : 18786,
		"reduce" : 193,
		"output" : 4
	},
	"ok" : 1
}

Jak ewentualnie usunę + z funkcji r po zlicz, to wynikiem będzie ilość znaków z ostatniego akapitu każdej książki. Czyli niby działa. Ale nie sumuje wszystkich akapitów. A może po zliczeniu jest tego za dużo?

Co robię źle? Jakiegoś forEach trzeba dodać?

Kolejna sprawa, to napisanie mapReduce, który dla każdej ksiązki zliczy tylko litery w p. Ale tu nawet nie zacząłem skoro z tym pierwszym nie pykło.
Będę wdzięczny za pomoc.

0

Kluczowym jest fakt, że typ zwracany przez funkcję redukującą powinien być dokładnie taki sam jak ten zwracany przez funkcję mapującą - u Ciebie tak nie jest. Jest to istotne z tego względu, że funkcja reduce jest uruchamiana kilkukrotnie, jeśli dla danego klucza znajduje się wiele wartości.

0

Postarałem się trochę uprościć Twoje rozwiązanie - możesz od razu w mapie zwracać długość p, zamiast dokonywać mapowania podczas redukcji. Wyszło mi coś takiego, powinno działać:

var mapFunction = function () {
    emit(this.title, { length: this.p.length });
}

var reduceFunction = function (key, values) {
    summedLength = values.map(x => x.length).reduce((a, b) => a + b)
    return { length: summedLength }
};

db.books.mapReduce(
    mapFunction,
    reduceFunction,
    { out: { inline: 1 } }
);
0

Wielkie dzięki.
A jeśli chcieć by to ograniczyć tylko do liter to gdzie .match(/[a-z]+/g) dopisać?

var mapFunction = function () {
	litery=this.p.toLowerCase().match(/[a-z]+/g);
    emit(this.title, { length: litery.length });
}

var reduceFunction = function (key, values) {
    summedLength = values.map(x => x.length).reduce((a, b) => a + b)
    return { length: summedLength }
};

db.books.mapReduce(
    mapFunction,
    reduceFunction,
    { out: { inline: 1 } }
);

Dopisałem jako nową zmienną w części map, ale coś nie pykło.

0

Tutaj masz problem czysto JSowy - match(regex) zwraca Ci tablicę wyrazów które przeszły przez Twojego regexa. Czyli dla

Ala ma kota

Dostaniesz ["ala", "ma", "kota"]. Wyciągnięcie z tego długości tablicy nie da Ci tego co chcesz osiągnąć. Musisz przemapować wyrazy w tablicy na ich długości, a następnie je zsumować, czyli byłoby to jakoś tak:

var mapFunction = function () {
    loweredWordsList=this.p.toLowerCase().match(/[a-z]+/g);
    totalLength = loweredWordsList.map(x => x.length).reduce((a, b) => a + b)
    emit(this.title, { length: totalLength });
}

var reduceFunction = function (key, values) {
    summedLength = values.map(x => x.length).reduce((a, b) => a + b)
    return { length: summedLength }
};

db.books.mapReduce(
    mapFunction,
    reduceFunction,
    { out: { inline: 1 } }
);

1 użytkowników online, w tym zalogowanych: 0, gości: 1