//#include "IntMod.h"
//typedef IntMod<1000000007> MInt;

//#include "Union_Find.h"

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <string>
#include <vector>
#include <utility>
#include <algorithm>
#include <functional>
#include <cmath>
#include <stack>
#include <queue>
#include <set>
#include <map>
//#include <unordered_set>
//#include <unordered_map>
#include <iomanip>
#include <sstream>

using namespace std;
#define REP(i,a,n) for(int i = a; i < n; ++i)
#define REPM(i,n,a) for(int i = n - 1; i >= a; --i)
#define EPS 0.0001
#define INF 0x3FFFFFFF
#define INFLL 0x3FFFFFFF3FFFFFFF
#define INFD 1.0e+308
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int, int> PP;

class SplitStream {
	typedef basic_ostream<char, char_traits<char> > BaseOst;
	std::ostream& ost_m;
	bool line_is_empty_m;
public:
	SplitStream(std::ostream& ost);
	template <typename T>
	SplitStream& operator<<(T right);
	SplitStream& operator<<(BaseOst& (*right)(BaseOst&));
};
SplitStream::SplitStream(std::ostream& ost) : ost_m(ost), line_is_empty_m(true) {}
template <typename T>
SplitStream& SplitStream::operator<<(T right) {
	if (!line_is_empty_m) ost_m << ' ';
	line_is_empty_m = false;
	ost_m << right;
	return *this;
}
/* std::endl のみ */
SplitStream& SplitStream::operator<<(BaseOst& (*right)(BaseOst&)) {
	ost_m << '\n';
	ost_m.flush();
	line_is_empty_m = true;
	return *this;
}
SplitStream Scout(cout);

template <typename T, int Begin, int End>
class OffsetArray {
	T arr_m[End - Begin];
public:
	OffsetArray();

	T& operator[](int index);
	T operator[](int index) const;
};
template<typename T, int Begin, int End>
OffsetArray<T, Begin, End>::OffsetArray() : arr_m() {}
template<typename T, int Begin, int End>
T& OffsetArray<T, Begin, End>::operator[](int index) {
	return arr_m[index - Begin];
}
template<typename T, int Begin, int End>
T OffsetArray<T, Begin, End>::operator[](int index) const {
	return arr_m[index - Begin];
}
int N, M;
int A[1000], B[1000], C[1000];
int G[100][100];
int D[100][100];
set<int> F[100][100];
bool T[10000];
int edge(int i, int j) {
	return i < j ? i * 100 + j : j * 100 + i;
}

int main() {
	cin >> N >> M;
	REP(i, 0, N) {
		REP(j, 0, N) {
			G[i][j] = INF;
		}
	}
	REP(i, 0, M) {
		cin >> A[i] >> B[i] >> C[i];
		G[A[i] - 1][B[i] - 1] = C[i];
		G[B[i] - 1][A[i] - 1] = C[i];
	}
	
	REP(i, 0, N) {
		REP(j, 0, N) {
			D[i][j] = (i == j ? 0 : G[i][j]);
			if(i != j) F[i][j].insert(edge(i, j));
		}
	}

	REP(k, 0, N) {
		REP(i, 0, N) {
			REP(j, 0, N) {
				if (D[i][k] + D[k][j] < D[i][j]) {
					D[i][j] = D[i][k] + D[k][j];
					F[i][j] = F[i][k];
					for (auto it = F[k][j].begin(); it != F[k][j].end(); ++it) {
						F[i][j].insert(*it);
					}
				}else if (D[i][k] + D[k][j] == D[i][j]) {
					for (auto it = F[i][k].begin(); it != F[i][k].end(); ++it) {
						F[i][j].insert(*it);
					}
					for (auto it = F[k][j].begin(); it != F[k][j].end(); ++it) {
						F[i][j].insert(*it);
					}
				}
			}
		}
	}
	REP(i, 0, N) {
		REP(j, 0, N) {
			for (auto it = F[i][j].begin(); it != F[i][j].end(); ++it) {
				T[*it] = true;
			}
		}
	}
	int cnt = 0;
	REP(i, 0, M) {
		if (T[edge(A[i] - 1, B[i] - 1)] == false) cnt++;
	}
	cout << cnt << endl;
	return 0;
}


Measured memory_bytes: 16384