formats/arff.js
function(dataset, relationName, featureLookupTable) {
	var arff = "% Automatically generated by Node.js\n";
	arff += "@relation "+relationName+"\n";

	featureLookupTable.featureIndexToFeatureName.forEach(function(featureName) {
		if (_.isUndefined(featureName)) 
			arff += "@attribute undefined {0,1}"+"\n";
		else if (!_.isString(featureName))
			throw new Error("Expected featureName to be a string, but found "+JSON.stringify(featureName));
		else arff += "@attribute "+featureName.replace(/[^a-zA-Z0-9]/g, "_")+" "+"{0,1}"+"\n";
	});

	arff += "\n@data\n";
	
	dataset.forEach(function(datum) {
		var datumArff = _.clone(datum.input, {});
		for (var i=0; i<datum.output.length; ++i)
			datumArff[datum.output[i]]=1;
		//console.dir(datumArff);
		var array = featureLookupTable.hashToArray(datumArff);
		arff += array + "\n";
	});

	return arff;
}
function(dataset, relationName, featureLookupTable) {
	var arff = "% Automatically generated by Node.js\n";
	arff += "@relation "+relationName+"\n";

	featureLookupTable.featureIndexToFeatureName.forEach(function(featureName) {
		if (_.isUndefined(featureName)) 
			arff += "@attribute undefined {0,1}"+"\n";
		else if (!_.isString(featureName))
			throw new Error("Expected featureName to be a string, but found "+JSON.stringify(featureName));
		else arff += "@attribute "+featureName.replace(/[^a-zA-Z0-9]/g, "_")+" "+"{0,1}"+"\n";
	});

	arff += "\n@data\n";
	
	dataset.forEach(function(datum) {
		var datumArff = _.clone(datum.input, {});
		for (var i=0; i<datum.output.length; ++i)
			datumArff[datum.output[i]]=1;
		//console.dir(datumArff);
		var array = featureLookupTable.hashToArray(datumArff);
		arff += array + "\n";
	});

	return arff;
}
convert a single dataset to Weka ARFF string.
@param dataset an array of samples in the format {input: {feature1: xxx, feature2: yyy, ...}, output: [1,2,3]}
@param relationName string for the @relation on the top of the file.
@param featureLookupTable maps features to indices
@return an ARFF string.