import qualified Data.Set as S
import qualified Data.ByteString.Char8 as B
import qualified Data.Array.IArray as A
import qualified Data.Map.Strict as M

main = do
  [n,m] <- map read . words <$> getLine
  es <- readUndirectedEdge <$> B.getContents
  let used = S.fromList $ concatMap (path2edge . snd) $ M.elems $ warshallFloyd n es
  print (m - length used)

path2edge (v1:v2:vs) = (min v1 v2, max v1 v2) : path2edge (v2:vs)
path2edge _ = []

type Memo = M.Map (Vertex, Vertex) (Weight, Path)

warshallFloyd :: Int -> [Edge] -> Memo
warshallFloyd n es = foldl shorten m0 kij
  where
    m0 = M.fromList (map (\((s, t), w) -> ((s, t), (w, [s, t]))) es)
    kij = [(k, i, j) | k <- [1 .. n], i <- [1 .. n], j <- [1 .. n]]

shorten :: Memo -> (Vertex, Vertex, Vertex) -> Memo
shorten m (k, i, j)
  | i == j = m
  | otherwise = case connect m k i j of
    Nothing -> m
    Just (w, p) -> M.insertWith min (i, j) (w, p) m

connect :: Memo -> Vertex -> Vertex -> Vertex -> Maybe (Weight, Path)
connect m k i j = do
  (w1, p1) <- M.lookup (i, k) m
  (w2, p2) <- M.lookup (k, j) m
  return (w1 + w2, p1 ++ tail p2)

type Bound = (Vertex, Vertex)
type Vertex = Int
type Weight = Int
type Edge = ((Vertex, Vertex), Weight)
type Path = [Vertex]
type Graph = A.Array Vertex (S.Set (Vertex, Weight))

buildG :: Bound -> [Edge] -> Graph
buildG b = A.accumArray (flip S.insert) S.empty b . map (\((s, t), w) -> (s, (t, w)))

from :: Graph -> Vertex -> S.Set (Vertex, Weight)
from = (A.!)

size :: Graph -> Int
size g = let (i,j) = A.bounds g in j - i + 1

readUndirectedEdge :: B.ByteString -> [Edge]
readUndirectedEdge = concatMap (constructTwoWay . map readInt . B.words) . B.lines

constructTwoWay :: [Int] -> [Edge]
constructTwoWay [s,t,w] = [((s, t), w), ((t, s), w)]
constructTwoWay _ = undefined

readInt :: B.ByteString -> Int
readInt = maybe undefined fst . B.readInt
