#include <bits/stdc++.h>
using namespace std;
template <class T>
void chmin(T& a, const T& b) {
  a = min(a, b);
}
template <class T>
void chmax(T& a, const T& b) {
  a = max(a, b);
}
void print() { cout << endl; }
template <class Head, class... Tail>
void print(Head&& h, Tail&&... t) {
  if (sizeof...(t) == 0)
    cout << h;
  else
    cout << h << ' ';
  print(move(t)...);
}
const double EPS = 1e-9;
const long INF = 2147483647;
const long MOD = 1e+9 + 7;
int dy[] = {0, 0, 1, -1, 1, 1, -1, -1};
int dx[] = {1, -1, 0, 0, 1, -1, -1, 1};
const int MAX_V = 100;
const int MAX_E = 1000;
struct edge {
  int from, to, cost, No;
};
int V, E;
vector<edge> G[MAX_V];
int d[MAX_V];
int pd[MAX_V];
int used[MAX_E];
void dijkstra(int s) {
  priority_queue<tuple<int, int, int>, vector<tuple<int, int, int>>,
                 greater<tuple<int, int, int>>>
      que;
  fill(d, d + V, INF);
  fill(pd, pd + V, -1);
  d[s] = 0;
  que.push(tuple<int, int, int>(0, s, -1));
  while (!que.empty()) {
    tuple<int, int, int> p = que.top();
    que.pop();
    int v = get<1>(p);
    if (d[v] < (get<0>(p))) continue;
    if (get<2>(p) != -1) used[get<2>(p)] = 1;
    for (int i = (0); i < ((int)G[v].size()); i++) {
      edge e = G[v][i];
      if (d[e.to] > d[v] + e.cost) {
        d[e.to] = d[v] + e.cost;
        pd[e.to] = v;
        que.push(tuple<int, int, int>(d[e.to], e.to, e.No));
      }
    }
  }
}
vector<int> get_path(int t) {
  vector<int> path;
  for (; t != -1; t = pd[t]) path.push_back(t);
  reverse(path.begin(), path.end());
  return path;
}
int main(void) {
  cin.tie(0);
  ios_base::sync_with_stdio(false);
  fill(used, used + 1000, 0);
  cin >> V >> E;
  edge e;
  for (int i = (0); i < (E); i++) {
    cin >> e.from >> e.to >> e.cost;
    e.from--;
    e.to--;
    e.No = i;
    G[e.from].push_back(e);
    swap(e.from, e.to);
    G[e.from].push_back(e);
  }
  dijkstra(0);
  int result = 0;
  for (int i = (0); i < (E); i++)
    if (used[i] == 0) result++;
  print(result);
  return 0;
}
