<!--order=2-->

<h1>Python: Data Structures and Comprehensions</h1>
<p>Once variables, conditions, loops, and functions make sense, the next step is learning how to organize data. Python's built-in collections cover most everyday jobs without needing an extra library.</p>

<h2>Lists</h2>
<p>A list is ordered and changeable. It is a good choice when the order matters or when items will be added and removed.</p>
<pre>
devices = ["gateway", "field node", "sensor"]

devices.append("test node")
devices[0] = "Pi gateway"

for number, device in enumerate(devices, start=1):
    print(f"{number}. {device}")

print(devices[1:3])  # A slice: items at positions 1 and 2
</pre>

<h2>Tuples</h2>
<p>A tuple is an ordered collection that should not change after it is created. Tuples are useful for fixed values such as coordinates or settings.</p>
<pre>
location = (50.4452, -104.6189)
latitude, longitude = location

print(f"Latitude: {latitude}")
print(f"Longitude: {longitude}")
</pre>

<h2>Sets</h2>
<p>A set stores unique values. It is useful for removing duplicates or testing whether a value has already been seen.</p>
<pre>
seen_nodes = {"node-a", "node-b", "node-a"}
print(seen_nodes)  # node-a appears only once

if "node-c" not in seen_nodes:
    print("This node has not reported yet")
</pre>

<h2>Dictionaries</h2>
<p>A dictionary maps keys to values. This is often the best way to represent one named object or a record with several fields.</p>
<pre>
node = {
    "name": "field-node-01",
    "battery": 87,
    "temperature": 18.4,
    "online": True,
}

print(node["name"])
print(node.get("signal", "unknown"))

for key, value in node.items():
    print(f"{key}: {value}")
</pre>

<h2>List Comprehensions</h2>
<p>A comprehension creates a collection from another iterable. Use one when the expression remains easy to read; an ordinary loop is better when the logic becomes complicated.</p>
<pre>
readings = [12.5, 18.0, 7.25, 21.5, 16.0]

# Keep only readings above 15
warm_readings = [value for value in readings if value &gt; 15]

# Transform every value
fahrenheit = [value * 9 / 5 + 32 for value in readings]

print(warm_readings)
print(fahrenheit)
</pre>

<h2>Choosing a Collection</h2>
<table>
  <thead><tr><th>Collection</th><th>Use it when</th></tr></thead>
  <tbody>
    <tr><td><code>list</code></td><td>You need an ordered, changeable sequence</td></tr>
    <tr><td><code>tuple</code></td><td>You need fixed grouped values</td></tr>
    <tr><td><code>set</code></td><td>You need unique values or fast membership checks</td></tr>
    <tr><td><code>dict</code></td><td>You need named keys mapped to values</td></tr>
  </tbody>
</table>

<h2>Records and Nested Data</h2>
<p>Real data is often a list of dictionaries: one dictionary for each node, reading, or device.</p>
<pre>
nodes = [
    {"name": "field-01", "battery": 92, "online": True},
    {"name": "field-02", "battery": 41, "online": True},
    {"name": "field-03", "battery": 8, "online": False},
]

for node in sorted(nodes, key=lambda item: item["battery"]):
    state = "online" if node["online"] else "offline"
    print(f"{node['name']}: {node['battery']}% ({state})")

low_battery = [
    node["name"] for node in nodes
    if node["battery"] < 20
]
print("Check:", ", ".join(low_battery))
</pre>
<p>Use a dictionary when a field has a name. A tuple such as <code>(92, True)</code> is smaller but makes the code harder to understand later.</p>

<h2>Copies, Mutation, and Defaults</h2>
<pre>
original = ["radio", "antenna"]
same_list = original       # both names point to one list
copied_list = original.copy()

same_list.append("battery")
print(original)             # changed too
print(copied_list)          # unchanged
</pre>
<p>Unexpected changes often come from two variables referring to the same mutable list or dictionary. Make an explicit copy when that is not what you want.</p>

<h2>Practice: Summarize Readings</h2>
<pre>
readings = [
    {"node": "field-01", "temperature": 18.2},
    {"node": "field-02", "temperature": 21.7},
    {"node": "field-01", "temperature": 18.9},
]

by_node = {}
for reading in readings:
    name = reading["node"]
    by_node.setdefault(name, []).append(reading["temperature"])

for name, values in by_node.items():
    average = sum(values) / len(values)
    print(f"{name}: {average:.1f} C")
</pre>

<h2>References</h2>
<ul>
  <li><a href="https://docs.python.org/3/tutorial/datastructures.html" target="_blank" rel="noopener">Python Data Structures ↗</a></li>
  <li><a href="https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions" target="_blank" rel="noopener">List Comprehensions ↗</a></li>
</ul>