#include <bits/stdc++.h>
#pragma GCC optimize("O3")
using namespace std;
using QWORD = uint64_t;
using SQWORD = int64_t;
using DWORD = uint32_t;
using SDWORD = int32_t;
using WORD = uint16_t;
using SWORD = int16_t;
using BYTE = uint8_t;
using SBYTE = int8_t;
using DOUBLE = double;
using FLOAT = float;
static inline DOUBLE MAX(DOUBLE a, DOUBLE b) { return a > b ? a : b; }
static inline QWORD MAX(QWORD a, QWORD b) { return a > b ? a : b; }
static inline DWORD MAX(DWORD a, DWORD b) { return a > b ? a : b; }
static inline SDWORD MAX(SDWORD a, SDWORD b) { return a > b ? a : b; }
static inline DOUBLE MIN(DOUBLE a, DOUBLE b) { return a < b ? a : b; }
static inline QWORD MIN(QWORD a, QWORD b) { return a < b ? a : b; }
static inline DWORD MIN(DWORD a, DWORD b) { return a < b ? a : b; }
static inline SDWORD MIN(SDWORD a, SDWORD b) { return a < b ? a : b; }
static inline void inputStringSpSeparated(char *pcStr) {
  char *pcCur = pcStr;
  for (;;) {
    char c = getchar();
    if (('\n' == c) || (EOF == c) || (' ' == c)) {
      break;
    }
    *pcCur = c;
    pcCur++;
  }
  *pcCur = '\0';
}
static inline void inputString(char *pcStr) {
  char *pcCur = pcStr;
  for (;;) {
    char c = getchar();
    if (('\n' == c) || (EOF == c)) {
      break;
    }
    *pcCur = c;
    pcCur++;
  }
  *pcCur = '\0';
}
static inline SQWORD inputSQWORD(void) {
  SQWORD sqNumber = 0;
  SQWORD sqMultiplier = 1;
  bool bRead = false;
  for (;;) {
    char c = getchar();
    if (!bRead) {
      if ('-' == c) {
        sqMultiplier = -1;
      }
    }
    if (('0' <= c) && (c <= '9')) {
      sqNumber *= 10LL;
      sqNumber += (SQWORD)(c - '0');
      bRead = true;
    } else {
      if (bRead) {
        return sqNumber * sqMultiplier;
      }
    }
  }
}
static inline SDWORD inputSDWORD(void) {
  SDWORD lNumber = 0;
  SDWORD lMultiplier = 1;
  bool bRead = false;
  for (;;) {
    char c = getchar();
    if (!bRead) {
      if ('-' == c) {
        lMultiplier = -1;
      }
    }
    if (('0' <= c) && (c <= '9')) {
      lNumber *= 10;
      lNumber += (c - '0');
      bRead = true;
    } else {
      if (bRead) {
        return lNumber * lMultiplier;
      }
    }
  }
}
static inline DOUBLE inputFP(void) {
  DOUBLE dInt = 0.0;
  DOUBLE dFrac = 0.0;
  DOUBLE dMultiplier = 1.0;
  DWORD dwFpCnt = 0;
  DOUBLE *pdCur = &dInt;
  bool bRead = false;
  for (;;) {
    char c = getchar();
    if (!bRead) {
      if ('-' == c) {
        dMultiplier = -1;
      }
    }
    if ('.' == c) {
      pdCur = &dFrac;
    } else if (('0' <= c) && (c <= '9')) {
      (*pdCur) *= 10;
      (*pdCur) += (DOUBLE)(c - '0');
      bRead = true;
      if (pdCur == &dFrac) {
        dwFpCnt++;
      }
    } else {
      if (bRead) {
        return dMultiplier *
               (dInt + dFrac / (pow((DOUBLE)10.0, (DOUBLE)dwFpCnt)));
      }
    }
  }
}
class MODINT {
  static SQWORD MOD;
  SQWORD m_x;

