Compare commits
No commits in common. "3c9e0e27d0db788f43965b8466530f404a1bca80" and "6cfa551ab8e53705aee65119df0809d05c2da1a9" have entirely different histories.
3c9e0e27d0
...
6cfa551ab8
3
.gitignore
vendored
3
.gitignore
vendored
@ -34,6 +34,3 @@
|
|||||||
|
|
||||||
# Build directory
|
# Build directory
|
||||||
build
|
build
|
||||||
|
|
||||||
# Clang files
|
|
||||||
.cache
|
|
||||||
|
57
README.md
57
README.md
@ -1,12 +1,5 @@
|
|||||||
# arff-mining
|
# arff-mining
|
||||||
|
|
||||||
## Author, Date, Description
|
|
||||||
- Gregory Crawford
|
|
||||||
- Date: 2024-04-17
|
|
||||||
- Description: Read data from ARFF file and generate OneR output
|
|
||||||
- Performance summary at the bottom of this README
|
|
||||||
|
|
||||||
|
|
||||||
## Compiling the project
|
## Compiling the project
|
||||||
|
|
||||||
Prerequisites
|
Prerequisites
|
||||||
@ -17,7 +10,6 @@ In order to compile the project, simply run these two commands:
|
|||||||
cmake -B build -S .
|
cmake -B build -S .
|
||||||
cmake --build build
|
cmake --build build
|
||||||
|
|
||||||
|
|
||||||
## Running the Project
|
## Running the Project
|
||||||
|
|
||||||
The programs should now be compiled at ./build/bin/
|
The programs should now be compiled at ./build/bin/
|
||||||
@ -26,52 +18,3 @@ ARFF:
|
|||||||
```plain
|
```plain
|
||||||
build/bin/arff
|
build/bin/arff
|
||||||
```
|
```
|
||||||
|
|
||||||
One Rule:
|
|
||||||
```plain
|
|
||||||
build/bin/onerule
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Performance Summary
|
|
||||||
|
|
||||||
***contactLenses***
|
|
||||||
```
|
|
||||||
tear-prod-rate:
|
|
||||||
normal ---> soft
|
|
||||||
reduced ---> none
|
|
||||||
Error rate: 7/24
|
|
||||||
build/bin/onerule 0.00s user 0.00s system 85% cpu 0.002 total
|
|
||||||
```
|
|
||||||
|
|
||||||
***restaurants***
|
|
||||||
```
|
|
||||||
Pat:
|
|
||||||
Full ---> No
|
|
||||||
None ---> No
|
|
||||||
Some ---> Yes
|
|
||||||
Error rate: 2/12
|
|
||||||
build/bin/onerule 0.00s user 0.00s system 85% cpu 0.001 total
|
|
||||||
```
|
|
||||||
|
|
||||||
***soybean***
|
|
||||||
```
|
|
||||||
fruit-spots:
|
|
||||||
absent ---> alternarialeaf-spot
|
|
||||||
brown-w/blk-specks ---> anthracnose
|
|
||||||
colored ---> frog-eye-leaf-spot
|
|
||||||
distort ---> alternarialeaf-spot
|
|
||||||
dna ---> brown-stem-rot
|
|
||||||
Error rate: 385/683
|
|
||||||
build/bin/onerule 0.02s user 0.00s system 98% cpu 0.024 total
|
|
||||||
```
|
|
||||||
|
|
||||||
***weather***
|
|
||||||
```
|
|
||||||
outlook:
|
|
||||||
overcast ---> yes
|
|
||||||
rainy ---> yes
|
|
||||||
sunny ---> no
|
|
||||||
Error rate: 4/14
|
|
||||||
build/bin/onerule 0.00s user 0.00s system 87% cpu 0.001 total
|
|
||||||
```
|
|
||||||
|
@ -1,2 +1 @@
|
|||||||
add_subdirectory(arff)
|
add_subdirectory(arff)
|
||||||
add_subdirectory(onerule)
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
add_executable(arff
|
add_executable(arff
|
||||||
main.cpp
|
./main.cpp
|
||||||
arff.cpp
|
./arff.cpp
|
||||||
log.cpp
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,17 +1,18 @@
|
|||||||
#include "arff.hpp"
|
#include "arff.hpp"
|
||||||
#include "log.hpp"
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
namespace ARFF {
|
namespace ARFF {
|
||||||
|
bool isVerbose = false;
|
||||||
|
|
||||||
void ParseArguments(int argc, char* argv[]) {
|
void ParseArguments(int argc, char* argv[]) {
|
||||||
std::string argument_string;
|
std::string argument_string;
|
||||||
for (int i = 0; i < argc; ++i) {
|
for (int i = 0; i < argc; ++i) {
|
||||||
argument_string.assign(argv[i]);
|
argument_string.assign(argv[i]);
|
||||||
if (argument_string == "-v" || argument_string == "--verbose") {
|
if (argument_string == "-v" || argument_string == "--verbose") {
|
||||||
debug::verbose = true;
|
isVerbose = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -21,13 +22,25 @@ namespace ARFF {
|
|||||||
std::cout << "Please enter name of the data file:\t";
|
std::cout << "Please enter name of the data file:\t";
|
||||||
std::cin >> filename;
|
std::cin >> filename;
|
||||||
if (filename.empty()) {
|
if (filename.empty()) {
|
||||||
debug::Log(kError, "No data filename provided, exiting...");
|
LogError("ARFF/Setup", "No data filename provided, exiting...");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
std::cout << std::endl;
|
std::cout << std::endl;
|
||||||
return filename;
|
return filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LogInfo(const std::string location, const std::string message) {
|
||||||
|
if (!isVerbose) { return; }
|
||||||
|
std::cout << '[' << location << " - INFO] ";
|
||||||
|
std::cout << message << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogError(const std::string location, const std::string message) {
|
||||||
|
if (!isVerbose) { return; }
|
||||||
|
std::cerr << '[' << location << " - ERROR] ";
|
||||||
|
std::cerr << message << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
AttributeType::AttributeType(std::string attribute) {
|
AttributeType::AttributeType(std::string attribute) {
|
||||||
this->attribute = attribute;
|
this->attribute = attribute;
|
||||||
}
|
}
|
||||||
@ -41,15 +54,11 @@ namespace ARFF {
|
|||||||
this->values.resize(size);
|
this->values.resize(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
AttributeEvaluation::AttributeEvaluation(AttributeType *attribute) {
|
|
||||||
this->currentAttribute = attribute;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read entire data file and parse it
|
// Read entire data file and parse it
|
||||||
void Arff::Read(std::string filename) {
|
void Arff::Read(std::string filename) {
|
||||||
std::ifstream dataFile(filename);
|
std::ifstream dataFile(filename);
|
||||||
if (!dataFile.is_open()) {
|
if (!dataFile.is_open()) {
|
||||||
debug::Log(kError, "Unable to open file with name `"
|
LogError("ARFF/Read", "Unable to open file with name `"
|
||||||
+ filename + ", exiting...");
|
+ filename + ", exiting...");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
@ -72,16 +81,11 @@ namespace ARFF {
|
|||||||
TestIntegrity();
|
TestIntegrity();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print generic data information
|
void Arff::Print(void) {
|
||||||
// Number of attributes and size of database
|
|
||||||
void Arff::PrintOverview(void) {
|
|
||||||
std::cout << attributeList.size() << " attributes\n";
|
std::cout << attributeList.size() << " attributes\n";
|
||||||
std::cout << database.size() << " examples\n";
|
std::cout << database.size() << " examples\n";
|
||||||
std::cout << std::endl;
|
std::cout << std::endl;
|
||||||
}
|
|
||||||
|
|
||||||
// Print full data information
|
|
||||||
void Arff::PrintData(void) {
|
|
||||||
std::cout << "Attribute (#): values\n";
|
std::cout << "Attribute (#): values\n";
|
||||||
for (AttributeType type : attributeList) {
|
for (AttributeType type : attributeList) {
|
||||||
std::cout << type.attribute << " (" << type.values.size() << "):";
|
std::cout << type.attribute << " (" << type.values.size() << "):";
|
||||||
@ -103,19 +107,6 @@ namespace ARFF {
|
|||||||
std::cout << std::endl;
|
std::cout << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print result of applying OneR
|
|
||||||
// TODO: Create function
|
|
||||||
void Arff::OneR(void) {
|
|
||||||
AttributeEvaluation bestAttribute = _OneR();
|
|
||||||
debug::Log(kNone, "***Best 1-rule***");
|
|
||||||
debug::Log(kNone, "\t" + bestAttribute.currentAttribute->attribute + ':');
|
|
||||||
for (auto it : bestAttribute.rules) {
|
|
||||||
debug::Log(kNone, "\t\t" + it.first + " ---> " + it.second);
|
|
||||||
}
|
|
||||||
debug::Log(kNone, "Error rate: " + std::to_string(bestAttribute.totalError) + "/" + std::to_string(database.size()));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Add the attribute to the list
|
// Add the attribute to the list
|
||||||
void Arff::AddAttribute(std::string line) {
|
void Arff::AddAttribute(std::string line) {
|
||||||
std::stringstream parser(line);
|
std::stringstream parser(line);
|
||||||
@ -129,12 +120,12 @@ namespace ARFF {
|
|||||||
if (token == "@relation" || token == "@RELATION") {
|
if (token == "@relation" || token == "@RELATION") {
|
||||||
parser >> token;
|
parser >> token;
|
||||||
relation = token;
|
relation = token;
|
||||||
debug::Log(kLog, "Relation set: " + relation);
|
LogInfo("ARFF/Attribute", "Relation set: " + relation);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
parser >> token;
|
parser >> token;
|
||||||
attributeList.emplace_back(token);
|
attributeList.emplace_back(token);
|
||||||
debug::Log(kLog, "Added attribute: " + token);
|
LogInfo("ARFF/Attribute", "Added attribute: " + token);
|
||||||
while (std::getline(parser, token, ',')) {
|
while (std::getline(parser, token, ',')) {
|
||||||
// Clean token from outside pieces
|
// Clean token from outside pieces
|
||||||
token.erase(std::remove(token.begin(), token.end(), ' '), token.end());
|
token.erase(std::remove(token.begin(), token.end(), ' '), token.end());
|
||||||
@ -145,7 +136,7 @@ namespace ARFF {
|
|||||||
token.erase(std::remove(token.begin(), token.end(), '\r'), token.end());
|
token.erase(std::remove(token.begin(), token.end(), '\r'), token.end());
|
||||||
token.erase(std::remove(token.begin(), token.end(), '\n'), token.end());
|
token.erase(std::remove(token.begin(), token.end(), '\n'), token.end());
|
||||||
attributeList.back().AddValue(token);
|
attributeList.back().AddValue(token);
|
||||||
debug::Log(kLog, "Added value: " + token);
|
LogInfo("ARFF/Attribute", "Added value: " + token);
|
||||||
}
|
}
|
||||||
// Additional missing value case
|
// Additional missing value case
|
||||||
attributeList.back().AddValue("?");
|
attributeList.back().AddValue("?");
|
||||||
@ -158,14 +149,14 @@ namespace ARFF {
|
|||||||
int id = 0;
|
int id = 0;
|
||||||
if (!database.empty()) { id = database.back().id + 1; }
|
if (!database.empty()) { id = database.back().id + 1; }
|
||||||
database.emplace_back(id, attributeList.size());
|
database.emplace_back(id, attributeList.size());
|
||||||
debug::Log(kLog, "Added id: " + std::to_string(database.back().id));
|
LogInfo("ARFF/Data", "Added id: " + std::to_string(database.back().id));
|
||||||
for (int i = 0; i < attributeList.size(); ++i) {
|
for (int i = 0; i < attributeList.size(); ++i) {
|
||||||
std::getline(parser, token, ',');
|
std::getline(parser, token, ',');
|
||||||
token.erase(std::remove(token.begin(), token.end(), ' '), token.end());
|
token.erase(std::remove(token.begin(), token.end(), ' '), token.end());
|
||||||
token.erase(std::remove(token.begin(), token.end(), '\r'), token.end());
|
token.erase(std::remove(token.begin(), token.end(), '\r'), token.end());
|
||||||
token.erase(std::remove(token.begin(), token.end(), '\n'), token.end());
|
token.erase(std::remove(token.begin(), token.end(), '\n'), token.end());
|
||||||
database.back().values.at(i) = token;
|
database.back().values.at(i) = token;
|
||||||
debug::Log(kLog, "Added instance value: " + token);
|
LogInfo("ARFF/Data", "Added instance value: " + token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,81 +164,25 @@ namespace ARFF {
|
|||||||
for (Instance instance : database) {
|
for (Instance instance : database) {
|
||||||
int successCheck = 0;
|
int successCheck = 0;
|
||||||
for (int i = 0; i < attributeList.size(); ++i) {
|
for (int i = 0; i < attributeList.size(); ++i) {
|
||||||
debug::Log(kTrace, "Instance value tested: '"
|
LogInfo("ARFF/Integrity", "Instance value tested: '"
|
||||||
+ instance.values.at(i) + "'");
|
+ instance.values.at(i) + "'");
|
||||||
for (std::string value : attributeList.at(i).values) {
|
for (std::string value : attributeList.at(i).values) {
|
||||||
debug::Log(kTrace, "attributeList value: '"
|
LogInfo("ARFF/Integrity", "attributeList value: '"
|
||||||
+ value + "'");
|
+ value + "'");
|
||||||
if (instance.values.at(i) == value) {
|
if (instance.values.at(i) == value) {
|
||||||
debug::Log(kTrace, "Value found: " + value);
|
LogInfo("ARFF/Integrity", "Value found: " + value);
|
||||||
++successCheck;
|
++successCheck;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (successCheck != attributeList.size()) {
|
if (successCheck != attributeList.size()) {
|
||||||
debug::Log(kError, "Value size mismatch: "
|
LogError("ARFF/Integrity", "Value size mismatch: "
|
||||||
+ std::to_string(successCheck) + " out of "
|
+ std::to_string(successCheck) + " out of "
|
||||||
+ std::to_string(attributeList.size()));
|
+ std::to_string(attributeList.size()));
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
debug::Log(kLog, "All values exist, continuing...");
|
LogInfo("ARFF/Integrity", "All values exist, continuing...");
|
||||||
}
|
|
||||||
|
|
||||||
// Perform OneR on data that was previously read in
|
|
||||||
AttributeEvaluation Arff::_OneR(void) {
|
|
||||||
AttributeEvaluation bestEvaluation;
|
|
||||||
bestEvaluation.totalErrorRate = 1.0f;
|
|
||||||
// -1 used for ignoring test rule (eg, play=yes/no)
|
|
||||||
for (int i = 0; i < attributeList.size() - 1; ++i) {
|
|
||||||
AttributeEvaluation evaluation = EvaluateAttribute(&attributeList[i], i);
|
|
||||||
if (evaluation.totalErrorRate < bestEvaluation.totalErrorRate) {
|
|
||||||
bestEvaluation = evaluation;
|
|
||||||
bestEvaluation.currentAttribute = evaluation.currentAttribute;
|
|
||||||
}
|
|
||||||
debug::Log(kLog, "Evaluation on " + evaluation.currentAttribute->attribute + " completed");
|
|
||||||
}
|
|
||||||
return bestEvaluation;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine error rate and best option for each value of an attribute
|
|
||||||
// Originally set up to use OneR
|
|
||||||
AttributeEvaluation Arff::EvaluateAttribute(AttributeType *attribute, const int attributePos) {
|
|
||||||
AttributeEvaluation evaluation(attribute);
|
|
||||||
std::map<std::string, int> results;
|
|
||||||
for (std::string value : attributeList.end()->values) {
|
|
||||||
results.emplace(value, 0);
|
|
||||||
}
|
|
||||||
for (int i = 0; i < attribute->values.size(); ++i) {
|
|
||||||
int resultsTotal = 0;
|
|
||||||
if (attribute->values[i] == "?") { continue; }
|
|
||||||
for (auto instance = database.begin(); instance != database.end(); ++instance) {
|
|
||||||
if (instance->values[attributePos] != attribute->values[i]) { continue; }
|
|
||||||
++results[instance->values.back()];
|
|
||||||
resultsTotal++;
|
|
||||||
}
|
|
||||||
debug::Log(kTrace, "Results:");
|
|
||||||
for (auto it : results) { debug::Log(kTrace, "\t" + it.first + ": " + std::to_string(it.second)); }
|
|
||||||
float lowest;
|
|
||||||
float lowestRate = 1.0f;
|
|
||||||
std::string bestResult = results.begin()->first;
|
|
||||||
for (auto it = results.begin(); it != results.end(); ++it) {
|
|
||||||
if (((resultsTotal - it->second) / float(resultsTotal)) < lowestRate) {
|
|
||||||
lowestRate = ((resultsTotal - it->second) / float(resultsTotal));
|
|
||||||
lowest = resultsTotal - it->second;
|
|
||||||
bestResult = it->first;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
evaluation.rules.emplace(attribute->values[i], bestResult);
|
|
||||||
evaluation.totalError += lowest;
|
|
||||||
debug::Log(kLog, "Added rule " + attribute->values[i] + "->" + bestResult);
|
|
||||||
// Reset
|
|
||||||
for (auto it = results.begin(); it != results.end(); ++it) {
|
|
||||||
it->second = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
evaluation.totalErrorRate = evaluation.totalError / float(database.size());
|
|
||||||
return evaluation;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,11 +3,12 @@
|
|||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <map>
|
|
||||||
|
|
||||||
namespace ARFF {
|
namespace ARFF {
|
||||||
void ParseArguments(int argc, char* argv[]);
|
void ParseArguments(int argc, char* argv[]);
|
||||||
std::string GetDataFilename(void);
|
std::string GetDataFilename(void);
|
||||||
|
void LogInfo(const std::string location, const std::string message);
|
||||||
|
void LogError(const std::string location, const std::string message);
|
||||||
|
|
||||||
struct AttributeType {
|
struct AttributeType {
|
||||||
public:
|
public:
|
||||||
@ -24,22 +25,11 @@ namespace ARFF {
|
|||||||
std::vector<std::string> values;
|
std::vector<std::string> values;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct AttributeEvaluation {
|
|
||||||
AttributeEvaluation() = default;
|
|
||||||
AttributeEvaluation(AttributeType *attribute);
|
|
||||||
AttributeType *currentAttribute;
|
|
||||||
std::map<std::string, std::string> rules;
|
|
||||||
float totalErrorRate = 0.0f;
|
|
||||||
int totalError = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Arff {
|
class Arff {
|
||||||
public:
|
public:
|
||||||
Arff() = default;
|
Arff() = default;
|
||||||
void Read(std::string filename);
|
void Read(std::string filename);
|
||||||
void PrintOverview(void);
|
void Print(void);
|
||||||
void PrintData(void);
|
|
||||||
void OneR(void);
|
|
||||||
private:
|
private:
|
||||||
std::string relation;
|
std::string relation;
|
||||||
std::vector<AttributeType> attributeList;
|
std::vector<AttributeType> attributeList;
|
||||||
@ -47,8 +37,6 @@ namespace ARFF {
|
|||||||
void AddAttribute(std::string line);
|
void AddAttribute(std::string line);
|
||||||
void AddData(std::string line);
|
void AddData(std::string line);
|
||||||
void TestIntegrity(void);
|
void TestIntegrity(void);
|
||||||
AttributeEvaluation _OneR(void);
|
|
||||||
AttributeEvaluation EvaluateAttribute(AttributeType *attribute, const int attributePos);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
#include "log.hpp"
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
namespace debug {
|
|
||||||
bool verbose = false;
|
|
||||||
|
|
||||||
void Log(LogLevel level, std::string message) {
|
|
||||||
std::string logMessage = "";
|
|
||||||
if (!verbose && level > kNone) { return; }
|
|
||||||
switch (level) {
|
|
||||||
case kLog: logMessage += "[LOG] "; break;
|
|
||||||
case kWarn: logMessage += "[WARN] "; break;
|
|
||||||
case kError: logMessage += "[ERROR] "; break;
|
|
||||||
case kTrace: logMessage += "[TRACE] "; break;
|
|
||||||
}
|
|
||||||
logMessage += message;
|
|
||||||
std::cout << logMessage << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
#ifndef LOG_HPP
|
|
||||||
#define LOG_HPP
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
enum LogLevel {
|
|
||||||
kNone = -1,
|
|
||||||
kLog = 0,
|
|
||||||
kWarn,
|
|
||||||
kError,
|
|
||||||
kTrace
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace debug {
|
|
||||||
extern bool verbose;
|
|
||||||
void Log(LogLevel level, std::string message);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
@ -9,6 +9,5 @@ int main(int argc, char* argv[]) {
|
|||||||
ARFF::ParseArguments(argc, argv);
|
ARFF::ParseArguments(argc, argv);
|
||||||
ARFF::Arff data;
|
ARFF::Arff data;
|
||||||
data.Read(ARFF::GetDataFilename());
|
data.Read(ARFF::GetDataFilename());
|
||||||
data.PrintOverview();
|
data.Print();
|
||||||
data.PrintData();
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
include_directories(../arff)
|
|
||||||
add_executable(onerule
|
|
||||||
main.cpp
|
|
||||||
../arff/arff.cpp
|
|
||||||
../arff/log.cpp
|
|
||||||
)
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
|||||||
/*
|
|
||||||
* Author: Gregory Crawford
|
|
||||||
* Date: 2024-03-18
|
|
||||||
* Description: Read data from ARFF file and generate OneR output
|
|
||||||
*/
|
|
||||||
#include "arff.hpp"
|
|
||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
|
||||||
ARFF::ParseArguments(argc, argv);
|
|
||||||
ARFF::Arff data;
|
|
||||||
data.Read(ARFF::GetDataFilename());
|
|
||||||
data.PrintOverview();
|
|
||||||
data.OneR();
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user