use std::vec::Vec;

fn read<T: std::str::FromStr>() -> T {
    let mut s = String::new();
    std::io::stdin().read_line(&mut s).ok();
    s.trim().parse().ok().unwrap()
}

fn read_vec<T: std::str::FromStr>() -> Vec<T> {
    read::<String>().split_whitespace().map(|e| e.parse().ok().unwrap()).collect()
}

fn read_touple2<T: std::str::FromStr + Copy>() -> (T, T) {
  let v: Vec<T> = read_vec();
  assert_eq!(v.len(), 2);
  (v[0], v[1])
}

fn read_touple3<T: std::str::FromStr + Copy>() -> (T, T, T) {
  let v: Vec<T> = read_vec();
  assert_eq!(v.len(), 3);
  (v[0], v[1], v[2])
}

fn main() {
  let (n, m) = read_touple2::<usize>();

  let mut g = Vec::new();
  for _ in 0..m {
    let i = read_touple3::<usize>();
    g.push(i);
  }

  let (dist, edges) = warshal_floyd_path(n, &g, false);
  let mut ans = m;
  for &(from, to, c) in &g {
    if edges[from-1][to-1] > 0 {
        ans -= 1;
    }
  }
  println!("{}", ans);
}


//
// O(V^3)
fn warshal_floyd_path(
  n: usize,
  v: &Vec<(usize, usize, usize)>,
  direct: bool,
) -> (Vec<Vec<usize>>, Vec<Vec<i64>>) {
  let mut dist = vec![vec![std::usize::MAX / 2; n]; n];
  let mut edges = vec![vec![0; n]; n];

  // init
  for &(x, y, c) in v {
    dist[x-1][y-1] = c;
    if !direct {
      dist[y-1][x-1] = c;
    }
  }

  for k in 0..n {
    for i in 0..n {
      for j in 0..n {
        if dist[i][j] > dist[i][k] + dist[k][j] {
          dist[i][j] = dist[i][k] + dist[k][j];
          edges[i][k] += 1;
          edges[k][j] += 1;
          edges[i][j] -= 1;
        }
      }
    }
  }
  (dist, edges)
}

// 経路復元用のの関数
// O(V)
fn recover_print(paths: &Vec<Vec<usize>>, start: usize, goal: usize) -> Vec<usize> {
  let mut res = Vec::new();
  let mut cur = start;
  while {
    res.push(cur);
    cur = paths[cur][goal];
    cur != goal
  } {}
  res
}