You are using an outdated browser. For a faster, safer browsing experience, upgrade for free today.

Loading...

Blog

Zde najdete zajímavé články ze světa IT.
Chcete napsat článek? Zapojte se.

Kolekce — Pole a listy

zpět na kategorii Mechaniky jazyka - Před 23 dny, Autor: Antonín Jehlář


Máš pole plné dat a potřebuješ z nich něco vytáhnout? Filtrovat, seřadit, spočítat průměr? Pojďme se podívat, jak si s tím poradí různé jazyky — a kdo z toho vyjde s čistýma rukama.



Kolekce — Pole a listy: protože data se sama nesrovnají

Toto je třetí díl série! Předchozí díl najdeš zde.

Pole je nejzákladnější datová struktura, kterou budeš v životě používat. Máš seznam věcí — uživatelů, produktů, čísel — a potřebuješ z nich něco vytáhnout. Vyfiltrovat, seřadit, transformovat, agregovat. Každý jazyk to umí, ale každý jazyk to dělá po svém.

Zadání je jednoduché. Máme seznam uživatelů:

Jméno Věk Aktivní
Alice 30 ano
Bob 25 ne
Charlie 35 ano
Diana 28 ano
Eve 22 ne

Chceme:

  1. Vyfiltrovat pouze aktivní uživatele
  2. Seřadit je podle jména
  3. Vypsat jejich jména
  4. Vypsat průměrný věk aktivních uživatelů

1. C++

#include <algorithm>
#include <iostream>
#include <numeric>
#include <string>
#include <vector>

struct User
{
    std::string name;
    int age;
    bool active;
};

int main()
{
    std::vector<User> users = {
        {"Alice",   30, true},
        {"Bob",     25, false},
        {"Charlie", 35, true},
        {"Diana",   28, true},
        {"Eve",     22, false},
    };

    std::vector<User> active;
    std::copy_if(users.begin(), users.end(), std::back_inserter(active),
        [](const User& u) { return u.active; });

    std::sort(active.begin(), active.end(),
        [](const User& a, const User& b) { return a.name < b.name; });

    for (size_t i = 0; i < active.size(); i++) {
        std::cout << active[i].name;
        if (i < active.size() - 1) std::cout << ", ";
    }
    std::cout << std::endl;

    double avg = std::accumulate(active.begin(), active.end(), 0.0,
        [](double sum, const User& u) { return sum + u.age; }) / active.size();
    std::cout << "Průměrný věk: " << avg << std::endl;

    return 0;
}

Vyzkoušet můžeš tady

C++ přistupuje ke kolekcím přes iterátory a STL algoritmy. std::copy_if filtruje, std::sort řadí, std::accumulate počítá součet. Vše s lambdami. Na papíře funkcionální, v praxi je to jako psát básničku pomocí slovníku — každé slovo je správně, ale celkový dojem bolí.

Přehlednost: 2/10

Počet řádků: 42

2. C#

using System;
using System.Collections.Generic;
using System.Linq;

class User
{
    public string Name { get; set; }
    public int Age { get; set; }
    public bool Active { get; set; }
}

class Program
{
    static void Main()
    {
        var users = new List<User>
        {
            new User { Name = "Alice",   Age = 30, Active = true  },
            new User { Name = "Bob",     Age = 25, Active = false },
            new User { Name = "Charlie", Age = 35, Active = true  },
            new User { Name = "Diana",   Age = 28, Active = true  },
            new User { Name = "Eve",     Age = 22, Active = false },
        };

        var active = users
            .Where(u => u.Active)
            .OrderBy(u => u.Name)
            .ToList();

        Console.WriteLine(string.Join(", ", active.Select(u => u.Name)));
        Console.WriteLine($"Průměrný věk: {active.Average(u => u.Age):0.0}");
    }
}

Vyzkoušet můžeš tady

LINQ je jedna z nejhezčích věcí, co C# má. .Where(), .OrderBy(), .Select(), .Average() — čteš to jako větu. Definice třídy User je upovídaná (každá property na třech řádcích), ale samotné zpracování dat je ukázkové. Tohle je přesně to, jak by funkcionální API mělo vypadat. Nicméně i tam C# dostal -2 body za upovídanost.

Přehlednost: 8/10

Počet řádků: 33

3. Java

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

class User
{
    String name;
    int age;
    boolean active;

    User(String name, int age, boolean active)
    {
        this.name = name;
        this.age = age;
        this.active = active;
    }
}

class Main
{
    public static void main(String[] args)
    {
        List<User> users = Arrays.asList(
            new User("Alice",   30, true),
            new User("Bob",     25, false),
            new User("Charlie", 35, true),
            new User("Diana",   28, true),
            new User("Eve",     22, false)
        );

        List<User> active = users.stream()
            .filter(u -> u.active)
            .sorted(Comparator.comparing(u -> u.name))
            .collect(Collectors.toList());

        System.out.println(active.stream()
            .map(u -> u.name)
            .collect(Collectors.joining(", ")));

        double avg = active.stream()
            .mapToInt(u -> u.age)
            .average()
            .orElse(0);
        System.out.printf("Průměrný věk: %.1f%n", avg);
    }
}

Vyzkoušet můžeš tady

Java Stream API dělá vše správně, ale s charakteristickou upovídaností. Collectors.toList(), Collectors.joining(), mapToInt(), .average().orElse(0) — každá operace má svůj vlastní ceremoniál. Výsledek je čitelný pokud Javu znáš, ale pro nováčka je to hodně nových pojmů najednou.

Přehlednost: 6/10

