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
  print (nsp n m es)

nsp n m es = div (foldr (check n memo) 0 es) 2
  where
    memo = warshallFloyd n es

check n memo ((i,j),w) acc
  | any (\x -> abs (memo !? (x,j) - memo !? (x,i)) == w) [1..n] = acc
  | otherwise = acc + 1

(!?) m k = M.findWithDefault 100000 k m

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

warshallFloyd :: Int -> [Edge] -> Memo
warshallFloyd n es = foldl shorten m0 kij
  where
    m0 = M.fromList 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 -> M.insertWith min (i, j) w m

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

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
