31 Mart 2025
Microsoft .NET ekosisteminde çalışan geliştiriciler genellikle Dil Entegre Sorgu'ya (LINQ) yoğun bir şekilde güvenirler. Bu güçlü özellik, çeşitli veri kaynaklarını (koleksiyonlar, veritabanları, XML) C# veya VB.NET'e özgü hissettiren bir sözdizimi kullanarak sorgulamaya olanak tanır. Veri manipülasyonunu zorunlu döngülerden bildirimsel ifadelere dönüştürerek kod okunabilirliğini ve kısalığını artırır. Peki geliştiriciler .NET alanının dışına çıktığında ne olur? Programcılar Python, Java veya C++ gibi dillerde benzer ifade gücü yüksek veri sorgulama yeteneklerini nasıl elde ederler? Neyse ki, LINQ'in temelini oluşturan kavramlar yalnızca .NET'e özgü değildir ve programlama dünyasında güçlü karşılıklar ve alternatifler mevcuttur.
Alternatifleri keşfetmeden önce, LINQ'in neler sunduğunu kısaca hatırlayalım. .NET Framework 3.5 ile tanıtılan LINQ, kaynağı ne olursa olsun verileri sorgulamak için birleşik bir yol sağlar. SQL ifadelerine benzeyen sorgu ifadelerini doğrudan dile entegre eder. Anahtar özellikler şunlardır:
Where
(filtreleme), Select
(projeksiyon/haritalama), OrderBy
(sıralama), GroupBy
(gruplama), Join
, Aggregate
ve daha fazlası gibi zengin bir metot seti.var results = collection.Where(x => x.IsValid).Select(x => x.Name);
yazmanın rahatlığı yadsınamaz. Bakalım diğer diller benzer görevleri nasıl ele alıyor.
Python, dile özgü yerleşik özelliklerden özel kütüphanelere kadar uzanan, LINQ benzeri yetenekler sağlayan çeşitli mekanizmalar sunar. Bu yaklaşımlar, geliştiricilerin filtreleme, haritalama ve toplamayı kısa ve okunabilir bir şekilde gerçekleştirmelerine olanak tanır.
Basit filtreleme (Where
) ve haritalama (Select
) işlemlerini gerçekleştirmenin Python'a en özgü yolu genellikle liste üretimleri veya üreteç ifadeleri kullanmaktır.
numbers = [1, 2, 3, 4, 5, 6]
# LINQ: numbers.Where(n => n % 2 == 0).Select(n => n * n)
squared_evens = [n * n for n in numbers if n % 2 == 0]
# Sonuç: [4, 16, 36]
numbers = [1, 2, 3, 4, 5, 6]
# LINQ: numbers.Where(n => n % 2 == 0).Select(n => n * n)
squared_evens_gen = (n * n for n in numbers if n % 2 == 0)
# Sonuçları almak için üzerinde yineleme yaparsınız (örneğin, list(squared_evens_gen))
# Değerler yalnızca yineleme sırasında gerektiğinde hesaplanır.
itertools
Birçok standart LINQ operatörünün Python'un yerleşik fonksiyonlarında veya güçlü itertools
modülünde doğrudan veya yakın karşılıkları vardır:
any()
, all()
: Öğeler arasında koşulları kontrol etmek için LINQ'in Any
ve All
metotlarına doğrudan karşılık gelir.
fruit = ['apple', 'orange', 'banana']
# LINQ: fruit.Any(f => f.Contains("a"))
any_a = any("a" in f for f in fruit) # True
# LINQ: fruit.All(f => f.Length > 3)
all_long = all(len(f) > 3 for f in fruit) # True
min()
, max()
, sum()
: LINQ'in toplama metotlarına benzer. Doğrudan yinelenebilirler üzerinde çalışabilir veya bir üreteç ifadesi alabilir.
numbers = [1, 5, 2, 8, 3]
# LINQ: numbers.Max()
maximum = max(numbers) # 8
# LINQ: numbers.Where(n => n % 2 != 0).Sum()
odd_sum = sum(n for n in numbers if n % 2 != 0) # 1 + 5 + 3 = 9
filter()
, map()
: Where
ve Select
'in fonksiyonel karşılıklarıdır. Python 3'te yineleyiciler döndürerek tembel değerlendirmeyi teşvik ederler.
numbers = [1, 2, 3, 4]
# LINQ: numbers.Where(n => n > 2)
filtered_iter = filter(lambda n: n > 2, numbers) # yineleme üzerine 3, 4 üretir
# LINQ: numbers.Select(n => n * 2)
mapped_iter = map(lambda n: n * 2, numbers) # yineleme üzerine 2, 4, 6, 8 üretir
sorted()
: OrderBy
'a karşılık gelir. Sıralama ölçütlerini belirtmek için isteğe bağlı bir key
fonksiyonu alır ve yeni sıralanmış bir liste döndürür.
fruit = ['pear', 'apple', 'banana']
# LINQ: fruit.OrderBy(f => f.Length)
sorted_fruit = sorted(fruit, key=len) # ['pear', 'apple', 'banana']
itertools.islice(iterable, stop)
veya itertools.islice(iterable, start, stop[, step])
: Take
ve Skip
'i uygular. Bir yineleyici döndürür.
from itertools import islice
numbers = [0, 1, 2, 3, 4, 5]
# LINQ: numbers.Take(3)
first_three = list(islice(numbers, 3)) # [0, 1, 2]
# LINQ: numbers.Skip(2)
skip_two = list(islice(numbers, 2, None)) # [2, 3, 4, 5]
# LINQ: numbers.Skip(1).Take(2)
skip_one_take_two = list(islice(numbers, 1, 3)) # [1, 2]
itertools.takewhile()
, itertools.dropwhile()
: TakeWhile
ve SkipWhile
'a eşdeğerdir, bir yükleme göre çalışır.
from itertools import takewhile, dropwhile
numbers = [2, 4, 6, 7, 8, 10]
# LINQ: numbers.TakeWhile(n => n % 2 == 0)
take_evens = list(takewhile(lambda n: n % 2 == 0, numbers)) # [2, 4, 6]
# LINQ: numbers.SkipWhile(n => n % 2 == 0)
skip_evens = list(dropwhile(lambda n: n % 2 == 0, numbers)) # [7, 8, 10]
itertools.groupby()
: GroupBy
'a benzer, ancak öğelerin doğru şekilde gruplanabilmesi için girdi yinelenebilirinin önce gruplama anahtarına göre sıralanmasını gerektirir. (anahtar, grup_yineleyici)
çiftleri üreten bir yineleyici döndürür.
from itertools import groupby
fruit = ['apple', 'apricot', 'banana', 'blueberry', 'cherry']
# Çoğu durumda groupby'ın beklendiği gibi çalışması için önce anahtara göre sıralama YAPILMALIDIR
keyfunc = lambda f: f[0] # İlk harfe göre grupla
sorted_fruit = sorted(fruit, key=keyfunc)
# LINQ: fruit.GroupBy(f => f[0])
grouped_fruit = groupby(sorted_fruit, key=keyfunc)
for key, group_iter in grouped_fruit:
print(f"{key}: {list(group_iter)}")
# Çıktı:
# a: ['apple', 'apricot']
# b: ['banana', 'blueberry']
# c: ['cherry']
set()
: Distinct
için kullanılabilir, ancak orijinal sırayı korumaz.
numbers = [1, 2, 2, 3, 1, 4, 3]
# LINQ: numbers.Distinct()
distinct_numbers_set = set(numbers) # Sıra garanti edilmez, örn. {1, 2, 3, 4}
distinct_numbers_list = list(distinct_numbers_set) # örn. [1, 2, 3, 4]
# Sırayı koruyan benzersizleştirme için:
seen = set()
distinct_ordered = [x for x in numbers if not (x in seen or seen.add(x))] # [1, 2, 3, 4]
py-linq
Kütüphanesi.NET LINQ'in belirli metot zincirleme sözdizimini ve adlandırma kurallarını tercih eden geliştiriciler için py-linq
kütüphanesi doğrudan bir uyarlama sunar. Kurulumdan sonra (pip install py-linq
), koleksiyonunuzu bir Enumerable
nesnesi içine alırsınız.
from py_linq import Enumerable
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __repr__(self):
return f'{self.name} ({self.age})'
people = [Person('Alice', 30), Person('Bob', 20), Person('Charlie', 25)]
e = Enumerable(people)
# LINQ: people.Where(p => p.age > 21).OrderBy(p => p.name).Select(p => p.name)
results = e.where(lambda p: p.age > 21)\
.order_by(lambda p: p.name)\
.select(lambda p: p.name)\
.to_list()
# Sonuç: ['Alice', 'Charlie']
# Count örneği
# LINQ: people.Count(p => p.age < 25)
young_count = e.count(lambda p: p.age < 25) # 1 (Bob)
py-linq
kütüphanesi, standart sorgu operatörlerinin büyük bir bölümünü uygulayarak .NET geliştirmeden geçiş yapan veya onunla birlikte çalışanlar için tanıdık bir arayüz sağlar.
pipe
kütüphanesi başka bir alternatiftir ve bazı geliştiricilerin karmaşık veri akışları için son derece okunabilir ve etkileyici bulduğu zincirleme işlemler için pipe operatörünü (|
) kullanan fonksiyonel bir yaklaşım sunar.
Java 8'den beri, Java'daki birincil ve dile özgü LINQ karşılığı kesinlikle Streams API'sidir (java.util.stream
). LINQ'in felsefesini ve yeteneklerini yakından yansıtan, öğe dizilerini işlemek için akıcı, bildirimsel bir yol sunarak LINQ benzeri özellikleri standart kütüphane içinde gerçeğe dönüştürür.
list.stream()
), diziler (Arrays.stream(array)
), G/Ç kanalları veya üreteç fonksiyonları (Stream.iterate
, Stream.generate
) gibi veri kaynakları üzerinde çalışır.filter
(Where), map
(Select), sorted
(OrderBy), distinct
, limit
(Take), skip
(Skip), reduce
(Aggregate), collect
(ToList, ToDictionary, vb.) gibi zengin bir işlem setini destekler.filter
, map
, sorted
gibi) yeni bir akış döndürür, bu da sorguyu temsil eden bir ardışık düzen oluşturmak üzere birbirine zincirlenmelerine olanak tanır.limit
, anyMatch
, findFirst
gibi) sonuç belirlendiğinde işlemeyi erken durdurabilir, bu da verimliliği artırır.collect
, count
, sum
, findFirst
, anyMatch
) veya bir yan etki (örneğin, forEach
) üretir.Java Streams kullanarak LINQ karşılıklarına bakalım:
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import static java.util.Comparator.comparing;
// Örnek veri sınıfı
class Transaction {
int id; String type; int value;
Transaction(int id, String type, int value) { this.id = id; this.type = type; this.value = value; }
int getId() { return id; }
String getType() { return type; }
int getValue() { return value; }
@Override public String toString() { return "ID:" + id + " Type:" + type + " Value:" + value; }
}
public class StreamExample {
public static void main(String[] args) {
List<Transaction> transactions = Arrays.asList(
new Transaction(1, "GROCERY", 50),
new Transaction(2, "UTILITY", 150),
new Transaction(3, "GROCERY", 75),
new Transaction(4, "RENT", 1200),
new Transaction(5, "GROCERY", 25)
);
// --- Filtreleme (Where) ---
// LINQ: transactions.Where(t => t.getType() == "GROCERY")
List<Transaction> groceryTransactions = transactions.stream()
.filter(t -> "GROCERY".equals(t.getType()))
.collect(Collectors.toList());
// Sonuç: ID'leri 1, 3, 5 olan işlemleri içerir
// --- Haritalama (Select) ---
// LINQ: transactions.Select(t => t.getId())
List<Integer> transactionIds = transactions.stream()
.map(Transaction::getId) // Metot referansı kullanarak
.collect(Collectors.toList());
// Sonuç: [1, 2, 3, 4, 5]
// --- Sıralama (OrderBy) ---
// LINQ: transactions.OrderByDescending(t => t.getValue())
List<Transaction> sortedByValueDesc = transactions.stream()
.sorted(comparing(Transaction::getValue).reversed())
.collect(Collectors.toList());
// Sonuç: Değere göre azalan sırada sıralanmış işlemler: [ID:4, ID:2, ID:3, ID:1, ID:5]
// --- İşlemleri Birleştirme ---
// Market işlemlerinin ID'lerini bul, değere göre azalan sırada sırala
// LINQ: transactions.Where(t => t.getType() == "GROCERY").OrderByDescending(t => t.getValue()).Select(t => t.getId())
List<Integer> groceryIdsSortedByValueDesc = transactions.stream()
.filter(t -> "GROCERY".equals(t.getType())) // Where
.sorted(comparing(Transaction::getValue).reversed()) // OrderByDescending
.map(Transaction::getId) // Select
.collect(Collectors.toList()); // Yürüt ve topla
// Sonuç: [3, 1, 5] (75, 50, 25 değerlerine karşılık gelen ID'ler)
// --- Diğer Yaygın İşlemler ---
// AnyMatch
// LINQ: transactions.Any(t => t.getValue() > 1000)
boolean hasLargeTransaction = transactions.stream()
.anyMatch(t -> t.getValue() > 1000); // true (KİRA işlemi)
// FindFirst / FirstOrDefault karşılığı
// LINQ: transactions.FirstOrDefault(t => t.getType() == "UTILITY")
Optional<Transaction> firstUtility = transactions.stream()
.filter(t -> "UTILITY".equals(t.getType()))
.findFirst(); // ID:2 işlemini içeren Optional döndürür
firstUtility.ifPresent(t -> System.out.println("Bulundu: " + t)); // Varsa bulunan işlemi yazdırır
// Count
// LINQ: transactions.Count(t => t.getType() == "GROCERY")
long groceryCount = transactions.stream()
.filter(t -> "GROCERY".equals(t.getType()))
.count(); // 3
// Sum (verimlilik için özel sayısal akışlar kullanarak)
// LINQ: transactions.Sum(t => t.getValue())
int totalValue = transactions.stream()
.mapToInt(Transaction::getValue) // IntStream'e dönüştür
.sum(); // 1500
System.out.println("Toplam değer: " + totalValue);
}
}
Java Akışları, çok çekirdekli işlemcilerde potansiyel performans kazanımları için sadece .stream()
yerine .parallelStream()
kullanarak kolayca paralelleştirilebilir. Streams API, görev ayrıştırmayı ve iş parçacığı yönetimini dahili olarak yönetir.
// Örnek: Paralel filtreleme ve haritalama
List<Integer> parallelResult = transactions.parallelStream() // Paralel akış kullan
.filter(t -> t.getValue() > 100) // Paralel olarak işlendi
.map(Transaction::getId) // Paralel olarak işlendi
.collect(Collectors.toList()); // Sonuçları birleştirir
// Sonuç: [2, 4] (Sıralama, toplamadan önceki sıralı akışa göre değişebilir)
Paralelleştirmenin ek yük getirdiğini ve özellikle basit işlemler veya küçük veri kümeleri için her zaman daha hızlı olmadığını unutmayın. Kıyaslama yapılması önerilir.
Java 8 Streams, Java'daki standart ve genellikle tercih edilen LINQ karşılığı olsa da, başka kütüphaneler de mevcuttur:
C++, .NET'in LINQ'i veya Java'nın Streams'i ile doğrudan karşılaştırılabilir, dile entegre bir sorgu özelliğine sahip değildir. Ancak, C++'ta bir LINQ karşılığı arayan veya LINQ desenlerini uygulamak isteyen geliştiriciler, standart kütüphane özelliklerinin, güçlü üçüncü taraf kütüphanelerin ve modern C++ deyimlerinin bir kombinasyonunu kullanarak benzer sonuçlar elde edebilirler.
<algorithm>
ve <numeric>
başlık dosyaları, yineleyici aralıkları (begin
, end
) üzerinde çalışan temel bir fonksiyon araç seti sağlar. Bunlar, C++'ta veri işleme için yapı taşlarıdır.
#include <vector>
#include <numeric>
#include <algorithm>
#include <iostream>
#include <string>
#include <iterator> // std::back_inserter için
struct Product {
int id;
double price;
std::string category;
};
int main() {
std::vector<Product> products = {
{1, 10.0, "A"}, {2, 25.0, "B"}, {3, 5.0, "A"}, {4, 30.0, "A"}
};
// --- Filtreleme (Where) ---
// LINQ: products.Where(p => p.category == "A")
std::vector<Product> categoryA;
std::copy_if(products.begin(), products.end(), std::back_inserter(categoryA),
[](const Product& p){ return p.category == "A"; });
// categoryA şimdi 1, 3, 4 ID'li ürünleri içeriyor
// --- Haritalama (Select) ---
// LINQ: products.Select(p => p.price)
std::vector<double> prices;
prices.reserve(products.size()); // Yer ayır
std::transform(products.begin(), products.end(), std::back_inserter(prices),
[](const Product& p){ return p.price; });
// prices şimdi [10.0, 25.0, 5.0, 30.0] içeriyor
// --- Sıralama (OrderBy) ---
// LINQ: products.OrderBy(p => p.price)
// Not: std::sort orijinal konteyneri değiştirir
std::vector<Product> sortedProducts = products; // Sıralamak için bir kopya oluştur
std::sort(sortedProducts.begin(), sortedProducts.end(),
[](const Product& a, const Product& b){ return a.price < b.price; });
// sortedProducts şimdi: [ {3, 5.0}, {1, 10.0}, {2, 25.0}, {4, 30.0} ]
// --- Toplama (Sum) ---
// LINQ: products.Where(p => p.category == "A").Sum(p => p.price)
double sumCategoryA = std::accumulate(products.begin(), products.end(), 0.0,
[](double current_sum, const Product& p){
return (p.category == "A") ? current_sum + p.price : current_sum;
});
// sumCategoryA = 10.0 + 5.0 + 30.0 = 45.0
// --- Bulma (FirstOrDefault karşılığı) ---
// LINQ: products.FirstOrDefault(p => p.id == 3)
auto found_it = std::find_if(products.begin(), products.end(),
[](const Product& p){ return p.id == 3; });
if (found_it != products.end()) {
std::cout << "ID 3'lu ürün bulundu, fiyat: " << found_it->price << std::endl;
} else {
std::cout << "ID 3'lu ürün bulunamadı." << std::endl;
}
return 0;
}
Güçlü ve verimli olmalarına rağmen, STL algoritmalarını doğrudan kullanmak ayrıntılı olabilir. Zincirleme işlemler genellikle ara konteynerler oluşturmayı veya daha karmaşık funktor kompozisyon teknikleri kullanmayı gerektirir.
Eric Niebler'ın range-v3
(C20'de tanıtılan standart std::ranges
'ı büyük ölçüde etkileyen) gibi modern C kütüphaneleri, LINQ veya Java Streams'e ruhen çok daha yakın olan, birleştirilebilir, pipe tabanlı (|
) bir sözdizimi sağlar.
#include <vector>
#include <string>
#include <iostream>
#ifdef USE_RANGES_V3 // range-v3 kullanıyorsanız bunu tanımlayın, aksi takdirde std::ranges kullanın
#include <range/v3/all.hpp>
namespace ranges = ::ranges;
#else // <ranges> destekli C++20 veya üstü varsayılıyor
#include <ranges>
#include <numeric> // ranges ile accumulate için
namespace ranges = std::ranges;
namespace views = std::views;
#endif
// Önceki örnekteki Product yapısı varsayılıyor...
int main() {
std::vector<Product> products = {
{1, 10.0, "A"}, {2, 25.0, "B"}, {3, 5.0, "A"}, {4, 30.0, "A"}
};
// LINQ: products.Where(p => p.category == "A").Select(p => p.price).Sum()
auto categoryAView = products
| ranges::views::filter([](const Product& p){ return p.category == "A"; })
| ranges::views::transform([](const Product& p){ return p.price; });
#ifdef USE_RANGES_V3
double sumCategoryA_ranges = ranges::accumulate(categoryAView, 0.0);
#else // C++20 std::ranges, accumulate için açık begin/end gerektirir
double sumCategoryA_ranges = std::accumulate(categoryAView.begin(), categoryAView.end(), 0.0);
#endif
std::cout << "A Kategorisi Toplamı (ranges): " << sumCategoryA_ranges << std::endl; // 45.0
// LINQ: products.Where(p => p.price > 15).OrderBy(p => p.id).Select(p => p.id)
// Not: Ranges ile sıralama genellikle önce bir konteynere toplamayı gerektirir,
// veya uygunsa ve mevcutsa belirli range eylemlerini/algoritmalarını kullanmayı gerektirir.
auto expensiveProducts = products
| ranges::views::filter([](const Product& p){ return p.price > 15.0; });
// Sıralamak için bir vektöre topla
std::vector<Product> expensiveVec;
#ifdef USE_RANGES_V3
ranges::copy(expensiveProducts, std::back_inserter(expensiveVec));
#else
ranges::copy(expensiveProducts.begin(), expensiveProducts.end(), std::back_inserter(expensiveVec));
#endif
ranges::sort(expensiveVec, [](const Product& a, const Product& b){ return a.id < b.id; }); // Vektörü sırala
auto ids_expensive_sorted = expensiveVec
| ranges::views::transform([](const Product& p){ return p.id; }); // ID'lerin görünümünü oluştur
std::cout << "Pahalı Ürün ID'leri (Sıralı): ";
for(int id : ids_expensive_sorted) { // Son görünüm üzerinde yinele
std::cout << id << " "; // 2 4
}
std::cout << std::endl;
return 0;
}
Range kütüphaneleri, geleneksel STL algoritmalarına kıyasla önemli ölçüde geliştirilmiş ifade gücü, tembellik (görünümler aracılığıyla) ve birleştirilebilirlik sunarak C++'ta güçlü LINQ karşılığı adaylarıdır.
Birkaç üçüncü taraf kütüphanesi, LINQ sözdizimini doğrudan C++'ta taklit etmeyi özellikle amaçlar:
from
, where
, select
, orderBy
, vb.) sağlayan, yalnızca başlık dosyası içeren bir kütüphane.Bu kütüphaneler, C# LINQ'e zaten aşina olan geliştiriciler için çekici olabilir. Ancak, harici bağımlılıklar getirirler ve her zaman standart C++ uygulamalarıyla sorunsuz bir şekilde entegre olmayabilir veya standart algoritmalar ya da köklü range kütüphaneleriyle aynı potansiyel performans optimizasyonlarını sunmayabilirler.
Koleksiyonları bildirimsel olarak sorgulama temel kavramı yaygındır:
filter()
, map()
, reduce()
, sort()
, find()
, some()
, every()
gibi güçlü Dizi metotları sunar. lodash
gibi kütüphaneler daha da kapsamlı yardımcı programlar sağlar.grep
(filtreleme için) ve map
(dönüştürme için) gibi temel fonksiyonlar, temel liste işleme yetenekleri sağlar.array_filter
, array_map
, array_reduce
) ve nesne yönelimli koleksiyon kütüphaneleri (örneğin, Laravel Koleksiyonları, Doctrine Koleksiyonları) benzer bildirimsel veri işleme özellikleri sunar.LINQ'in temel prensipleri - bildirimsel veri sorgulama, fonksiyonel dönüşümler, tembel değerlendirme ve birleştirilebilirlik - .NET ile sınırlı değildir. Java, Streams API aracılığıyla güçlü bir standart çözüm sunar. Python geliştiricileri yerleşik üretimleri (comprehensions), itertools
modülünü ve py-linq
gibi kütüphaneleri kullanır. C++ programcıları STL algoritmalarını, modern range kütüphanelerini (std::ranges
, range-v3
) veya özel LINQ emülasyon kütüphanelerini kullanabilirler.
Asıl değer sözdiziminde değil, bu kavramları temiz ve verimli veri işleme için evrensel bir araç seti olarak tanımakta yatar. Anlaşıldıktan sonra, ister Java, Python, C++ isterse bildirimsel paradigmaları benimseyen herhangi bir dilde kod yazıyor olun, aktarılabilir hale gelirler.