# `Yog.IO.GDF`
[🔗](https://github.com/code-shoily/yog_ex/blob/v1.0.0/lib/yog/io/gdf.ex#L1)

GDF (GUESS Graph Format) serialization support.

Provides functions to serialize and deserialize graphs in GDF format,
a simple text-based format used by Gephi and other graph visualization tools.
GDF uses a column-based format similar to CSV with separate sections for nodes and edges.

## Format Overview

GDF files consist of two sections:
- **nodedef>** - Defines node columns and data
- **edgedef>** - Defines edge columns and data

## Example

    iex> graph = Yog.directed()
    ...> |> Yog.add_node(1, "Alice")
    ...> |> Yog.add_node(2, "Bob")
    ...> |> Yog.add_edge_ensure(from: 1, to: 2, with: "5")
    iex>
    iex> # Serialize to GDF
    iex> _gdf_string = Yog.IO.GDF.serialize(graph)
    iex>
    iex> # You could then write to a file:
    iex> # Yog.IO.GDF.write("graph.gdf", graph)

## Output Format

```
nodedef>name VARCHAR,label VARCHAR
1,Alice
2,Bob
edgedef>node1 VARCHAR,node2 VARCHAR,directed BOOLEAN,weight VARCHAR
1,2,true,5
```

## References

- [GDF Format Specification](https://gephi.org/users/supported-graph-formats/gdf-format/)
- [GUESS Visualization Tool](https://graphexploration.cond.org/)

# `default_options`

```elixir
@spec default_options() :: tuple()
```

Default GDF serialization options.

Returns a options tuple used by the `_with` serialization functions.
It includes:
- `separator`: `","`
- `include_types`: `true`
- `include_directed`: `:none` (auto-detects from graph type)
- `node_formatter`: `Kernel.to_string/1`
- `edge_formatter`: `Kernel.to_string/1`

# `deserialize`

```elixir
@spec deserialize(String.t()) :: {:ok, Yog.graph()} | {:error, term()}
```

Deserializes a GDF string to a graph.

This is a simplified version of `deserialize_with` for graphs where
you want node data and edge data as string dictionaries containing all attributes.

Raises `ArgumentError` if `gdf` is not a binary string.

Time complexity: $\mathcal{O}(V + E)$

## Example

    iex> gdf_string = """
    ...> nodedef>name VARCHAR,label VARCHAR
    ...> 1,Alice
    ...> edgedef>node1 VARCHAR,node2 VARCHAR,directed BOOLEAN,weight VARCHAR
    ...> 1,2,true,10
    ...> """
    iex> {:ok, graph} = Yog.IO.GDF.deserialize(gdf_string)
    iex> node1_data = Yog.Model.node(graph, 1)
    iex> Map.get(node1_data, "label")
    "Alice"

# `deserialize_with`

```elixir
@spec deserialize_with((map() -&gt; any()), (map() -&gt; any()), String.t()) ::
  {:ok, Yog.graph()} | {:error, term()}
```

Deserializes a GDF string into a graph with custom data mappers.

This function allows you to control how GDF columns are converted
to your node and edge data types. Use `deserialize/1` for simple cases
where you want node/edge data as string dictionaries.

Raises `ArgumentError` if `gdf` is not a binary string or mappers are invalid.

Time complexity: $\mathcal{O}(V + E)$

## Example

    iex> node_folder = fn attrs ->
    ...>   name = Map.get(attrs, "label", "")
    ...>   age = Map.get(attrs, "age", "0") |> String.to_integer()
    ...>   %{name: name, age: age}
    ...> end
    iex>
    iex> edge_folder = fn attrs -> Map.get(attrs, "weight", "") end
    iex>
    iex> gdf = """
    ...> nodedef>name VARCHAR,label VARCHAR,age VARCHAR
    ...> 1,Alice,30
    ...> edgedef>node1 VARCHAR,node2 VARCHAR,directed BOOLEAN,weight VARCHAR
    ...> 1,2,true,strong
    ...> """
    iex> {:ok, graph} = Yog.IO.GDF.deserialize_with(node_folder, edge_folder, gdf)
    iex> Yog.Model.node_count(graph)
    2

# `read`

```elixir
@spec read(String.t()) :: {:ok, Yog.graph()} | {:error, term()}
```

Reads a graph from a GDF file.

Raises `ArgumentError` if `path` is not a binary string.

Time complexity: $\mathcal{O}(V + E)$ + file I/O

Returns `{:ok, graph}` on success, where the attributes on nodes and edges are generic.

# `read_with`

```elixir
@spec read_with(String.t(), (map() -&gt; any()), (map() -&gt; any())) ::
  {:ok, Yog.graph()} | {:error, term()}
```

Reads a graph from a GDF file with custom data mappers.

Raises `ArgumentError` if `path` is not a binary string.

Time complexity: $\mathcal{O}(V + E)$ + file I/O

# `serialize`

```elixir
@spec serialize(Yog.graph() | Yog.DAG.t()) :: String.t()
```

Serializes a graph to GDF format where node and edge data are strings.

This is a simplified version of `serialize_with` for graphs where
node data and edge data are already strings. The string data is used
as the "label" attribute for both nodes and edges.

Raises `ArgumentError` if `graph` is invalid.

Time complexity: $\mathcal{O}(V + E)$

## Example

    iex> graph = Yog.directed()
    ...> |> Yog.add_node(1, "Alice")
    ...> |> Yog.add_node(2, "Bob")
    ...> |> Yog.add_edge_ensure(from: 1, to: 2, with: "friend")
    iex>
    iex> gdf = Yog.IO.GDF.serialize(graph)
    iex> String.contains?(gdf, "1,Alice")
    true
    iex> String.contains?(gdf, "1,2,true,friend")
    true

# `serialize_weighted`

```elixir
@spec serialize_weighted(Yog.graph() | Yog.DAG.t()) :: String.t()
```

Serializes a graph to GDF format with integer edge weights.

This is a convenience function for the common case of graphs with
integer weights. Node data is used as labels, and edge weights are
serialized to the "weight" column.

Raises `ArgumentError` if `graph` is invalid.

Time complexity: $\mathcal{O}(V + E)$

## Example

    iex> graph = Yog.directed()
    ...> |> Yog.add_node(1, "Alice")
    ...> |> Yog.add_node(2, "Bob")
    ...> |> Yog.add_edge_ensure(from: 1, to: 2, with: 5)
    iex>
    iex> gdf = Yog.IO.GDF.serialize_weighted(graph)
    iex> String.contains?(gdf, "1,2,true,5")
    true

# `serialize_with`

```elixir
@spec serialize_with(
  (any() -&gt; map()),
  (any() -&gt; map()),
  tuple(),
  Yog.graph() | Yog.DAG.t()
) ::
  String.t()
```

Serializes a graph to GDF format with custom attribute mappers and options.

This function allows you to control how node and edge data are converted
to GDF attributes, and customize the output format.

Raises `ArgumentError` if attribute mappers, options, or graph input are invalid.

Time complexity: $\mathcal{O}(V + E)$

## Example

    iex> graph = Yog.directed()
    ...> |> Yog.add_node(1, %{name: "Alice", age: 30})
    ...> |> Yog.add_node(2, %{name: "Bob", age: 25})
    ...> |> Yog.add_edge_ensure(from: 1, to: 2, with: %{weight: 10, relation: "friend"})
    iex>
    iex> node_attr = fn data -> %{"label" => data.name, "age" => Integer.to_string(data.age)} end
    iex> edge_attr = fn data -> %{"weight" => Integer.to_string(data.weight), "type" => data.relation} end
    iex>
    iex> gdf = Yog.IO.GDF.serialize_with(node_attr, edge_attr, Yog.IO.GDF.default_options(), graph)
    iex> String.contains?(gdf, "nodedef>name")
    true
    iex> String.contains?(gdf, "Alice")
    true
    iex> String.contains?(gdf, "30")
    true

# `write`

```elixir
@spec write(String.t(), Yog.graph() | Yog.DAG.t()) :: {:ok, nil} | {:error, atom()}
```

Writes a graph to a GDF file.

Raises `ArgumentError` if `path` is not a binary string or `graph` is invalid.

Time complexity: $\mathcal{O}(V + E)$ + file I/O

## Parameters

- `path` - File path to write to (typically `.gdf` extension)
- `graph` - The graph to serialize

## Returns

- `{:ok, nil}` on success
- `{:error, reason}` on failure

## Example

    iex> graph = Yog.directed()
    ...> |> Yog.add_node(1, "Start")
    ...> |> Yog.add_node(2, "End")
    ...> |> Yog.add_edge_ensure(from: 1, to: 2, with: "connection")
    iex>
    iex> path = "/tmp/test1.gdf"
    iex> Yog.IO.GDF.write(path, graph)
    {:ok, nil}

# `write_with`

```elixir
@spec write_with(
  String.t(),
  (any() -&gt; map()),
  (any() -&gt; map()),
  tuple(),
  Yog.graph() | Yog.DAG.t()
) ::
  {:ok, nil} | {:error, atom()}
```

Writes a graph to a GDF file with custom attribute mappers.

Raises `ArgumentError` if `path` is not a binary string or parameters are invalid.

Time complexity: $\mathcal{O}(V + E)$ + file I/O

---

*Consult [api-reference.md](api-reference.md) for complete listing*
