#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
#define rep(i,n) for (int i=0; i < int(n); i++)

struct UnionFind
{
  // par[i]：データiが属する木の親の番号。i == par[i]のとき、データiは木の根ノードである
  vector<int> par;
  // sizes[i]：根ノードiの木に含まれるデータの数。iが根ノードでない場合は無意味な値となる
  vector<int> sizes;

  UnionFind(int n) : par(n), sizes(n, 1){
    // 最初は全てのデータiがグループiに存在するものとして初期化
    rep(i, n) par[i] = i;
  }

  // データxが属する木の根を得る
  int find(int x){
    if (x == par[x]) return x;
    return par[x] = find(par[x]); // 根を張り替えながら再帰的に根ノードを探す
  }

  // 二つのデータx, yが属する木をマージする
  void unite(int x, int y){
    // データの根ノードを得る
    x = find(x);
    y = find(y);

    // 既に同じ木に属しているならマージしない
    if (x == y)  return;

    // xの木がyの木より大きくなるようにする

    if (sizes[x] < sizes[y]) swap(x, y);

    // xがyの親になるように連結する
    par[y] = x;
    sizes[x] += sizes[y];

  }

  // 2つのデータx, yが属する木が同じならtrueを返す
  bool same(int x, int y){
    return find(x) == find(y);
  }

  // データxが含まれる木の大きさを返す
  int size(int x) {
    return sizes[find(x)];
  }
};


struct Edge
{
  int a, b, cost;
  // コストの大小で順序定義
  bool operator<(const Edge& o) const {
    return cost < o.cost;
  }

};

// 頂点数と辺集合の組として定義したグラフ
struct Graph
{
  int n; //頂点数
  vector<Edge> es; //辺集合

  // クラスカル法で無向最小全域木のコストの和を計算する
  // グラフが非連結のときは最小全域森のコストの和となる
  int kruskal(){
    // コストが小さい順にソート
    sort(es.begin(), es.end());

    UnionFind uf(n);
    int min_cost = 0;

    rep(ei, es.size()){
      Edge& e = es[ei];
      if(!uf.same(e.a, e.b)){
        // 辺を追加しても閉路ができないなら、その辺を採用する
        min_cost += e.cost;
        uf.unite(e.a, e.b);
      }
    }

    return min_cost;
  }
};

Graph input_graph(int k, int l){
  Graph g;
  int m;
  g.n = k;
  m = l;
  rep(i, m) {
    Edge e;
    cin >> e.a >> e.b >> e.cost;
    g.es.push_back(e);
  }
  return g;

}

int main(){
  cin.tie(0);
  ios::sync_with_stdio(false);
  int n, m;
  Graph g;
  while((cin >> n >> m) && n){
    g = input_graph(n, m);
    cout << g.kruskal() << endl;
  }

  

}
