#include<algorithm>
#include<iomanip>
#include<iostream>
#include<map>
#include<set>
#include<stack>
#include<string>
#include<vector>
#include<bitset>
#include<cmath>
#include<queue>
#define FOR(i,a,b) for(int i=(a); i<int(b); ++i)
#define RFOR(i,a,b) for(int i=(b)-1; i>=int(a); --i)
#define rep(i,n) FOR(i,0,n)
#define rep1(i,n) FOR(i,1,int(n)+1)
#define rrep(i, n) RFOR(i, 0, n)
#define rrep1(i, n) RFOR(i, 1, int(n) + 1)
#define all(c) begin(c), end(c)
#define al(d) d.begin(),d.end()
#define fill(n) std::setfill('0')<<std::right<<std::setw(n)
template <typename T>
using vec = std::vector<T>;
using vi = vec<int>;
using vvi = vec<vi>;
using pii = std::pair<int, int>;
using ll = long long;
using ld = long double;
using namespace std;
const int MOD = 1000000007;
#define pair(a,b) "("<<a<<","<<b<<")"
#define pairr(a,b,c) "("<<a<<","<<b<<","<<c<<")"

template <typename T>
void putv(vector<T>& V){
	// cout << "The elements in the vector are: " << endl;
	for(auto x: V)
		cout << x << " ";
	cout << endl;
}
template <class T>
vector<T> getv(int n){
	vector<T> vec;
	rep(i,n){
		T input;
		cin >> input;
		vec.emplace_back(input);
	}
	return vec;
}

////Graph
using Weight = int;
using Flow = int;
struct Edge {
	int src, dst;
	Weight weight;
	Flow cap;
	Edge() : src(0), dst(0), weight(0) {}
	Edge(int s, int d, Weight w) : src(s), dst(d), weight(w) {}
};
using Edges = std::vector<Edge>;
using Graph = std::vector<Edges>;
using Array = std::vector<Weight>;
using Matrix = std::vector<Array>;
void add_edge(Graph &g, int a, int b, Weight w = 1){
	g[a].emplace_back(a, b, w);
	g[b].emplace_back(b, a, w);
}
void add_arc(Graph &g, int a, int b, Weight w = 1){
	g[a].emplace_back(a, b, w);
}
void init_weighted_graph(Graph &g, int N, int M){
	vector<Edge> empty_Edges;
	g = Graph(N,empty_Edges);
	rep(i,M){
		int a,b,w;
		cin >> a >> b >> w;
		add_edge(g,a-1,b-1,w);
	}
// 例えば
//// 3 3
//// 1 2 1
//// 1 3 1
//// 2 3 3
// から
//// The Adjacency List of
//// G=(V,E) (n=3,m=6)
//// 0: (1,1)(2,1)
//// 1: (0,1)(2,3)
//// 2: (0,1)(1,3)
// を得る．
}
void show_weighted_graph(const Graph &g){
	int N = g.size();
	int M = 0;
	rep(s,N)
		M += g[s].size();
	cout << "///////////////////////"<<endl;
	cout <<"The Adjacency List of" <<endl<< "G=(V,E) (n="<<N<<","<<"m="<<M<<")"<<endl;
	rep(s,N){
		cout << s << ": ";
		int size = g[s].size();
		rep(t,size)
			cout << pair(g[s][t].dst, g[s][t].weight);
		cout << endl;
	}
	cout << "////////////////////////"<<endl<<endl;
}
// a->bが存在するか
bool is_adj(Graph &g, int a, int b){
	for(auto e: g[a])
		if(b==e.dst)
			return true;
	return false;
}


// 単一始点最短経路問題を解くアルゴリズム．負辺があるとやり直しが発生し，最悪負のループがあると終了しないため，ベルマンフォードを使う．下の実装は優先度付きキューを使用．
// 密なグラフではO(n2)の実装(graph/dijkstra-matrix.cc) を使うべき．負辺は計算量に影響し，最悪無限ループ．
// 計算量O((E+V)logV)
vector<Weight> dijkstra_sparse(const Graph &g, int s){
	const Weight INF = numeric_limits<Weight>::max()/8;
	using state = tuple<Weight,int>;
	priority_queue<state> q;
	vector<Weight> dist(g.size(),INF);
	dist[s] = 0;
	q.emplace(0,s);
	while(q.size()){
		Weight d;
		int v;
		tie(d, v) = q.top();
		q.pop();
		d *= -1;
		/* if(v == t) return d; */
		if(dist[v] < d) continue;
		for(auto &e : g[v]){
			if (dist[e.dst] > dist[v] + e.weight){
				dist[e.dst] = dist[v] + e.weight;
				q.emplace(-dist[e.dst], e.dst);
			}
		}
	}
	return dist;
}

