#include <bits/stdc++.h>
using namespace std;
const int dx[4] = {1, 0, -1, 0}, dy[4] = {0, 1, 0, -1};
class UnionFindTree {
 private:
  vector<int> par;
  vector<int> rnk;
  vector<int> siz;

 public:
  UnionFindTree(int n) {
    par.assign(n, -1);
    rnk.assign(n, -1);
    siz.assign(n, -1);
    for (int i = 0; i < n; ++i) {
      par[i] = i;
      rnk[i] = 0;
      siz[i] = 1;
    }
  }
  int find(int x) {
    if (par[x] == x)
      return x;
    else
      return par[x] = find(par[x]);
  }
  bool same(int x, int y) { return find(x) == find(y); }
  void unite(int x, int y) {
    x = find(x);
    y = find(y);
    if (x == y) return;
    if (rnk[x] < rnk[y]) {
      par[x] = y;
      siz[y] += siz[x];
    } else {
      par[y] = x;
      siz[x] += siz[y];
      if (rnk[x] == rnk[y]) ++rnk[x];
    }
  }
  int size(int x) {
    x = find(x);
    return siz[x];
  }
};
class Edge {
 public:
  long long from;
  long long to;
  long long cost;
  Edge() {}
  Edge(long long from, long long to, long long cost) {
    this->from = from;
    this->to = to;
    this->cost = cost;
  }
  bool operator<(const Edge &edge) const { return cost < edge.cost; }
};
class Graph {
 public:
  long long nodes;
  vector<Edge> edges;
  Graph() {}
  Graph(long long nodes) { this->nodes = nodes; }
  void addEdge(long long from, long long to, long long cost) {
    this->edges.push_back(Edge(from, to, cost));
  }
};
class Kruskal {
 private:
  Graph graph;
  vector<Edge> MinimumSpanningTree;
  long long minimumCost;
  void searchMinimumSpanningTree() {
    UnionFindTree uf(graph.nodes);
    sort((graph.edges).begin(), (graph.edges).end());
    for (auto(edge) = (graph.edges).begin(); (edge) != (graph.edges).end();
         (edge)++) {
      if (!uf.same(edge->from, edge->to)) {
        uf.unite(edge->from, edge->to);
        MinimumSpanningTree.push_back(*edge);
      }
    }
  }

 public:
  Kruskal(Graph graph) { this->graph = graph; }
  long long getMinimumSpanningTreeCost() {
    searchMinimumSpanningTree();
    long long cost = 0;
    for (auto(it) = (MinimumSpanningTree).begin();
         (it) != (MinimumSpanningTree).end(); (it)++) {
      cost += it->cost;
    }
    return cost;
  }
};
class Dijkstra {
 private:
  Graph graph;
  map<long long, vector<Edge>> fromPaths;
  vector<long long> distances;
  vector<long long> srcs;

 public:
  Dijkstra(Graph graph) {
    this->graph = graph;
    for (auto(edge) = (graph.edges).begin(); (edge) != (graph.edges).end();
         (edge)++) {
      fromPaths[edge->from].push_back(*edge);
      fromPaths[edge->to].push_back(Edge(edge->to, edge->from, edge->cost));
    }
  }
  void searchMinimumPathFrom(long long src) {
    this->distances = vector<long long>(graph.nodes + 1, 0x3f3f3f3f);
    this->srcs = vector<long long>(graph.nodes + 1, 0x3f3f3f3f);
    priority_queue<long long> pq;
    distances[src] = 0;
    srcs[src] = -1;
    pq.push(src);
    while (!pq.empty()) {
      int u = pq.top();
      pq.pop();
      for (auto(edge) = (fromPaths[u]).begin(); (edge) != (fromPaths[u]).end();
           (edge)++) {
        int v = edge->to;
        int w = edge->cost;
        if (distances[v] > distances[u] + w) {
          distances[v] = distances[u] + w;
          srcs[v] = u;
          pq.push(v);
        }
      }
    }
  };
  long long getDistance(long long n) { return this->distances[n]; }
  long long getFrom(long long n) { return this->srcs[n]; }
};
class BellmanFord {
 private:
  Graph graph;
  vector<bool> hasNegativeCycles;
  vector<long long> distances;

 public:
  BellmanFord(Graph graph) {
    this->graph = graph;
    this->distances = vector<long long>(this->graph.nodes + 1, 0x3f3f3f3f);
    this->hasNegativeCycles = vector<bool>(this->graph.nodes + 1, false);
  }
  void searchMinimumPathFrom(long long src) {
    this->distances[src] = 0;
    for (long long i = 0; i < graph.nodes - 1; i++) {
      for (auto(edge) = (graph.edges).begin(); (edge) != (graph.edges).end();
           (edge)++) {
        long long u = edge->from;
        long long v = edge->to;
        long long w = edge->cost;
        if (this->distances[u] + w < this->distances[v]) {
          this->distances[v] = this->distances[u] + w;
        }
      }
    }
    for (auto(edge) = (graph.edges).begin(); (edge) != (graph.edges).end();
         (edge)++) {
      long long u = edge->from;
      long long v = edge->to;
      long long w = edge->cost;
      if (this->distances[u] + w < this->distances[v]) {
        this->hasNegativeCycles[v] = true;
      }
      if (this->hasNegativeCycles[u] == true) {
        this->hasNegativeCycles[v] = true;
      }
    }
  }
  long long getDistance(long long n) { return this->distances[n]; }
  bool hasNegativeCycle(long long n) { return this->hasNegativeCycles[n]; }
};
void solve(long long N, long long M, std::vector<long long> a,
           std::vector<long long> b, std::vector<long long> c) {
  Graph g(N);
  for (int(m) = (0); (m) < (M); ++(m)) {
    g.addEdge(a[m], b[m], c[m]);
  }
  Dijkstra dijkstra(g);
  set<pair<long long, long long>> passed;
  for (int(s) = (1); (s) < (N + 1); ++(s)) {
    dijkstra.searchMinimumPathFrom(s);
    for (int(e) = (s + 1); (e) < (N + 1); ++(e)) {
      long long now = e;
      long long pre = dijkstra.getFrom(now);
      while (true) {
        passed.insert(make_pair(pre, now));
        if (pre == s) {
          break;
        }
        now = pre;
        pre = dijkstra.getFrom(now);
      }
    }
  }
  int ans = 0;
  for (int(m) = (0); (m) < (M); ++(m)) {
    if (find((passed).begin(), (passed).end(), make_pair(a[m], b[m])) ==
        passed.end()) {
      ans++;
    }
  }
  cout << ans << endl;
}
int main() {
  long long N;
  scanf("%lld", &N);
  long long M;
  scanf("%lld", &M);
  std::vector<long long> a(M);
  std::vector<long long> b(M);
  std::vector<long long> c(M);
  for (int i = 0; i < M; i++) {
    scanf("%lld", &a[i]);
    scanf("%lld", &b[i]);
    scanf("%lld", &c[i]);
  }
  solve(N, M, std::move(a), std::move(b), std::move(c));
  return 0;
}
