// depends on: -

/*
usage:

tools::dijkstra<int> calculator(node_count);
calculator.add_edge(from_node, to_node, cost);
std::vector<int> distances = calculator.calc(start_node);
*/

#include <queue>
#include <cstddef>
#include <utility>
#include <vector>
#include <limits>
#include <unordered_map>
#include <cassert>

namespace tools {
  template <typename T> class dijkstra {
  private:
    std::vector<T> distances;
    std::vector<std::size_t> prev_nodes;
    std::priority_queue<std::pair<T, std::size_t>, std::vector<std::pair<T, std::size_t>>, std::greater<std::pair<T, std::size_t>>> tasks;
    std::vector<std::unordered_map<std::size_t, T>> edges;

  public:
    static constexpr std::size_t NONE = std::numeric_limits<std::size_t>::max();

    dijkstra(const std::size_t node_count) :
      distances(node_count),
      prev_nodes(node_count),
      tasks(),
      edges(node_count) {
    }

    void add_edge(const std::size_t from, const std::size_t to, const T distance) {
      this->edges[from].insert(std::make_pair(to, distance));
    }

    std::vector<T> calc(const std::size_t start_node) {
      assert(start_node < this->distances.size());
      this->distances.assign(this->distances.size(), std::numeric_limits<T>::max());
      this->prev_nodes.assign(this->prev_nodes.size(), std::size_t(NONE));
      this->tasks.push(std::make_pair(0, start_node));
      while (!this->tasks.empty()) {
        const std::pair<T, std::size_t> task = this->tasks.top();
        this->tasks.pop();
        const T& distance = std::get<0>(task);
        const std::size_t& target_node = std::get<1>(task);
        if (this->distances[target_node] != std::numeric_limits<T>::max()) {
          // old task for already visited node
          continue;
        }
        this->distances[target_node] = distance;
        for (const std::pair<std::size_t, T>& edge : this->edges[target_node]) {
          const std::size_t& next_node = std::get<0>(edge);
          const T new_distance = distance + std::get<1>(edge);
          if (new_distance < this->distances[next_node]) {
            this->prev_nodes[next_node] = target_node;
            this->tasks.push(std::make_pair(new_distance, next_node));
          }
        }
      }
      return this->distances;
    }
  };
}
#include <iostream>
#include <cstdint>
#include <vector>

class edge {
public:
  std::int_fast64_t a;
  std::int_fast64_t b;
  std::int_fast64_t c;
  constexpr edge(const std::int_fast64_t a, const std::int_fast64_t b, const std::int_fast64_t c) : a(a), b(b), c(c) {}
  constexpr edge() : edge(0, 0, 0) {}
  friend std::istream& operator>>(std::istream& is, edge& edge) {
    std::istream& result = is >> edge.a >> edge.b >> edge.c;
    --edge.a;
    --edge.b;
    return result;
  }
};

int main() {
  std::int_fast64_t n, m;
  std::cin >> n >> m;
  std::vector<edge> edges(m);
  for (edge& edge : edges) {
    std::cin >> edge;
  }

  tools::dijkstra<std::int_fast64_t> graph(n);
  for (edge& edge : edges) {
    graph.add_edge(edge.a, edge.b, edge.c);
    graph.add_edge(edge.b, edge.a, edge.c);
  }

  std::vector<std::vector<std::int_fast64_t>> shortest_distances(n, std::vector<std::int_fast64_t>(n));
  for (std::int_fast64_t i = 0; i < n; ++i) {
    shortest_distances[i] = graph.calc(i);
  }

  std::int_fast64_t result = 0;
  for (edge& edge : edges) {
    if (shortest_distances[edge.a][edge.b] < edge.c) ++result;
  }
  std::cout << result << std::endl;
  return 0;
}

Measured memory_bytes: 512