// ヒープを使わないダイクストラ法．密（E∝V^2）なグラフではこっちの方が高速．
// 計算量O(V^2)
std::vector<Weight> dijkstra_dense(const Graph &g, int s) {
	static const Weight INF = std::numeric_limits<Weight>::max() / 8;
	const size_t n = g.size();
	Matrix m(n, Array(n, INF));
	for (const Edges &es : g)
		for (const Edge &e : es) m[e.src][e.dst] = std::min(m[e.src][e.dst], e.weight);
	std::vector<Weight> d(n, INF);
	std::vector<int> used(n);
	d[s] = 0;
	while (1) {
		int v = -1;
		for (size_t j = 0; j < n; j++)
			if (!used[j] && (v == -1 || d[j] < d[v])) v = j;
		if (v == -1) break;
		used[v] = true;
		/* if(v == t) return d[v]; */
		for (const Edge &e : g[v]) {
			int u = e.dst;
			d[u] = std::min(d[u], d[v] + m[v][u]);
		}
	}
	return d;
}

// O(V^3)
Matrix WarshallFloyd(const Graph &g) {
	auto const INF = numeric_limits<Weight>::max()/8;
	int n = g.size();
	Matrix d(n,Array(n,INF));
	rep(i,n) d[i][i]=0;
	rep(i, n) for(auto &e: g[i]) d[e.src][e.dst] = min(d[e.src][e.dst],e.weight);
	rep(k,n) rep(i,n) rep(j,n)
		if(d[i][k]!=INF && d[k][j]!=INF) d[i][j] = min(d[i][j],d[i][k]+d[k][j]);
	return d;
}

//////////

void out(bool ans){
	if(ans == true) cout << "yes" << endl;
	else cout << "no" << endl;
}
void Out(bool ans){
	if(ans == true) cout << "Yes" << endl;
	else cout << "No" << endl;
}
void OUT(bool ans){
	if(ans == true) cout << "YES" << endl;
	else cout << "NO" << endl;
}

//// Mathematical functions
ll gcd(const ll a, const ll b){
	return (b==0) ? a : gcd(b,a%b);
}
ll lcm(const ll a, const ll b){
	return a*(b/gcd(a,b));
}
ll Manhattan_dist(ll x1, ll y1, ll x2, ll y2){
	return abs(x1-x2) + abs(y1-y2);
}
// nをpで何回割れるか
ll hmt_p_divide_n(ll p, ll n){
	ll s = 0;
	while(n%p == 0){
		n /= p;
		s++;
	}
	return s;
}
int digit(ll n){
	int cntr = 0;
	while(n>0){
		n/=10;
		cntr++;
	}
	return cntr;
}
// generarized absolute value
template<class T> T gabs(const T& x) { return max(x, -x); }
#define abs gabs
////////



//////////////////////////////////////////////////
//////////////////////////////////////////////////
//////////////////////////////////////////////////
#include<iostream>
#include<string>
using namespace std;


void func(vector<int> vec){
	vec[0] = 100;
}

int main(){
	int N,M;
	cin >>N>>M;
	Graph g;
	init_weighted_graph(g,N,M);
	// show_weighted_graph(g);

	bool ok[101][101] = {};
	rep(u,N){
		vi d = dijkstra_sparse(g,u);
		rep(v,N)
			if(v!=u)
				for(auto e:g[v])
					if(d[v] == d[e.dst]+e.weight)
						ok[e.dst][v] = ok[v][e.dst] = true;
	}
	int ans = 0;
	rep(i,N)
		FOR(j,i+1,N)
			if(is_adj(g,i,j) && !ok[i][j])
				++ans;

	cout << ans << endl;
}

Measured memory_bytes: 384