The code works like this, you type in a combination and another combination which is your desired combination, for example:
[
The first two lines are the combination, the next two are the desired combination, the fifth line is the minimum number of moves required, then the program displays the minimum number of moves required to get the combination, but now I'm required to make the program work through the terminal such that a file is passed in as input.txt
then the result will be saved as output.txt
, and can be run from terminal as app.exe input.txt output.txt
, the code works fine right now but finding a way to read inputs from a file is problematic from
#include <iostream>
#include <fstream>
#include <queue>
#include <map>
#include <algorithm>
using namespace std;
struct State {
char a[2][4];
static State read() {
State s;
for (int i = 0; i < 2; i) {
for (int j = 0; j < 4; j) {
cin >> s.a[i][j];
}
}
return s;
}
State shift(int di, int dj) const {
State next = *this;
for (int i = 0; i < 2; i) {
for (int j = 0; j < 4; j) {
if (next.a[i][j] == '#') {
int ni = di i;
int nj = dj j;
if (ni >= 0 && ni < 2 && nj >= 0 && nj < 4) {
swap(next.a[i][j], next.a[ni][nj]);
}
return next;
}
}
}
}
};
int compare(const State& left, const State& rigth) {
for (int i = 0; i < 2; i) {
for (int j = 0; j < 4; j) {
if (left.a[i][j] != rigth.a[i][j]) {
return left.a[i][j] - rigth.a[i][j];
}
}
}
return 0;
}
bool operator < (const State& left, const State& rigth) {
return compare(left, rigth) < 0;
}
bool operator == (const State& left, const State& rigth) {
return compare(left, rigth) == 0;
}
int main(int argc, char** argv)
{
if (argc < 3) {
perror("Not enough arguments");
return 1;
}
string filename("input.txt");
vector<char> bytes;
FILE* input_file = fopen(filename.c_str(), "r");
if (input_file == nullptr) {
return EXIT_FAILURE;
}
unsigned char character = 0;
while (!feof(input_file)) {
character = getc(input_file);
cout << character << "-";
}
State start = State::read();
State finish = State::read();
map<State, int> len;
queue<State> q;
len[start] = 0;
q.push(start);
while (!q.empty()) {
State cur = q.front();
q.pop();
if (cur == finish) {
cout << len[cur];
return 0;
}
for (int i = -1; i < 2; i) {
for (int j = -1; j < 2; j) {
if (i * i j * j == 1) {
State next = cur.shift(i, j);
if (len.count(next) == 0) {
len[next] = len[cur] 1;
q.push(next);
}
}
}
}
}
cout << -1;
}
CodePudding user response:
You need to read about and understand the C iostream facilities. Please study them.
There you can read about the extraction operator >>
and the insertion operator <<
.
And what you need to do, is to overwrite the extraction operator for your class and the use this instead of your "read" function.
Because the extraction operator works on all istream
s and childs, you can basically read from everything. Please see this picture:
Taken from here. There you can see that there is basically no difference between an istream
like std::cin
or a file stream or even a string stream.
So, if you have overwritten the extraction operator for your class State, then you can write
State state;
std::cin >> state;
//or, if you have an std::ifstream ifs
ifs >> state;
and so on. You will understand that pattern. You can see an implementation example in the code example below.
I had a hard time to understand your description of the orignal problem. It was desribed rather cryptic. Basically you have a game board with symbols on it and one field is free. Then you can shift one symbol to this free field. This means swap the symbol with the empty field. The goal is, to reach a given target pattern with many steps of shifting/swapping.
Additionally you made some strange or complicated implementations in your code.
And, there was no line of comment and also the variable names are not that "readable".
Because of all of the above, I created a new implementation based on your algorithm, with some minor improvements.
Please see one possible improved solution below:
#include <iostream>
#include <fstream>
#include <array>
#include <algorithm>
#include <map>
#include <queue>
#include <utility>
// -----------------------------------
// Some basic definitions
// Dimensions of the game board
constexpr size_t NumberOfRows {2u};
constexpr size_t NumberOfColumns {4u};
// Character to denote an empty field
constexpr char EmptyField {'#'};
// -----------------------------------------------------------------
// Directions will be given as offsets in row and column coordinates
using Direction = std::pair<const int, const int>;
// directions for up, down left right
constexpr std::array<Direction, 4> Moves{{ {-1,0},{1,0},{0,-1},{0,1} }};
// ----------------------------------------------------------
// Safeguarding minimum requirements for gameBoard dimensions
static_assert ((NumberOfRows>1)&&(NumberOfColumns>1),"Error. Wrong game board dimensions\n");
// Borders of the gameBoard
constexpr int BorderLeft {0};
constexpr int BorderUp {0};
constexpr int BorderRight{NumberOfColumns - 1};
constexpr int BorderBottom{NumberOfRows - 1 };
// Make reading/writing simpler by using an alias
using GameBoard = std::array<std::array<char, NumberOfColumns>, NumberOfRows>;
// ---------------------------------------
// A game board at a certain point in time
class GameState {
// The game board, stored as a 2d array
GameBoard gameBoard{};
public:
// Create a new GameState with the EmptyField shifted to a new position
GameState swapPosition(const Direction& direction);
// Extractor operator. Read a gameBoard from a stream
friend std::istream& operator >> (std::istream& is, GameState& gs) {
// Read character for all rows and columns
for (auto& row : gs.gameBoard) for (char& c : row) is >> c;
return is;
}
// Overwrite less than operator for std::map
bool operator < (const GameState& other) const { return gameBoard < other.gameBoard; }
// And overwrite equal operator for easier evaluation
bool operator == (const GameState& other) const { return gameBoard == other.gameBoard; }
};
// --------------------------------------------------------------------
// Create a new GameState with the EmptyField shifted to a new position
GameState GameState::swapPosition(const Direction& direction){
// Make a copy from this gameBoard for a new GameState
GameState swappedGameState = *this;
// Find the EmptyField
for (int row{}; row < static_cast<const int>(NumberOfRows); row) {
for (int column{}; column < static_cast<const int>(NumberOfColumns); column) {
if (gameBoard[row][column] == EmptyField) {
// Calculate new row and column index based on the given direction
// DO not cross the borders of the gameBoard
const int nextRow = std::clamp((row direction.first), BorderUp, BorderBottom);
const int nextColumn = std::clamp((column direction.second), BorderLeft, BorderRight);
// Shift. Exchange the EmptyField with a char in the given direction
std::swap(swappedGameState.gameBoard[row][column],swappedGameState.gameBoard[nextRow][nextColumn]);
}
}
}
return swappedGameState;
}
// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
int main(int argc, char **argv) {
// First check, if we have sufficient command line arguments
if (argc == 3) {
// OK, we have command line arguments, try to open the specified files
if (std::ifstream inputStream{argv[1]}; inputStream) {
// Now the output stream
if (std::ofstream outputStream{argv[2]}; outputStream) {
// All files are open. Get start and destination game states
// First define the instances
GameState startGameState{};
GameState destinationGameState{};
// Then read/extract data from file and store in instances
inputStream >> startGameState;
inputStream >> destinationGameState;
// Set/Initialize evaluation parameters
// Here we will store the number of swaps that lead to this state
std::map<GameState, unsigned int> numberOfSwaps{{startGameState, 0u}};
// Here we will store all intermediate states after one or many swaps
std::queue<GameState> trialGameState{};
trialGameState.push(startGameState);
// Indicator, if we found a solution or not
bool solutionFound{};
// Until we find a solution (or not)
while (not trialGameState.empty() && not solutionFound) {
// get the current (latest swapped) game state
GameState currentGameState = trialGameState.front();
trialGameState.pop();
// check for end condition. Did we find the desitnation combination
if (currentGameState == destinationGameState) {
solutionFound = true;
outputStream << numberOfSwaps[currentGameState] << std::endl;
}
else {
// Get next states in all directions. Iterate over all possible moves
for (const Direction& direction : Moves) {
// Next state for this direction
GameState nextState = currentGameState.swapPosition(direction);
// Prevent circular swapping and storing double elements
if (numberOfSwaps.count(nextState) == 0) {
// OK, valid next state. Store the number of swaps done
numberOfSwaps[nextState] = numberOfSwaps[currentGameState] 1 ;
// And save this state as one potential new candidate
trialGameState.push(nextState);
}
}
}
} // All error mesaages
if (not solutionFound)
std::cerr << "\nNo solution found\n\n";
}
else std::cerr << "\nError: Could not open input file '" << argv[1] << "'\n";
}
else std::cerr << "\nError: Could not open output file '" << argv[2] << "'\n";
}
else std::cerr << "\nError: Please call with: app inputFileName outputFileName\n";
return 0;
}
But even this needs to be further improved. At lease an input validation should be addedd. Anyway, have fun.
CodePudding user response:
In C read characters from input file like this :
#include <fstream>
char input_c;
std::ifstream input_file("input.txt");
while (input_file >> input_c) // stops at eof
{
// validate input
//.. do your things..
}