Počet řádků: 47

4. Rust

struct User
{
    name: &'static str,
    age: u32,
    active: bool,
}

fn main()
{
    let users = vec![
        User { name: "Alice",   age: 30, active: true  },
        User { name: "Bob",     age: 25, active: false },
        User { name: "Charlie", age: 35, active: true  },
        User { name: "Diana",   age: 28, active: true  },
        User { name: "Eve",     age: 22, active: false },
    ];

    let mut active: Vec<&User> = users.iter()
        .filter(|u| u.active)
        .collect();

    active.sort_by_key(|u| u.name);

    println!("{}", active.iter().map(|u| u.name).collect::<Vec<_>>().join(", "));

    let avg = active.iter().map(|u| u.age).sum::<u32>() as f64 / active.len() as f64;
    println!("Průměrný věk: {}", avg);
}

Vyzkoušet můžeš tady

Rust iterátorové API je kompaktní, ale stále plné specifik jazyka. Vec<&User> (vektor referencí), collect::<Vec<_>>() (turbofish syntax pro specifikaci typu), sum::<u32>(), as f64 — každé z toho dává smysl v kontextu Rustu, ale dohromady to vytváří čtení, které vyžaduje soustředění. Na rozdíl od předchozích dílů série se zde Rust drží o něco lépe — iterátorové metody jako .filter() a .map() jsou čitelné.

Přehlednost: 4/10

Počet řádků: 28

5. JavaScript

const users = [
    { name: "Alice",   age: 30, active: true  },
    { name: "Bob",     age: 25, active: false },
    { name: "Charlie", age: 35, active: true  },
    { name: "Diana",   age: 28, active: true  },
    { name: "Eve",     age: 22, active: false },
];

const active = users
    .filter(u => u.active)
    .sort((a, b) => a.name.localeCompare(b.name));

console.log(active.map(u => u.name).join(", "));

const avg = active.reduce((sum, u) => sum + u.age, 0) / active.length;
console.log(`Průměrný věk: ${avg}`);

Vyzkoušet můžeš tady

JavaScript array metody jsou přesně to, co by funkcionální API mělo být. .filter(), .sort(), .map(), .reduce() — vše řetězitelné, přirozené, čitelné. Žádné importy, žádné třídy, žádný ceremoniál. Prostě data a operace nad nimi.

Přehlednost: 9/10

Počet řádků: 16

6. Python

users = [
    {"name": "Alice",   "age": 30, "active": True},
    {"name": "Bob",     "age": 25, "active": False},
    {"name": "Charlie", "age": 35, "active": True},
    {"name": "Diana",   "age": 28, "active": True},
    {"name": "Eve",     "age": 22, "active": False},
]

active = sorted(
    filter(lambda u: u["active"], users),
    key=lambda u: u["name"]
)

print(", ".join(u["name"] for u in active))

avg = sum(u["age"] for u in active) / len(active)
print(f"Průměrný věk: {avg}")

Vyzkoušet můžeš tady

Python je opět čistý a přirozený. Generator expressions (u["name"] for u in active) jsou elegantní zkratka pro map. Jedinou výtkou je, že filter() vrací iterátor, takže ho musíš obalit do sorted() — trochu neintuitivní pořadí operací oproti .filter().sort() řetězci v jiných jazycích, za což ztrácí 3 body.

Přehlednost: 7/10

Počet řádků: 17

7. PHP

<?php
$users = [
    ["name" => "Alice",   "age" => 30, "active" => true],
    ["name" => "Bob",     "age" => 25, "active" => false],
    ["name" => "Charlie", "age" => 35, "active" => true],
    ["name" => "Diana",   "age" => 28, "active" => true],
    ["name" => "Eve",     "age" => 22, "active" => false],
];

$active = array_filter($users, fn(array $u): bool => $u["active"]);
usort($active, fn(array $a, array $b): int => $a["name"] <=> $b["name"]);

echo implode(", ", array_column($active, "name")) . "\n";

$avg = array_sum(array_column($active, "age")) / count($active);
echo "Průměrný věk: $avg\n";

Vyzkoušet můžeš tady

PHP má pro práci s poli celou sadu array_* funkcí. array_filter(), array_column(), array_sum() — každá dělá přesně to, co říká. usort() s strcmp() je trochu staromódní, ale funkční. Celkově je kód přehledný, i když postrádá elegantní řetězení jako JavaScript nebo C#.

Přehlednost: 8/10

Počet řádků: 16

Závěr

Jazyk Přehlednost Počet řádků
C++ 2/10 42
C# 8/10 33
Java 6/10 47
Rust 4/10 28
JavaScript 9/10 16
Python 7/10 17
PHP 8/10 16

Vítěz v přehlednosti je jednoznačně JavaScript — čisté řetězení bez importů, bez tříd, bez zbytečností. Těsně za ním sdílejí druhé místo C# a PHP s 8/10, každý jiným způsobem.

V počtu řádků vede JavaScript a PHP na 16 řádcích, Python je těsně za nimi na 17. Na opačném konci se bijí o poslední místo Java (47 řádků) a C++ (42 řádků) — dvě jazyky, které pro jednoduché zpracování pole vyžadují více infrastruktury než samotné řešení.

Rust se vejde na 28 řádků, ale při přehlednosti 4/10 je to jen méně katastrofální výsledek — ne vítězství. Jako kdybys chválil někoho za to, že místo hodiny se mu úloha podařila za půl hodiny, ale stále ji vyřešil špatně.

Tak užívej a já jdu pouštět vodu z pramenů.