#include <bits/stdc++.h>
using namespace std;
const int T5 = 100008;
template <class T>
void dumpBetween(const T& a, const T& b) {
  for (T it = a; it != b; it++) cout << *it << " ";
  cout << endl;
}
template <class T>
void dumpAll(const T& a) {
  dumpBetween((a).begin(), (a).end());
}
template <class T>
void minify(T& a, const T& b) {
  if (a > b) a = b;
}
template <class T>
void maxify(T& a, const T& b) {
  if (a < b) a = b;
}
vector<int> graph[T5];
int n, m;
void readInput() {
  scanf("%d%d", &n, &m);
  for (int _57 = 0; _57 < ((int)m); _57++) {
    int a, b;
    scanf("%d%d", &a, &b);
    graph[a].push_back(b);
    graph[b].push_back(a);
  }
}
bool vis[T5];
int dfn[T5];
int dfnt = 0;
int dfsDegree(int v) {
  if (vis[v]) return 0;
  vis[v] = true;
  int ret = (int)graph[v].size();
  for (unsigned int i = 0; i < ((unsigned int)(graph[v]).size()); i++) {
    ret += dfsDegree(graph[v][i]);
  }
  return ret;
}
vector<int> sol[T5];
bool dfsSolve(int v, int parent) {
  if (dfn[v] != -1) return parent == -1 ? false : dfn[parent] < dfn[v];
  dfn[v] = dfnt++;
  for (unsigned int i = 0; i < ((unsigned int)(graph[v]).size()); i++) {
    if (graph[v][i] == parent) continue;
    if (dfsSolve(graph[v][i], v)) {
      sol[v].push_back(graph[v][i]);
    }
  }
  if (sol[v].size() % 2 != 0) {
    assert(parent != -1);
    sol[v].push_back(parent);
    return false;
  }
  return true;
}
void solve() {
  fill(vis, vis + T5, false);
  for (int i = 1; i < ((int)n + 1); i++) {
    int d = dfsDegree(i);
    if (d % 4 != 0) {
      printf("No solution\n");
      return;
    }
  }
  fill(dfn, dfn + T5, -1);
  for (int i = 1; i < ((int)n + 1); i++) {
    dfsSolve(i, -1);
    unsigned int sn = (unsigned int)(sol[i].size());
    assert(sn % 2 == 0);
    for (unsigned int j = 0; j < ((unsigned int)sn / 2); j++) {
      printf("%d %d %d\n", sol[i][2 * j], i, sol[i][2 * j + 1]);
    }
  }
}
void tc() {
  readInput();
  solve();
}
int main() { tc(); }