 public:
  MODINT(SQWORD val) { m_x = (val % MOD + MOD) % MOD; };
  MODINT() { m_x = 0; }
  static void Init(SQWORD sqMod) { MOD = sqMod; }
  MODINT &operator+=(const MODINT a) {
    m_x = (m_x + a.m_x) % MOD;
    return *this;
  };
  MODINT &operator-=(const MODINT a) {
    m_x = (m_x - a.m_x + MOD) % MOD;
    return *this;
  };
  MODINT &operator*=(const MODINT a) {
    m_x = (m_x * a.m_x) % MOD;
    return *this;
  };
  MODINT pow(SQWORD t) const {
    if (!t) return 1;
    MODINT a = pow(t >> 1);
    a *= a;
    if (t & 1) a *= *this;
    return a;
  }
  MODINT operator+(const MODINT a) const {
    MODINT res(*this);
    return (res += a);
  }
  MODINT operator-(const MODINT a) const {
    MODINT res(*this);
    return (res -= a);
  }
  MODINT operator*(const MODINT a) const {
    MODINT res(*this);
    return (res *= a);
  }
  MODINT operator/(const MODINT a) const {
    MODINT res(*this);
    return (res /= a);
  }
  MODINT inv() const { return pow(MOD - 2); }
  MODINT &operator/=(const MODINT a) { return (*this) *= a.inv(); }
  MODINT &operator+=(const SQWORD a) {
    *this += MODINT(a);
    return *this;
  };
  MODINT &operator-=(const SQWORD a) {
    *this -= MODINT(a);
    return *this;
  };
  MODINT &operator*=(const SQWORD a) {
    *this *= MODINT(a);
    return *this;
  };
  MODINT &operator/=(const SQWORD a) {
    *this /= MODINT(a);
    return *this;
  };
  SQWORD getVal() { return m_x; };
};
SQWORD MODINT::MOD = (1000000007);
struct EDGE {
  SQWORD sqTo;
  SQWORD sqD;
  SQWORD sqIdx;
  EDGE(SQWORD t, SQWORD d, SQWORD i) : sqTo(t), sqD(d), sqIdx(i){};
};
static bool dfs(const vector<vector<EDGE>> &vvE, vector<bool> &vbIsUsed,
                vector<SQWORD> &vsqPos, SQWORD sqCur) {
  for (auto e : vvE[sqCur]) {
    if (!vbIsUsed[e.sqIdx]) {
      vbIsUsed[e.sqIdx] = true;
      SQWORD sqNextPos = vsqPos[sqCur] + e.sqD;
      if ((100100100100100100) == vsqPos[e.sqTo]) {
        vsqPos[e.sqTo] = sqNextPos;
        if (!dfs(vvE, vbIsUsed, vsqPos, e.sqTo)) {
          return false;
        }
      } else {
        if (vsqPos[e.sqTo] != sqNextPos) {
          return false;
        }
      }
    }
  }
  return true;
}
struct EDGE_ST {
  SQWORD sqTo;
  SQWORD sqCost;
  EDGE_ST(SQWORD to, SQWORD cost) {
    sqTo = to;
    sqCost = cost;
  };
};
void solveDijkstra(SQWORD sqFrom, const vector<vector<EDGE_ST>> &vvEdges,
                   vector<SQWORD> &cost) {
  priority_queue<pair<SQWORD, SQWORD>, vector<pair<SQWORD, SQWORD>>,
                 greater<pair<SQWORD, SQWORD>>>
      que;
  cost[sqFrom] = 0;
  que.push(pair<SQWORD, SQWORD>(0, sqFrom));
  while (!que.empty()) {
    pair<SQWORD, SQWORD> p = que.top();
    que.pop();
    SDWORD v = p.second;
    if (p.first <= cost[v]) {
      for (SDWORD lIdx = 0; lIdx < vvEdges[v].size(); lIdx++) {
        EDGE_ST e = vvEdges[v][lIdx];
        if (cost[e.sqTo] > cost[v] + e.sqCost) {
          cost[e.sqTo] = cost[v] + e.sqCost;
          que.push(pair<SQWORD, SQWORD>(cost[e.sqTo], e.sqTo));
        }
      }
    }
  }
}
struct EDGE_DEFINE_ST {
  SQWORD sqA;
  SQWORD sqB;
  SQWORD sqC;
  EDGE_DEFINE_ST(SQWORD a, SQWORD b, SQWORD c) : sqA(a), sqB(b), sqC(c){};
};
static void solveSinglePattern(SQWORD sqVMax,
                               const vector<EDGE_DEFINE_ST> &vEdgeDef,
                               vector<vector<SQWORD>> &vvCost,
                               SQWORD sqRemoveEdgeIdx) {
  vector<vector<EDGE_ST>> vvstE(sqVMax);
  for (SQWORD sqEdgeIdx = 0; sqEdgeIdx < vEdgeDef.size(); sqEdgeIdx++) {
    if (sqEdgeIdx != sqRemoveEdgeIdx) {
      auto &ed = vEdgeDef[sqEdgeIdx];
      vvstE[ed.sqA].emplace_back(ed.sqB, ed.sqC);
      vvstE[ed.sqB].emplace_back(ed.sqA, ed.sqC);
    }
  }
  for (SQWORD sqStartIdx = 1; sqStartIdx < sqVMax; sqStartIdx++) {
    solveDijkstra(sqStartIdx, vvstE, vvCost[sqStartIdx]);
  }
}
int main(void) {
  SQWORD sqN = inputSQWORD();
  SQWORD sqM = inputSQWORD();
  vector<EDGE_DEFINE_ST> vEd;
  for (SQWORD sqIdx = 0; sqIdx < sqM; sqIdx++) {
    SQWORD sqA = inputSQWORD();
    SQWORD sqB = inputSQWORD();
    SQWORD sqC = inputSQWORD();
    vEd.emplace_back(sqA, sqB, sqC);
  }
  SQWORD sqMaxV = sqN + 1;
  vector<vector<SQWORD>> vvCostOrg(
      sqMaxV, vector<SQWORD>(sqMaxV, (100100100100100100)));
  solveSinglePattern(sqMaxV, vEd, vvCostOrg, -1);
  SQWORD sqAns = 0;
  for (SQWORD sqRemoveEdgeIdx = 0; sqRemoveEdgeIdx < sqM; sqRemoveEdgeIdx++) {
    vector<vector<SQWORD>> vvCostRem(
        sqMaxV, vector<SQWORD>(sqMaxV, (100100100100100100)));
    solveSinglePattern(sqMaxV, vEd, vvCostRem, sqRemoveEdgeIdx);
    if (vvCostOrg == vvCostRem) {
      sqAns++;
    }
  }
  printf("%lld\n", sqAns);
  return 0;
}
