জাভা স্ট্রিমগুলি কেন একবার বন্ধ?


239

সি # এর বিপরীতে IEnumerable, যেখানে আমরা যতবার ইচ্ছা এক্সিকিউশন পাইপলাইন কার্যকর করতে পারি, জাভাতে কেবল একবারই একটি স্ট্রিম 'পুনরাবৃত্তি' করা যেতে পারে।

টার্মিনাল অপারেশনে যে কোনও কল স্ট্রিমটি বন্ধ করে দেয় এবং এটিকে অযোগ্য ব্যবহার করে। এই 'বৈশিষ্ট্য' অনেক বেশি শক্তি কেড়ে নেয়।

আমি কল্পনা করি এর কারণটি প্রযুক্তিগত নয় । এই অদ্ভুত বিধিনিষেধের পেছনের নকশা বিবেচনাগুলি কী ছিল?

সম্পাদনা করুন: আমি যা বলছি তা প্রদর্শনের জন্য, সি # তে কুইক-বাছাইয়ের নিম্নলিখিত প্রয়োগটি বিবেচনা করুন:

IEnumerable<int> QuickSort(IEnumerable<int> ints)
{
  if (!ints.Any()) {
    return Enumerable.Empty<int>();
  }

  int pivot = ints.First();

  IEnumerable<int> lt = ints.Where(i => i < pivot);
  IEnumerable<int> gt = ints.Where(i => i > pivot);

  return QuickSort(lt).Concat(new int[] { pivot }).Concat(QuickSort(gt));
}

এখন নিশ্চিত হয়ে উঠতে, আমি দ্রুত পরামর্শ দিচ্ছি যে এটি দ্রুত সাজানোর একটি ভাল বাস্তবায়ন! এটি স্ট্রিম অপারেশনের সাথে মিলিত ল্যাম্বডা এক্সপ্রেশনটির অভিব্যক্তিগত শক্তির দুর্দান্ত উদাহরণ।

এবং জাভাতে এটি করা যায় না! এমনকি কোনও স্ট্রিমটিকে ব্যবহারযোগ্য না করে খালি কিনা তা আমি জিজ্ঞাসাও করতে পারি না।


4
আপনি কী একটি দৃ power় উদাহরণ দিতে পারেন যেখানে প্রবাহটি বন্ধ করে "বিদ্যুৎ কেড়ে নেয়"?
রোগারিও

23
আপনি যদি স্ট্রিম থেকে একাধিকবার ডেটা ব্যবহার করতে চান তবে আপনাকে এটি সংগ্রহের মধ্যে ফেলে দিতে হবে। এই প্রায় কাছাকাছি কিভাবে এটা হয়েছে কাজ করুন: হয় আপনি গণনার পুনরায় করার প্রবাহ তৈরি করার মত, অথবা আপনি অন্তর্বর্তী ফলাফল সংরক্ষণ করতে হবে।
লুই ওয়াসারম্যান

5
ঠিক আছে, তবে একই স্ট্রিমে একই গণনা আবার করা ভুল মনে হচ্ছে। গণনা সম্পাদনের আগে প্রদত্ত উত্স থেকে একটি স্ট্রিম তৈরি করা হয়, যেমন প্রতিটি পুনরাবৃত্তির জন্য পুনরাবৃত্তি তৈরি করা হয়। আমি এখনও একটি বাস্তব কংক্রিট উদাহরণ দেখতে চাই; শেষ পর্যন্ত, আমি বাজি দিয়েছি যে একবার ব্যবহার প্রবাহের সাথে প্রতিটি সমস্যা সমাধানের একটি পরিষ্কার উপায় আছে, ধরে নিচ্ছি যে সি # এর গণনাগুলির সাথে একটি উপযুক্ত উপায় বিদ্যমান।
Rogério

2
এর কারণ হল আমি এই প্রশ্ন কহা হবে সি # S আমাকে প্রথমে বিভ্রান্তিকর ছিল, IEnumerableএর স্ট্রিমjava.io.*
SpaceTrucker

9
মনে রাখবেন যে সি # তে বহুসংখ্যক বার ব্যবহার করা একটি ভঙ্গুর প্যাটার্ন, সুতরাং প্রশ্নের ভিত্তি কিছুটা ত্রুটিযুক্ত হতে পারে। আইনিউমারেবলের অনেকগুলি বাস্তবায়ন এটির অনুমতি দেয় তবে কিছু দেয় না! কোড বিশ্লেষণ সরঞ্জামগুলি আপনাকে এই জাতীয় কোনও কাজ করার বিরুদ্ধে সতর্ক করে দেয়।
স্যান্ডার

উত্তর:


368

আমার কাছে স্ট্রিমস এপিআইয়ের প্রাথমিক নকশা থেকে কিছু পুনরুদ্ধার রয়েছে যা ডিজাইনের যৌক্তিকতার উপর কিছুটা আলোকপাত করতে পারে।

২০১২ সালে, আমরা ভাষায় ল্যাম্বডাস যুক্ত করছিলাম, এবং আমরা ল্যাম্বডাস ব্যবহার করে প্রোগ্রাম করা একটি সংকলন-ভিত্তিক বা "বাল্ক ডেটা" অপারেশনগুলির সেট চাইছিলাম, যা সমান্তরালতা সহজতর করবে। অলসভাবে শৃঙ্খলাবদ্ধ অপারেশনগুলির ধারণাটি এই পয়েন্টটি দ্বারা ভালভাবে প্রতিষ্ঠিত হয়েছিল। আমরা মধ্যবর্তী ক্রিয়াকলাপগুলি ফলাফল সঞ্চয় করতে চাইনি।

আমাদের যে মুখ্য বিষয়গুলি সিদ্ধান্ত নেওয়ার দরকার হয়েছিল তা হ'ল এপিআই-তে চেইনের অবজেক্টগুলি কেমন দেখায় এবং কীভাবে তারা ডেটা উত্সগুলিতে ঝাঁকিয়ে পড়ে। উত্সগুলি প্রায়শই সংগ্রহ ছিল, তবে আমরা কোনও ফাইল বা নেটওয়ার্ক থেকে আসা ডেটা বা ফ্লাই-অন-ফ্লাইতে উত্পন্ন ডেটা, যেমন একটি এলোমেলো সংখ্যা জেনারেটর থেকেও সমর্থন করতে চেয়েছিলাম support

নকশায় বিদ্যমান কাজের অনেক প্রভাব ছিল। আরও প্রভাবশালীগুলির মধ্যে ছিল গুগলের পেয়ারা গ্রন্থাগার এবং স্কালা সংগ্রহ গ্রন্থাগার। (যদি কেউ পেয়ারা থেকে প্রভাব সম্পর্কে অবাক হন, তবে খেয়াল করুন যে পেয়ারা সীসা বিকাশকারী কেভিন বোউরিলিয়ন জেএসআর -৩৩5 ল্যাম্বদা বিশেষজ্ঞ গ্রুপে ছিলেন।) স্কালা সংগ্রহগুলিতে আমরা মার্টিন ওডারস্কির এই আলোচনাটিকে বিশেষ আগ্রহী বলে মনে করেছি: ভবিষ্যত- প্রুফিং স্কেল সংগ্রহ: মিউটেবল থেকে পার্সেন্টাল থেকে সমান্তরালে । (স্ট্যানফোর্ড EE380, 2011 জুন 1)

আমাদের প্রোটোটাইপ ডিজাইনটি প্রায় ভিত্তিক ছিল Iterable। পরিচিত ক্রিয়াকলাপ filter, mapএবং আরও অনেক কিছু ছিল এক্সটেনশন (ডিফল্ট) পদ্ধতিগুলি Iterable। একজনকে ডেকে চেইনে অপারেশন যুক্ত করে অন্যটিকে ফিরিয়ে দিল Iterable। একটি টার্মিনাল অপারেশন যেমন উত্সটিতে চেইনটি countকল iterator()করে, এবং প্রতিটি পর্যায়ের ইটারের মধ্যে অপারেশনগুলি কার্যকর করা হয়েছিল implemented

যেহেতু এগুলি ইটারেবলস, আপনি এই iterator()পদ্ধতিটিকে একাধিকবার কল করতে পারেন । তাহলে কি হবে?

উত্সটি যদি সংগ্রহ হয় তবে এটি বেশিরভাগ ক্ষেত্রে সূক্ষ্মভাবে কাজ করে। সংগ্রহগুলি পর্যালোচনাযোগ্য এবং প্রতিটি কল iterator()একটি স্বতন্ত্র আইট্রেটার উদাহরণ তৈরি করে যা অন্য কোনও সক্রিয় দৃষ্টান্তের চেয়ে স্বতন্ত্র এবং প্রতিটি সংগ্রহ স্বাধীনভাবে পৃথক করে। গ্রেট।

এখন যদি উত্সটি একটি শটযুক্ত হয় তবে কোনও ফাইলের লাইন পড়ার মতো? হতে পারে প্রথম আইট্রেটারের সমস্ত মান পাওয়া উচিত তবে দ্বিতীয় এবং পরবর্তী মানগুলি খালি থাকা উচিত। সম্ভবত মানগুলি ইটারেটরগুলির মধ্যে আন্তঃলিখন করা উচিত। অথবা হতে পারে প্রতিটি আইট্রেটারের একই মানগুলি পাওয়া উচিত। তারপরে, আপনার যদি দুটি পুনরুক্তি থাকে এবং একজন অন্যটির থেকে আরও দূরে হন? যে কেউ পড়া না হওয়া পর্যন্ত দ্বিতীয় আইট্রেটারে মানগুলি বাফার করতে হবে। সবচেয়ে খারাপ, আপনি যদি একজন আইট্রেটার পেয়ে থাকেন এবং সমস্ত মান পড়েন, এবং কেবল তখনই দ্বিতীয় আইট্রেটার পান। এখন থেকে মূল্যবোধগুলি কোথা থেকে আসে? কেউ যদি দ্বিতীয় আইট্রেটার চান কেবল তখনই কি তাদের সকলের জন্য বাফার করা দরকার ?

স্পষ্টতই, এক শট উত্সের মাধ্যমে একাধিক আইট্রেটারকে অনুমতি দেওয়া অনেক প্রশ্ন উত্থাপন করে। আমাদের কাছে তাদের পক্ষে ভাল উত্তর ছিল না। আপনি iterator()দু'বার ফোন করলে কী হয় তার জন্য আমরা ধারাবাহিক, অনুমানযোগ্য আচরণ চাইছিলাম । এটি আমাদের পাইপলাইনগুলিকে এক-শট বানিয়ে একাধিক ট্র্যাভারসালকে অস্বীকার করার দিকে ঠেলে দিয়েছে।

আমরা অন্যদেরও এই বিষয়গুলিতে ঝাঁপিয়ে পড়ে পর্যবেক্ষণ করেছি। জেডিকে, বেশিরভাগ আইটেমেবলগুলি হ'ল সংগ্রহ বা সংগ্রহের মতো বস্তু, যা একাধিক ট্র্যাভারসালকে অনুমতি দেয়। এটি কোথাও সুনির্দিষ্ট করা হয়নি, তবে এমন একটি অলিখিত প্রত্যাশা বলে মনে হয়েছিল যা Iterables একাধিক ট্র্যাভারসালকে অনুমতি দেয়। একটি উল্লেখযোগ্য ব্যতিক্রম হ'ল এনআইও ডিরেক্টরি স্ট্রিম ইন্টারফেস। এর স্পেসিফিকেশনটিতে এই আকর্ষণীয় সতর্কতা অন্তর্ভুক্ত রয়েছে:

ডাইরেক্টরি স্ট্রিমটি স্বল্প পরিসরে প্রসারিত হওয়ার পরে এটি কোনও সাধারণ-উদ্দেশ্যমূলক আইটেবল নয় কারণ এটি কেবলমাত্র একক আইট্রেটরকে সমর্থন করে; দ্বিতীয় বা পরবর্তী পুনরুক্তি পেতে পুনরাবৃত্তি পদ্ধতিটি অনুরোধ করে ইলিজেলস্টেটএক্সেপশন ছুড়ে দেয়।

[মূল সাহসী]

এটিকে যথেষ্ট অস্বাভাবিক এবং অপ্রীতিকর বলে মনে হয়েছিল যে আমরা পুরো নতুন গুটি তৈরি করতে চাইনি যা কেবলমাত্র একবারের জন্য হতে পারে। এটি আমাদের আইটেবল ব্যবহার থেকে দূরে ঠেলে দিয়েছে।

এই সময়ে, ব্রুস এক্কেলের একটি নিবন্ধ প্রকাশিত হয়েছিল যাতে স্কালাকে নিয়ে তিনি যে সমস্যার মুখোমুখি হতেন তা বর্ণনা করেছিল। তিনি এই কোডটি লিখেছিলেন:

// Scala
val lines = fromString(data).getLines
val registrants = lines.map(Registrant)
registrants.foreach(println)
registrants.foreach(println)

এটা বেশ সোজা। এটি টেক্সটের লাইনগুলিকে Registrantবস্তুগুলিতে পার্স করে এবং দু'বার মুদ্রণ করে। এগুলি ব্যতীত এটি কেবল একবারই তাদের প্রিন্ট করে। দেখা যাচ্ছে যে তিনি ভাবেন যে registrantsএটি একটি সংগ্রহ, যখন বাস্তবে এটি পুনরুক্তিকারী। foreachখালি পুনরাবৃত্তির মুখোমুখি হওয়া দ্বিতীয় কল , যা থেকে সমস্ত মান শেষ হয়ে গেছে, তাই এটি কিছুই মুদ্রণ করে না।

এই জাতীয় অভিজ্ঞতা আমাদের বোঝায় যে একাধিক ট্র্যাভারসাল চেষ্টা করা থাকলে পরিষ্কারভাবে অনুমানযোগ্য ফলাফল পাওয়া খুব জরুরি ছিল। এটি তথ্য সংরক্ষণ করে এমন প্রকৃত সংগ্রহ থেকে অলস পাইপলাইনের মতো কাঠামোর মধ্যে পার্থক্যের গুরুত্বকেও তুলে ধরেছে। ফলস্বরূপ এটি অলস পাইপলাইন ক্রিয়াকলাপগুলিকে নতুন স্ট্রিম ইন্টারফেসে বিভক্ত করে এবং কেবল উত্সাহী, মিউটেশনীয় অপারেশনগুলি সরাসরি সংগ্রহে রাখে। ব্রায়ান গয়েট তার পক্ষে যুক্তি ব্যাখ্যা করেছেন

সংগ্রহ-ভিত্তিক পাইপলাইনগুলির জন্য একাধিক ট্র্যাভারসালকে অনুমতি দেওয়া কিন্তু সংগ্রহ-ভিত্তিক পাইপলাইনগুলির জন্য এটি অস্বীকার করার বিষয়ে কী? এটি অসঙ্গতিপূর্ণ, তবে এটি বুদ্ধিমান। আপনি যদি নেটওয়ার্ক থেকে মানগুলি পড়েন তবে অবশ্যই আপনি সেগুলি আর পেরে উঠতে পারবেন না। আপনি যদি এগুলি একাধিকবার অতিক্রম করতে চান তবে আপনাকে সেগুলি স্পষ্টভাবে কোনও সংগ্রহের মধ্যে টানতে হবে।

তবে আসুন সংগ্রহ-ভিত্তিক পাইপলাইনগুলি থেকে একাধিক ট্র্যাভারসালকে মঞ্জুর করে ঘুরে দেখুন। বলুন আপনি এটি করেছেন:

Iterable<?> it = source.filter(...).map(...).filter(...).map(...);
it.into(dest1);
it.into(dest2);

( intoঅপারেশনটি এখন বানানযুক্ত collect(toList())))

যদি উত্সটি কোনও সংগ্রহ হয়, তবে প্রথম into()কলটি উত্সটিতে ফিরে আইট্রেটারের একটি শৃঙ্খল তৈরি করবে, পাইপলাইন ক্রিয়াকলাপ চালাবে এবং ফলাফলগুলি গন্তব্যে প্রেরণ করবে। দ্বিতীয় কলটি into()আইট্রেটারদের আরেকটি চেইন তৈরি করবে এবং আবার পাইপলাইন ক্রিয়াকলাপ চালাবে । এটি স্পষ্টতই ভুল নয় তবে এতে প্রতিটি উপাদানটির জন্য দ্বিতীয়বারের মতো সমস্ত ফিল্টার এবং মানচিত্র পরিচালনা করার প্রভাব রয়েছে। আমি মনে করি অনেক প্রোগ্রামাররা এই আচরণ দেখে অবাক হবেন।

আমি উপরে উল্লিখিত হিসাবে, আমরা পেয়ারা বিকাশকারীদের সাথে কথা বলছিলাম। তাদের কাছে শীতল জিনিসগুলির মধ্যে একটি হ'ল একটি আইডিয়া কবরস্থান যেখানে তারা এমন বৈশিষ্ট্যগুলি বর্ণনা করে যা তারা কারণগুলি সহ কার্যকর না করার সিদ্ধান্ত নিয়েছিল । অলস সংগ্রহগুলির ধারণাটি খুব দুর্দান্ত লাগছে, তবে এ সম্পর্কে তাদের কী বলতে হবে তা এখানে। List.filter()এমন একটি অপারেশন বিবেচনা করুন যা প্রত্যাবর্তন করে List:

এখানে সবচেয়ে বড় উদ্বেগ হ'ল অনেকগুলি ক্রিয়াকলাপ ব্যয়বহুল, লিনিয়ার-সময় প্রস্তাবগুলি হয়ে ওঠে। আপনি যদি কোনও তালিকা ফিল্টার করতে চান এবং একটি তালিকা ফিরে পেতে চান, এবং কেবল কোনও সংগ্রহ বা অপরিবর্তনীয় নয়, আপনি এটি ব্যবহার করতে পারেন ImmutableList.copyOf(Iterables.filter(list, predicate)), এটি কী করছে এবং কত ব্যয়বহুল তা "সামনের দিকে তুলে ধরে"।

একটি নির্দিষ্ট উদাহরণ নিতে, একটি তালিকার দাম get(0)বা size()কী? সাধারণত ব্যবহৃত ক্লাসগুলির জন্য ArrayList, তারা ও (1)। তবে আপনি যদি অলস-ফিল্টারযুক্ত তালিকায় এগুলির একটির কল করেন তবে এটি ফিল্টারটি ব্যাকিং তালিকার উপর দিয়ে চালাতে হবে এবং হঠাৎ এই সমস্ত অপারেশনগুলি হ'ল (এন)। সবচেয়ে খারাপ, এটি প্রতিটি অপারেশনে ব্যাকিং লিস্টকে অতিক্রম করতে হয়।

এটি আমাদের কাছে খুব অলস বলে মনে হয়েছিল । কিছু অপারেশন সেট আপ করা এবং আপনার "Go" না হওয়া পর্যন্ত প্রকৃত বাস্তবায়ন স্থগিত করা এক জিনিস। জিনিসগুলি এমনভাবে সেট আপ করা অন্যটি যা সম্ভাব্য পরিমাণে পুনঃনির্মাণের গোপন করে।

অ-রৈখিক বা "পুনঃব্যবহার" স্ট্রিমগুলি অস্বীকার করার প্রস্তাব দেওয়ার ক্ষেত্রে, পল স্যান্ডোজ তাদের "অপ্রত্যাশিত বা বিভ্রান্তিমূলক ফলাফল" হিসাবে জন্ম দেওয়ার অনুমতি দেওয়ার সম্ভাব্য পরিণতিগুলি বর্ণনা করেছিলেন । তিনি আরও উল্লেখ করেছিলেন যে সমান্তরাল সম্পাদন জিনিসকে আরও জটিল করে তুলবে। পরিশেষে, আমি যুক্ত করব যে পার্শ্ব প্রতিক্রিয়াযুক্ত একটি পাইপলাইন অপারেশনটি অপ্রত্যাশিতভাবে একাধিকবার কার্যকর করা হলে বা প্রোগ্রামারের প্রত্যাশার চেয়ে কমপক্ষে একটি ভিন্ন সংখ্যক বার কার্যকর করা হলে সমস্যাগুলি এবং অস্পষ্ট বাগগুলির দিকে পরিচালিত করবে। (তবে জাভা প্রোগ্রামাররা ল্যাম্বডা এক্সপ্রেশনটি পার্শ্ব প্রতিক্রিয়া সহ লেখেন না, তারা কি? ??

সুতরাং এটি জাভা 8 স্ট্রিমস এপিআই ডিজাইনের জন্য মৌলিক যুক্তি যা ওয়ান-শট ট্র্যাভারসালকে অনুমতি দেয় এবং এর জন্য কঠোরভাবে রৈখিক (কোনও শাখা ছাড়াই) পাইপলাইন প্রয়োজন। এটি একাধিক বিভিন্ন স্ট্রিম উত্স জুড়ে সামঞ্জস্যপূর্ণ আচরণ সরবরাহ করে, এটি অলসভাবে আগ্রহী অপারেশনগুলি থেকে পৃথক করে এবং এটি একটি সরল বাস্তবায়ন মডেল সরবরাহ করে।


সম্পর্কিত IEnumerable, আমি সি # এবং .NET এর বিশেষজ্ঞের থেকে অনেক দূরে, তাই আমি যদি কোনও ভুল উপসংহার আঁকে তবে আমি সংশোধন করা (মৃদুভাবে) প্রশংসা করব। এটি প্রদর্শিত হচ্ছে, IEnumerableযা বিভিন্ন উত্সের সাথে একাধিক ট্র্যাভারসালকে আলাদা আচরণের অনুমতি দেয়; এবং এটি নেস্টেড IEnumerableক্রিয়াকলাপগুলির একটি শাখা কাঠামোকে অনুমতি দেয় , যার ফলে কিছু উল্লেখযোগ্য পুনর্বিবেচনা হতে পারে। যদিও আমি প্রশংসা করি যে বিভিন্ন সিস্টেমগুলি বিভিন্ন ট্রেডঅফ তৈরি করে, এটি দুটি বৈশিষ্ট্য যা আমরা জাভা 8 স্ট্রিমস এপিআইয়ের নকশায় এড়াতে চেয়েছিলাম।

ওপি কর্তৃক প্রদত্ত কুইকোর্টের উদাহরণটি আকর্ষণীয়, বিস্ময়কর এবং আমি কিছুটা ভয়াবহ বলে দুঃখিত। কলিংটি QuickSortএকটি গ্রহণ করে IEnumerableএবং একটি ফেরত দেয় IEnumerable, সুতরাং ফাইনালটি IEnumerableট্র্যাশিং না হওয়া পর্যন্ত কোনও বাছাই করা আসলেই করা হয় না । কলটি যা করছে বলে মনে হচ্ছে তা হল একটি গাছের কাঠামো তৈরি করা IEnumerablesযা বিভাজন প্রতিফলিত করে যা কুইকোর্টটি বাস্তবে না করেই করবে। (এটি সর্বোপরি অলস গণনা)) উত্সটিতে যদি এন উপাদান থাকে তবে গাছটি তার প্রশস্ত প্রস্থে N উপাদানগুলি বিশিষ্ট হবে এবং এটি এলজি (এন) স্তর গভীর হবে।

এটি আমার কাছে মনে হয় - এবং আবারও আমি কোনও সি # বা। ​​নেট বিশেষজ্ঞ নই - কারণ এটি কিছু নিষ্প্রভ ints.First()বর্ণন কলগুলি যেমন পিভট নির্বাচনের মাধ্যমে তাদের চেহারাগুলির চেয়ে ব্যয়বহুল হয়ে উঠবে। প্রথম স্তরে অবশ্যই এটি ও (1)। তবে ডান হাতের প্রান্তে গাছের গভীরে একটি পার্টিশন বিবেচনা করুন। এই পার্টিশনের প্রথম উপাদানটি গণনা করতে, পুরো উত্সকে অতিক্রম করতে হবে, একটি হে (এন) অপারেশন। তবে উপরের পার্টিশনগুলি অলস হওয়ার কারণে সেগুলি অবশ্যই পুনরায় সংশোধন করতে হবে, যার জন্য ও (এলজি এন) তুলনা প্রয়োজন। পিভটটি নির্বাচন করা একটি ও (এন এলজি এন) অপারেশন হবে যা পুরো সাজানোর মতো ব্যয়বহুল।

আমরা যতক্ষণ না ফিরে আসি ততক্ষণ আমরা আসলে বাছাই করি না IEnumerable। স্ট্যান্ডার্ড কুইকোর্টোর্ট অ্যালগরিদমে, পার্টিশনের প্রতিটি স্তর পার্টিশনের সংখ্যা দ্বিগুণ করে। প্রতিটি পার্টিশন আকারের অর্ধেক, তাই প্রতিটি স্তর O (N) জটিলতায় থাকে। পার্টিশনের গাছটি ও (এলজি এন) উচ্চ, সুতরাং মোট কাজটি ও (এন এলজি এন) হয়।

অলস আইনিউমারেবলস গাছের সাথে গাছের নীচে এন পার্টিশন রয়েছে। প্রতিটি পার্টিশন গণনা করার জন্য এন উপাদানগুলির একটি ট্র্যাভারসাল প্রয়োজন, যার প্রত্যেকটিতে গাছের তুলনায় lg (N) প্রয়োজন। গাছের নীচে সমস্ত পার্টিশন গণনা করতে, তারপরে ও (এন ^ 2 এলজি এন) তুলনা প্রয়োজন।

(এটা কি ঠিক? আমি এই কথাটি বিশ্বাস করতে পারি না। কেউ দয়া করে আমার জন্য এটি পরীক্ষা করে দেখুন।)

যাই হোক না কেন, এটি সত্যিই দুর্দান্ত যে IEnumerableগণনার জটিল কাঠামো তৈরি করতে এইভাবে ব্যবহার করা যেতে পারে। তবে এটি যদি কমপিটেশনাল জটিলতা বৃদ্ধি করে যতটা আমার মনে হয় যতটুকু করে, এটি মনে হয় যে এইভাবে প্রোগ্রামিং এমন একটি বিষয় যা কেউ অত্যন্ত সতর্ক না হলে এড়ানো উচিত।


35
প্রথমত, দুর্দান্ত এবং অ-ঘন উত্তরের জন্য আপনাকে ধন্যবাদ! এটি এখন পর্যন্ত সবচেয়ে সঠিক এবং আমি যে পয়েন্ট ব্যাখ্যা পেয়েছি তার কাছে to কুইকসোর্ট উদাহরণটি যতদূর যায়, মনে হচ্ছে আপনি অন্তর্দৃষ্টি সম্পর্কে ঠিক আছেন the পুনরাবৃত্তির স্তর বাড়ার সাথে সাথে প্রথম ফোটানো। আমি বিশ্বাস করি খুব সহজেই 'জিটি' এবং 'এলটি' গণনা করার মাধ্যমে (টোআরয়ের সাথে ফলাফল সংগ্রহ করে) এটি সহজেই ঠিক করা যেতে পারে। বলা হচ্ছে, এটি অবশ্যই আপনার বক্তব্যকে সমর্থন করে যে এই ধরণের প্রোগ্রামিংয়ের ফলে অপ্রত্যাশিত পারফরম্যান্সের দাম পড়তে পারে। (দ্বিতীয় মন্তব্যে চালিয়ে যান)
ভিটালিয়

18
অন্যদিকে, সি # এর সাথে আমার অভিজ্ঞতা থেকে (আরও 5 বছর) আমি বলতে পারি যে 'রিডানড্যান্ট' গণনাগুলি নির্মূল করা এতটা কঠিন নয় যে আপনি একবার কোনও পারফরম্যান্সের সমস্যাটিকে আঘাত করেন (বা নিষেধ পেয়েছেন, যদি কেউ কল্পনাও করা যায় না এবং একটি পরিচয় করিয়ে দেয় তবে পার্শ্ব সেখানে প্রভাবিত)। এটি কেবল আমার কাছে মনে হয়েছিল যে সম্ভাবনার মতো সি # ব্যয় করে এপিআইয়ের খাঁটিতা নিশ্চিত করার জন্য খুব বেশি আপস করা হয়েছিল। আপনি অবশ্যই আমার দৃষ্টিভঙ্গি সামঞ্জস্য করতে সহায়তা করেছেন।
ভিটালি

7
@ ভিটালি ন্যায্য চিন্তাভাবনা বিনিময় করার জন্য ধন্যবাদ। এই উত্তরটি অনুসন্ধান এবং লেখার মাধ্যমে আমি সি # এবং .NET সম্পর্কে কিছুটা শিখেছি।
স্টুয়ার্ট

10
ছোট মন্তব্য: রিসার্পার একটি ভিজ্যুয়াল স্টুডিও এক্সটেনশন যা সি # এর সাথে সহায়তা করে। উপরের কুইকসোর্ট কোডের সাথে রিশার্পার প্রতিটি ব্যবহারের জন্যints একটি সতর্কতা যুক্ত করেছে : "সম্ভাব্য একাধিক গণনার গণনার সংখ্যা"। একই ব্যবহারIEenumerableএকাধিকবার সন্দেহজনক এবং এড়ানো উচিত। আমি এই প্রশ্নটির দিকেও (যা আমি উত্তর দিয়েছি) নির্দেশ করেছিলাম, যা। নেট পদ্ধতির (কিছুটা খারাপ পারফরম্যান্স) সহ কিছু সতর্কতা দেখায়: তালিকা <টি> এবং সংখ্যামূলক পার্থক্য
কোবি

4
@ কোবি অত্যন্ত আকর্ষণীয় যে রিশার্পারে এমন একটি সতর্কতা রয়েছে। আপনার উত্তরের পয়েন্টারটির জন্য ধন্যবাদ। আমি সি # /। নেট জানি না তাই আমাকে এটিকে সতর্কতার সাথে বেছে নিতে হবে, তবে আমি উপরে উল্লিখিত নকশা সংক্রান্ত উদ্বেগের মতো বিষয়গুলি প্রদর্শিত হবে বলে মনে হচ্ছে না।
স্টুয়ার্ট

122

পটভূমি

প্রশ্নটি সহজ দেখা গেলেও প্রকৃত উত্তরের কিছুটা ব্যাকগ্রাউন্ড বোধ করা দরকার। আপনি যদি উপসংহারে যেতে চান তবে নীচে স্ক্রোল করুন ...

আপনার তুলনা বিন্দুটি চয়ন করুন - বেসিক কার্যকারিতা

বুনিয়াদি ধারণা ব্যবহার করে, সি # এর IEnumerableধারণা জাভা সম্পর্কিত আরও ঘনিষ্ঠভাবে সম্পর্কিতIterable যা আপনার ইচ্ছামত বেশি আইট্রেটার তৈরি করতে সক্ষম । IEnumerablesতৈরি IEnumerators। জাভা IterableতৈরিIterators

প্রতিটি ধারণার ইতিহাস একই, উভয়ই IEnumerableএবং Iterableতথ্যের সংগ্রহের সদস্যদের উপর 'প্রত্যেকের জন্য' শৈলী লুপিংয়ের অনুমতি দেওয়ার জন্য একটি প্রাথমিক অনুপ্রেরণা রয়েছে। এটি উভয়ই কেবল এটির চেয়ে বেশি অনুমতি দেয় বলে এটি একটি প্রশংসনীয়করণ এবং তারা বিভিন্ন ধরণের অগ্রগতির মাধ্যমেও এই পর্যায়ে এসে পৌঁছেছিল, তবে এটি একটি গুরুত্বপূর্ণ সাধারণ বৈশিষ্ট্য নির্বিশেষে is

আসুন সেই বৈশিষ্ট্যটি তুলনা করি: উভয় ভাষায়, যদি কোনও শ্রেণি প্রয়োগ করে IEnumerable /Iterable , তবে সেই শ্রেণিকে অবশ্যই কমপক্ষে একটি একক পদ্ধতি প্রয়োগ করতে হবে (সি # এর GetEnumeratorজন্য , এটি জাভাটির জন্য iterator())। প্রতিটি ক্ষেত্রে, উদাহরণটি সেই ( IEnumerator/ Iterator) থেকে ফিরে আসার সাথে সাথে আপনাকে তথ্যের বর্তমান এবং পরবর্তী সদস্যদের অ্যাক্সেস করতে দেয়। এই বৈশিষ্ট্যটি প্রতিটি ভাষার বাক্য বিন্যাসে ব্যবহৃত হয়।

আপনার তুলনা করার পয়েন্টটি চয়ন করুন - বর্ধিত কার্যকারিতা

IEnumerableসি # তে কয়েকটি অন্যান্য ভাষার বৈশিষ্ট্য ( বেশিরভাগ লিনকের সাথে সম্পর্কিত ) অনুমতি দেওয়ার জন্য বাড়ানো হয়েছে । সংযুক্ত বৈশিষ্ট্যগুলির মধ্যে নির্বাচনগুলি, প্রজেকশনগুলি, সমষ্টিগুলি অন্তর্ভুক্ত রয়েছে extension এই এক্সটেনশনের মধ্যে এসকিউএল এবং রিলেশনাল ডেটাবেস ধারণাগুলির মতো সেট-তত্ত্বের ব্যবহার থেকে প্রবল প্রেরণা রয়েছে।

স্ট্রিমস এবং ল্যাম্বডাস ব্যবহার করে কিছুটা ফাংশনাল প্রোগ্রামিং সক্ষম করতে জাভা 8 এর কার্যকারিতাও যুক্ত হয়েছে। নোট করুন যে জাভা 8 স্ট্রিম প্রাথমিকভাবে সেট তত্ত্ব দ্বারা পরিচালিত নয়, তবে ক্রিয়ামূলক প্রোগ্রামিং দ্বারা। নির্বিশেষে, অনেক সমান্তরাল আছে।

সুতরাং, এটি দ্বিতীয় পয়েন্ট। সি # তে করা বর্ধিতকরণগুলি IEnumerableধারণার উন্নতি হিসাবে প্রয়োগ করা হয়েছিল । জাভা কিন্ত, প্রণীত উন্নত Lambdas এবং প্রবাহের নতুন বেস ধারণা তৈরি, এবং তারপর থেকেও রূপান্তর করতে একটি অপেক্ষাকৃত তুচ্ছ পথ তৈরি করে বাস্তবায়িত হয়েছে IteratorsএবংIterables প্রবাহের, এবং ভিসা বিপরীতভাবে।

সুতরাং, জাওয়ার স্ট্রিম ধারণার সাথে আইনিউনামেবলের তুলনা অসম্পূর্ণ। আপনার এটি জাভাতে সংযুক্ত স্ট্রিম এবং সংগ্রহ API এর সাথে তুলনা করতে হবে।

জাভাতে, স্ট্রিমগুলি আইট্রেবলস বা আইট্রেটারগুলির মতো হয় না

স্ট্রিমগুলি সমস্যাগুলি একইভাবে সমাধানের জন্য ডিজাইন করা হয়নি যেমনভাবে পুনরাবৃত্তিকারীরা:

  • আইট্রেটারগুলি ডেটার ক্রম বর্ণনা করার একটি উপায়।
  • স্ট্রিমগুলি ডেটা ট্রান্সফর্মেশনের ক্রম বর্ণনা করার একটি উপায়।

একটি দিয়ে Iteratorআপনি একটি ডেটা মান পাবেন, এটি প্রক্রিয়া করুন এবং তারপরে অন্য একটি ডেটা মান পাবেন।

স্ট্রিমের সাহায্যে আপনি ক্রিয়াকলাপগুলির ক্রম একসাথে চেইন করেন, তারপরে আপনি স্ট্রিমটিতে একটি ইনপুট মান খাওয়ান এবং সম্মিলিত ক্রম থেকে আউটপুট মান পাবেন। দ্রষ্টব্য, জাভা পদগুলিতে প্রতিটি ফাংশন একক Streamদৃষ্টিতে আবদ্ধ হয় ulated স্ট্রিমস এপিআই আপনাকে Streamউদাহরণগুলির ক্রমটিকে এমনভাবে লিঙ্ক করতে দেয় যাতে রূপান্তর অভিব্যক্তিগুলির ক্রমটি শৃঙ্খলাবদ্ধ হয়।

Streamধারণাটি সম্পূর্ণ করার জন্য , আপনার স্ট্রিমটি ফিড করার জন্য ডেটা উত্স এবং স্ট্রিম গ্রাসকারী একটি টার্মিনাল ফাংশন প্রয়োজন।

প্রবাহে আপনি যেভাবে মানগুলি খাওয়ান তা আসলে একটি থেকে হতে পারে Iterableতবে Streamক্রমটি নিজেই একটি নয় Iterable, এটি একটি যৌগিক ফাংশন।

একজন Streamএছাড়াও, অলস হতে অর্থে যে এটা শুধুমাত্র আপনি এটি থেকে একটি মান অনুরোধ যখন কাজ করে এ উদ্দেশ্যে।

স্ট্রিমগুলির এই উল্লেখযোগ্য অনুমান এবং বৈশিষ্ট্যগুলি নোট করুন:

  • একটি Streamজাভা, একটি রূপান্তর ইঞ্জিন এটা এক রাজ্যের একটি ডাটা আইটেমটি রূপান্তরিত অন্য রাজ্যের হচ্ছে।
  • স্ট্রিমগুলির ডেটা অর্ডার বা অবস্থানের কোনও ধারণা নেই, কেবল যা বলা হয় তা রূপান্তরিত করে।
  • স্ট্রিমগুলি অন্যান্য স্ট্রিম, আইট্রেটারস, ইটারেবলস, সংগ্রহগুলি, সহ অনেক উত্স থেকে ডেটা সরবরাহ করা যেতে পারে
  • আপনি কোনও স্ট্রিমকে "রিসেট" করতে পারবেন না, এটি "রূপান্তর পুনর্নির্মাণ" এর মতো। ডেটা উত্সটি পুনরায় সেট করা সম্ভবত আপনি চান।
  • প্রবাহে যে কোনও সময়ে যৌক্তিকভাবে কেবলমাত্র 1 টি ডেটা আইটেম 'ফ্লাইটে' রয়েছে (যদি না স্ট্রিমটি একটি সমান্তরাল স্ট্রিম হয়, যার পয়েন্টে প্রতি থ্রেডে 1 টি আইটেম থাকে)। এটি এমন ডেটা উত্স থেকে স্বতন্ত্র যেখানে স্ট্রিম সরবরাহ করার জন্য বর্তমান আইটেমগুলির 'প্রস্তুত', বা স্ট্রিম সংগ্রাহকের চেয়ে বেশি থাকতে পারে যা একত্রীকরণ এবং একাধিক মানকে হ্রাস করতে পারে।
  • স্ট্রিমগুলি আনবাউন্ড (অসীম) হতে পারে, কেবলমাত্র ডেটা উত্স বা সংগ্রাহক (যা অসীমও হতে পারে) দ্বারা সীমাবদ্ধ।
  • স্ট্রিমগুলি 'চেইনএবল', একটি স্ট্রিম ফিল্টার করার আউটপুট, অন্য স্ট্রিম। কোনও স্ট্রিমের মধ্যে মান ইনপুট এবং রূপান্তরিত হওয়ার পরিবর্তে অন্য স্ট্রমে সরবরাহ করা যেতে পারে যা ভিন্ন রূপান্তর করে। এর রূপান্তরিত অবস্থায় ডেটা এক স্ট্রিম থেকে অন্য প্রবাহে প্রবাহিত হয়। আপনাকে হস্তক্ষেপ করতে হবে না এবং এক স্ট্রিম থেকে ডেটা টানতে হবে এবং এটিকে অন্যটিতে প্লাগ ইন করতে হবে না।

সি # তুলনা

আপনি যখন বিবেচনা করেন যে একটি জাভা স্ট্রিম সরবরাহ, স্ট্রিম এবং সংগ্রহ সিস্টেমের একটি অংশ মাত্র এবং স্ট্রিম এবং আইট্রেটারগুলি প্রায়শই সংগ্রহের সাথে একত্রে ব্যবহৃত হয়, তখন অবাক হওয়ার কিছু নেই যে একই ধারণাগুলির সাথে সম্পর্কিত হওয়া কি কঠিন? প্রায় সবগুলি IEnumerableসি # তে একটি মাত্র ধারণায় এম্বেড করা হয়েছে ।

আইইনুমারেবল (এবং ঘনিষ্ঠ সম্পর্কিত ধারণাগুলি) এর অংশগুলি জাভা আইট্রেটার, আইটেবল, লাম্বদা এবং স্ট্রিম ধারণার সমস্তগুলিতে স্পষ্ট।

জাভা ধারণাগুলি যেগুলি করতে পারে তা ছোট ছোট জিনিস রয়েছে যা আইনিউমারেবল এবং ভিসা-বিপরীতে শক্ত।


উপসংহার

  • এখানে কোনও ডিজাইনের সমস্যা নেই, কেবলমাত্র ভাষার মধ্যে ধারণাগুলির সাথে মিলছে।
  • স্ট্রিমগুলি বিভিন্নভাবে সমস্যার সমাধান করে
  • স্ট্রিমগুলি জাভাতে কার্যকারিতা যুক্ত করে (এগুলি কাজ করার বিভিন্ন উপায় যুক্ত করে, তারা কার্যকারিতা দূরে রাখে না)

সমস্যাগুলি সমাধান করার সময় স্ট্রিম যুক্ত করা আপনাকে আরও পছন্দ দেয়, যা 'শক্তি বাড়ানো' হিসাবে শ্রেণিবদ্ধ করা ন্যায়সঙ্গত, 'হ্রাস', 'দূরে নিয়ে যাওয়া' বা 'সীমাবদ্ধ' নয়।

জাভা স্ট্রিমগুলি কেন একবার বন্ধ?

এই প্রশ্নটি বিপথগামী, কারণ স্ট্রিমগুলি ডেটা নয়, ফাংশন সিকোয়েন্স। স্ট্রিমটি ফিড দেয় এমন ডেটা উত্সের উপর নির্ভর করে আপনি ডেটা উত্সটি পুনরায় সেট করতে পারেন এবং একই বা বিভিন্ন স্ট্রিমটি ফিড করতে পারেন।

সি # এর আইউনুমারেবলের বিপরীতে, যেখানে আমরা যতবার ইচ্ছা এক্সিকিউশন পাইপলাইন কার্যকর করতে পারি, জাভাতে কেবল একবারই একটি স্ট্রিম 'পুনরাবৃত্তি' হতে পারে।

একটি IEnumerableসাথে তুলনা Streamকরা বিপথগামী। আপনি যে প্রসঙ্গটি বলতে IEnumerableচাইছেন সেটি আপনি চান যতবার সম্পাদন করা যেতে পারে, এটি জাভার সাথে তুলনা করা সবচেয়ে ভাল Iterables, যা আপনার ইচ্ছে অনুসারে বহুবার পুনরুক্ত করা যেতে পারে। একটি জাভা ধারণার Streamএকটি উপসেট উপস্থাপন করে IEnumerableএবং উপসেটটি সরবরাহ করে না যা ডেটা সরবরাহ করে এবং এভাবে 'পুনরায় চালানো' যায় না।

টার্মিনাল অপারেশনে যে কোনও কল স্ট্রিমটি বন্ধ করে দেয় এবং এটিকে অযোগ্য ব্যবহার করে। এই 'বৈশিষ্ট্য' অনেক বেশি শক্তি কেড়ে নেয়।

প্রথম বক্তব্যটি এক অর্থে সত্য। 'ক্ষমতা কেড়ে নেয়' বিবৃতিটি নয়। আপনি এখনও স্ট্রিমের সাথে এটি তুলনামূলকভাবে মূল্যায়নযোগ্য n স্ট্রিমের টার্মিনাল অপারেশনটি লুপের জন্য 'ব্রেক' ধারাটির মতো is আপনি চাইলে এবং আপনি যদি প্রয়োজনীয় ডেটা পুনরায় সরবরাহ করতে পারেন তবে আপনি অন্য স্ট্রিমটি রাখতে সর্বদা মুক্ত। আবার, আপনি যদি এই বিবৃতিটির জন্য IEnumerableএকে আরও বেশি পছন্দ Iterableকরেন তবে জাভা এটি ঠিক আছে fine

আমি কল্পনা করি এর কারণটি প্রযুক্তিগত নয়। এই অদ্ভুত বিধিনিষেধের পেছনের নকশা বিবেচনাগুলি কী ছিল?

কারণটি প্রযুক্তিগত এবং সাধারণ কারণে যে স্ট্রিম এটি কী মনে করে তার একটি সাবসেট। স্ট্রিম সাবসেটটি ডেটা সরবরাহ নিয়ন্ত্রণ করে না, তাই আপনার সরবরাহটি পুনরায় সেট করা উচিত, স্ট্রিমটি নয়। সেই প্রসঙ্গে, এটি এত বিস্ময়কর নয়।

কুইকসোর্ট উদাহরণ

আপনার কুইকোর্টের উদাহরণটিতে স্বাক্ষর রয়েছে:

IEnumerable<int> QuickSort(IEnumerable<int> ints)

আপনি ইনপুটটিকে IEnumerableডেটা উত্স হিসাবে বিবেচনা করছেন :

IEnumerable<int> lt = ints.Where(i => i < pivot);

অতিরিক্তভাবে, রিটার্নের মানটিও হ'ল IEnumerableএটি ডেটার সরবরাহ এবং যেহেতু এটি একটি বাছাই করা অপারেশন, সেই সরবরাহের ক্রমটি উল্লেখযোগ্য। আপনি যদি জাভা Iterableক্লাসটিকে এটির জন্য যথাযথ ম্যাচ বলে বিবেচনা করেন , বিশেষত Listবিশেষায়িতকরণ Iterable, যেহেতু তালিকাটি এমন একটি সরবরাহের সরবরাহ যা গ্যারান্টিযুক্ত অর্ডার বা পুনরাবৃত্তি রয়েছে, তবে আপনার কোডের সমতুল্য জাভা কোডটি হ'ল:

Stream<Integer> quickSort(List<Integer> ints) {
    // Using a stream to access the data, instead of the simpler ints.isEmpty()
    if (!ints.stream().findAny().isPresent()) {
        return Stream.of();
    }

    // treating the ints as a data collection, just like the C#
    final Integer pivot = ints.get(0);

    // Using streams to get the two partitions
    List<Integer> lt = ints.stream().filter(i -> i < pivot).collect(Collectors.toList());
    List<Integer> gt = ints.stream().filter(i -> i > pivot).collect(Collectors.toList());

    return Stream.concat(Stream.concat(quickSort(lt), Stream.of(pivot)),quickSort(gt));
}    

নোট করুন যে এখানে একটি বাগ রয়েছে (যা আমি পুনরুত্পাদন করেছি), যাতে সাজানো সদৃশভাবে সদৃশ মানগুলি হ্যান্ডেল করে না, এটি একটি 'অনন্য মান' বাছাই।

এছাড়াও নোট করুন যে জাভা কোড কীভাবে ডেটা সোর্স ( List) এবং বিভিন্ন পর্যায়ে স্ট্রিম ধারণাগুলি ব্যবহার করে এবং সি # তে এই দুটি 'ব্যক্তিত্ব' কেবল প্রকাশিত হতে পারে IEnumerable। এছাড়াও, যদিও আমার Listবেস টাইপ হিসাবে ব্যবহার করা হয়েছে, আমি আরও সাধারণ ব্যবহার করতে পারতাম Collectionএবং একটি ছোট আইট্রেটার-টু-স্ট্রিম রূপান্তর সহ আমি আরও সাধারণ ব্যবহার করতে পারতামIterable


9
আপনি যদি একটি স্ট্রিম 'পুনরাবৃত্তি' করার কথা ভাবছেন তবে আপনি এটি ভুল করছেন। একটি স্ট্রিম রূপান্তরগুলির একটি শৃঙ্খলে সময়ে একটি নির্দিষ্ট সময়ে ডেটার স্থিতি উপস্থাপন করে। স্ট্রিম উত্সে ডেটা সিস্টেমে প্রবেশ করে, তারপরে একটি স্ট্রিম থেকে অন্য প্রবাহে প্রবাহিত হয়, অবস্থার পরিবর্তে স্থিতিশীল হয়ে ওঠে, যতক্ষণ না এটি সংগ্রহ করা হয়, হ্রাস করা হয় বা শেষ পর্যন্ত ডাম্প করা হয়। এ Streamএকটি পয়েন্ট-ইন-টাইম ধারণা, কোনও 'লুপ অপারেশন' নয় .... (
অবিরত

7
একটি স্ট্রিমের সাহায্যে আপনার কাছে এক্সের মতো দেখতে প্রবাহে প্রবেশ করা ডেটা রয়েছে এবং ওয়াইয়ের মতো দেখতে প্রবাহটি প্রস্থান করা হচ্ছে There এমন একটি ফাংশন রয়েছে যা সেই স্ট্রিমটি করে যে রূপান্তরটি সম্পাদন করে f(x)স্ট্রিমটি ফাংশনটিকে আচ্ছন্ন করে, এটি প্রবাহিত ডেটাগুলিকে আবদ্ধ করে না
রলফ্ল

4
IEnumerableএলোমেলো মান সরবরাহ করতে পারে, আনবাউন্ড হতে পারে এবং ডেটা উপস্থিত থাকার আগে সক্রিয় হয়ে যায়।
আর্টুরো টরেস সানচেজ

6
@ ভিটালিয়ি: এমন অনেকগুলি পদ্ধতি যা IEnumerable<T>প্রত্যাশা অর্জন করে এটি একটি সীমাবদ্ধ সংগ্রহের প্রতিনিধিত্ব করে যা একাধিকবার পুনরাবৃত্তি হতে পারে। কিছু জিনিস যা পুনরাবৃত্তিযোগ্য তবে সেই শর্তগুলি বাস্তবায়িত হয় না IEnumerable<T>কারণ অন্য কোনও মানক ইন্টারফেস বিলটি ফিট করে না, তবে যে পদ্ধতিগুলি একাধিকবার পুনরাবৃত্তি হতে পারে এমন সীমাবদ্ধ সংগ্রহের প্রত্যাশার পদ্ধতিগুলি যদি সেই শর্তগুলি মেনে চলেন না যদি পুনরাবৃত্তিযোগ্য জিনিসগুলি দেওয়া হয় তবে ক্রাশ হওয়ার আশঙ্কা রয়েছে ।
সুপারক্যাট

5
আপনার quickSortউদাহরণটি আরও সহজ হতে পারে যদি এটি ফিরে আসে Stream; এটি দুটি .stream()কল এবং একটি .collect(Collectors.toList())কল সংরক্ষণ করতে পারে । তারপরে আপনি যদি কোডটি প্রতিস্থাপন Collections.singleton(pivot).stream()করেন Stream.of(pivot)তবে প্রায় পঠনযোগ্য হয়ে যায় ...
হলগার

22

Streamগুলি গুলি এর চারপাশে নির্মিত Spliteratorযা রাষ্ট্রীয়, পরিবর্তনীয় বস্তু। তাদের "রিসেট" ক্রিয়া নেই এবং প্রকৃতপক্ষে, এই জাতীয় রিওয়াইন্ড ক্রিয়াকে সমর্থন করার জন্য "অনেক বেশি ক্ষমতা কেড়ে নেওয়া" দরকার। কীভাবে এই Random.ints()জাতীয় অনুরোধটি পরিচালনা করবেন?

অন্যদিকে, Streamযেগুলির একটি পুনরুদ্ধারযোগ্য উত্স রয়েছে তাদের জন্য Streamআবার ব্যবহারের জন্য সমতুল্য নির্মাণ করা সহজ । Streamপুনরায় ব্যবহারযোগ্য পদ্ধতিতে নির্মাণের জন্য তৈরি পদক্ষেপগুলি কেবল রেখে দিন । মনে রাখবেন যে এই পদক্ষেপগুলি পুনরাবৃত্তি করা কোনও ব্যয়বহুল ক্রিয়াকলাপ নয় কারণ এই সমস্ত পদক্ষেপগুলি অলস ক্রিয়াকলাপ; প্রকৃত কাজটি টার্মিনাল অপারেশন দিয়ে শুরু হয় এবং প্রকৃত টার্মিনাল অপারেশনের উপর নির্ভর করে সম্পূর্ণ ভিন্ন কোড কার্যকর হতে পারে।

এই পদ্ধতিটির লেখক, এই পদ্ধতিটির লেখক যে বিষয়টি দুটিবার বোঝায় তা নির্দিষ্ট করা আপনার উপর নির্ভর করবে: এটি কি অবিচলিত অ্যারে বা সংগ্রহের জন্য তৈরি প্রবাহগুলি যেমন একই ক্রমটি পুনরুত্পাদন করে, বা এটি একটি স্ট্রিম উত্পাদন করে? অনুরূপ শব্দার্থবিজ্ঞান তবে বিভিন্ন উপাদান যেমন এলোমেলো ints এর প্রবাহ বা কনসোল ইনপুট লাইনগুলির স্ট্রিম ইত্যাদি


উপায় দ্বারা, এড়ানোর বিভ্রান্তির, একটি টার্মিনাল অপারেশন হ্রাসStream যা থেকে স্বতন্ত্র বন্ধেরStream আহ্বানের মত গণ্য করো close()প্রবাহে নেই (যা স্ট্রিম জন্য প্রয়োজন বোধ করা হয়, দ্বারা উত্পাদিত যেমন মত সম্পদ যুক্ত থাকার Files.lines())।


দেখে মনে হচ্ছে এর IEnumerableসাথে তুলনা করার দিক থেকে প্রচুর বিভ্রান্তি সৃষ্টি হয়েছিল Stream। একটি IEnumerableপ্রকৃত প্রদান করার ক্ষমতা প্রতিনিধিত্ব করে IEnumerator, তাই তার একটি মত Iterableজাভা। বিপরীতে, একটি Streamহ'ল এক ধরণের পুনরাবৃত্তিকারী এবং এর সাথে তুলনাযোগ্য IEnumeratorতাই দাবি করা ভুল যে এই জাতীয় ডেটা টাইপ .NET এ একাধিকবার ব্যবহার করা যেতে পারে, এর জন্য সমর্থনটি option IEnumerator.Resetচ্ছিক। এখানে আলোচিত উদাহরণগুলি বরং এই সত্যটি ব্যবহার করে যে একটি নতুন তৈরিIEnumerable করতে ব্যবহার করা যেতে পারে এবং এটি জাভা'র সাথেও কাজ করে; আপনি একটি নতুন পেতে পারেন । যদি জাভা বিকাশকারীরা এটি যুক্ত করার সিদ্ধান্ত নিয়েছে তবে এটি সত্যই তুলনামূলক ছিল এবং এটি একইভাবে কাজ করতে পারে। IEnumeratorCollectionStreamStream অপারেশনগুলিIterable সরাসরি , মধ্যবর্তী ক্রিয়াকলাপগুলি অন্যকে ফিরিয়ে দেয়Iterable

তবে, বিকাশকারীরা এর বিপরীতে সিদ্ধান্ত নিয়েছে এবং এই প্রশ্নে সিদ্ধান্তটি নিয়ে আলোচনা করা হয়েছে । সবচেয়ে বড় বিষয় হ'ল আগ্রহী কালেকশন অপারেশন এবং অলস স্ট্রিম অপারেশন সম্পর্কে বিভ্রান্তি। .NET এপিআই দেখে আমি (হ্যাঁ, ব্যক্তিগতভাবে) এটি ন্যায়সঙ্গত বলে মনে করি। এটি IEnumerableএকা একা দেখার পক্ষে যুক্তিসঙ্গত মনে হলেও , একটি নির্দিষ্ট সংকলনে সরাসরি সংগ্রহটি পরিচালনা করে প্রচুর পদ্ধতি এবং অলস ফিরিয়ে আনার প্রচুর পদ্ধতি থাকবে IEnumerable, যখন কোনও পদ্ধতির নির্দিষ্ট প্রকৃতি সর্বদা স্বজ্ঞাতভাবে স্বীকৃত নয়। সবচেয়ে খারাপ উদাহরণ আমি পেয়েছি (কয়েক মিনিটের মধ্যে আমি এটি দেখেছি) পুরোপুরি বিরোধী আচরণের সাথে List.Reverse()যার নাম উত্তরাধিকার সূত্রে প্রাপ্ত নামের সাথে মিলিত হয় (এটি কি এক্সটেনশন পদ্ধতির সঠিক টার্মিনাস?) Enumerable.Reverse()পুরোপুরি বিপরীতমুখী আচরণ করার পরে।


অবশ্যই, এটি দুটি স্বতন্ত্র সিদ্ধান্ত। প্রথমটি / Streamথেকে পৃথক করে এবং দ্বিতীয়টি এক ধরণের এক সময়ের পুনরাবৃত্তির পরিবর্তে এক ধরণের পুনরাবৃত্ত হয়। তবে এই সিদ্ধান্তগুলি এক সাথে করা হয়েছিল এবং এটি হতে পারে যে এই দুটি সিদ্ধান্তকে আলাদা করার বিষয়টি কখনই বিবেচনা করা হয়নি। এটি নেট নেটকে মাথায় রেখে তুলনাযোগ্য করে তৈরি করা হয়নি।IterableCollectionStream

আসল এপিআই ডিজাইনের সিদ্ধান্তটি ছিল উন্নত ধরণের পুনরুক্তিকারী, যুক্ত করা SpliteratorSpliteratorপুরাতন দ্বারা সরবরাহ করা যেতে পারে Iterable(এটি কীভাবে এগুলি পুনরায় প্রেরণ করা হয়েছিল) বা সম্পূর্ণ নতুন বাস্তবায়ন। তারপরে, Streamবরং নিম্ন স্তরের Spliteratorএসগুলিতে একটি উচ্চ-স্তরের ফ্রন্ট-এন্ড হিসাবে যুক্ত করা হয়েছিল । এটাই. ভিন্ন ডিজাইনটি আরও ভাল হবে কিনা তা নিয়ে আপনি আলোচনা করতে পারেন, তবে এটি উত্পাদনশীল নয়, এখন ডিজাইন করার পদ্ধতি অনুসারে এটি পরিবর্তন হবে না।

আপনার আরও একটি বাস্তবায়ন দিক বিবেচনা করতে হবে। Streamগুলি হয় না অপরিবর্তনীয় ডাটা স্ট্রাকচার। প্রতিটি মধ্যবর্তী ক্রিয়াকলাপ Streamপুরানোটিকে আবদ্ধ করে নতুন উদাহরণ আনতে পারে তবে এটি তার পরিবর্তে নিজস্ব উদাহরণটিও ম্যানিপুলেট করতে পারে এবং নিজেই ফিরে আসতে পারে (এটি একই অপারেশনের জন্য এমনকি উভয়কেই করণীয় নয়)। সাধারণভাবে পরিচিত উদাহরণ অপারেশন মত parallelবা unorderedযা আরেক ধাপ জুড়বেন না বরং সারা পাইপলাইন নিপূণভাবে)। এইরকম একটি পরিবর্তনীয় ডেটা কাঠামো থাকা এবং পুনরায় ব্যবহারের চেষ্টা করা (বা আরও খারাপ, এটি একই সাথে একাধিকবার ব্যবহার করা) ভাল খেলছে না ...


সম্পূর্ণতার জন্য, জাভা Streamএপিআই- তে অনুবাদ করা আপনার কুইকোর্টের উদাহরণ এখানে । এটি দেখায় যে এটি সত্যই "বেশি শক্তি কেড়ে নেয়" না।

static Stream<Integer> quickSort(Supplier<Stream<Integer>> ints) {

  final Optional<Integer> optPivot = ints.get().findAny();
  if(!optPivot.isPresent()) return Stream.empty();

  final int pivot = optPivot.get();

  Supplier<Stream<Integer>> lt = ()->ints.get().filter(i -> i < pivot);
  Supplier<Stream<Integer>> gt = ()->ints.get().filter(i -> i > pivot);

  return Stream.of(quickSort(lt), Stream.of(pivot), quickSort(gt)).flatMap(s->s);
}

এটি ব্যবহার করা যেতে পারে

List<Integer> l=new Random().ints(100, 0, 1000).boxed().collect(Collectors.toList());
System.out.println(l);
System.out.println(quickSort(l::stream)
    .map(Object::toString).collect(Collectors.joining(", ")));

আপনি এটি আরও কমপ্যাক্ট হিসাবে লিখতে পারেন

static Stream<Integer> quickSort(Supplier<Stream<Integer>> ints) {
    return ints.get().findAny().map(pivot ->
         Stream.of(
                   quickSort(()->ints.get().filter(i -> i < pivot)),
                   Stream.of(pivot),
                   quickSort(()->ints.get().filter(i -> i > pivot)))
        .flatMap(s->s)).orElse(Stream.empty());
}

1
ভাল, গ্রাহ্য হয় বা না, আবার এটি গ্রাস করার চেষ্টা করা একটি ব্যতিক্রম ছুঁড়ে দেয় যে স্ট্রিম ইতিমধ্যে বন্ধ ছিল , গ্রাস হয়নি not এলোমেলো পূর্ণসংখ্যার একটি স্ট্রিম পুনরায় সেট করতে সমস্যা হিসাবে, যেমন আপনি বলেছিলেন- একটি রিসেট ক্রিয়াকলাপের সঠিক চুক্তিটি সংজ্ঞায়িত করা গ্রন্থাগারের লেখকের উপর নির্ভর করে।
ভিটালিয়া

2
না, বার্তাটি "স্ট্রিম ইতিমধ্যে চালু হয়েছে বা বন্ধ হয়ে গেছে" এবং আমরা কোনও "রিসেট" অপারেশন সম্পর্কে কথা বলছিলাম না তবে দু'তিন টার্মিনাল অপারেশনগুলিকে ওএনএ কল করছিলাম Streamযেখানে উত্সটির পুনরায় সেট করার বিষয়টি Spliteratorবোঝানো হবে। এবং আমি নিশ্চিত যে এটি সম্ভব হয়েছিল কিনা, তাই এসও নিয়ে প্রশ্ন ছিল যেমন "কেন count()Stream
হোলগার

1
গণনা () এর জন্য বিভিন্ন ফলাফল দেওয়া একেবারেই বৈধ। গণনা () একটি স্ট্রিমের একটি ক্যোয়ারী এবং স্ট্রিমটি যদি পরিবর্তনীয় হয় (বা আরও সঠিকভাবে বলা যায় তবে স্ট্রিমটি একটি পরিবর্তনীয় সংগ্রহের কোনও প্রশ্নের ফলাফলকে উপস্থাপন করে) তবে এটি প্রত্যাশিত। সি # এর এপিআই দেখুন Have তারা এই সমস্ত বিষয় নিষ্ঠার সাথে মোকাবেলা করে।
ভিটালিয়া

4
আপনি যাকে "একেবারে বৈধ" বলছেন তা একটি স্ব-স্বজ্ঞাত আচরণ। সর্বোপরি, ফলাফলটি প্রক্রিয়া করার জন্য একাধিকবার একটি স্ট্রিম ব্যবহার করার বিষয়ে জিজ্ঞাসা করার মূল প্রেরণা, একইরকম, বিভিন্ন উপায়ে প্রত্যাশিত। Streamএর এখনও অ পুনঃব্যবহারযোগ্য প্রকৃতি সম্পর্কে প্রতিটি প্রশ্ন টার্মিনাল অপারেশনগুলিকে একাধিকবার কল করে কোনও সমস্যা সমাধানের প্রচেষ্টা থেকে উদ্ভূত হয়েছে (স্পষ্টতই, অন্যথায় আপনি খেয়াল করেন না) যা Streamএপিআই অনুমতি দিলে নীরবে ভাঙ্গা সমাধানের দিকে নিয়ে যায় প্রতিটি মূল্যায়নের বিভিন্ন ফলাফল সহ। এখানে একটি চমৎকার উদাহরণ
হলগার

3
প্রকৃতপক্ষে, আপনার উদাহরণটি পুরোপুরি প্রমাণ করে যে কোনও প্রোগ্রামার একাধিক টার্মিনাল ক্রিয়াকলাপ প্রয়োগের প্রবন্ধগুলি বুঝতে না পারলে কী ঘটে। এই অপারেশনগুলির প্রতিটি সম্পূর্ণ আলাদা আলাদা উপাদানগুলিতে প্রয়োগ করা হবে তখন কী ঘটেছিল তা ভেবে দেখুন। এটি কেবলমাত্র যদি স্ট্রিমের উত্স প্রতিটি ক্যোয়ারিতে একই উপাদানগুলি ফেরত দেয় তবে এটি কাজ করে তবে এটি হ'ল আমরা যে ভুল অনুমানের কথা বলছিলাম।
হলগার

8

আমি মনে করি আপনি যখন খুব ঘনিষ্ঠভাবে দেখেন তখন দুজনের মধ্যে খুব কম পার্থক্য রয়েছে।

এটির মুখে, IEnumerableএকটিটি পুনরায় ব্যবহারযোগ্য নির্মাণ হিসাবে দেখা দেয়:

IEnumerable<int> numbers = new int[] { 1, 2, 3, 4, 5 };

foreach (var n in numbers) {
    Console.WriteLine(n);
}

তবে সংকলকটি আসলে আমাদের সাহায্য করতে কিছুটা কাজ করছে; এটি নিম্নলিখিত কোড উত্পন্ন করে:

IEnumerable<int> numbers = new int[] { 1, 2, 3, 4, 5 };

IEnumerator<int> enumerator = numbers.GetEnumerator();
while (enumerator.MoveNext()) {
    Console.WriteLine(enumerator.Current);
}

প্রতিবার আপনি যখন প্রকৃতগণের উপরে পুনরাবৃত্তি করবেন, সংকলক একটি গণক তৈরি করে। গণক পুনরায় ব্যবহারযোগ্য নয়; আরও কলMoveNext কেবল মিথ্যা ফিরে আসবে এবং এটিকে শুরুতে পুনরায় সেট করার কোনও উপায় নেই। আপনি যদি আবার সংখ্যাগুলির মাধ্যমে পুনরাবৃত্তি করতে চান তবে আপনাকে অন্য একটি গণক উদাহরণ তৈরি করতে হবে।


জাভা স্ট্রিমের মতো আইনিউমারেবলের একই 'বৈশিষ্ট্য' থাকতে পারে এমনটি আরও ভালভাবে বোঝানোর জন্য, এমন একটি গণনার বিবেচনা করুন যার সংখ্যাগুলির উত্স স্থিতিশীল সংগ্রহ নয়। উদাহরণস্বরূপ, আমরা একটি পরিগণিত অবজেক্ট তৈরি করতে পারি যা 5 টি এলোমেলো সংখ্যার ক্রম উত্পন্ন করে:

class Generator : IEnumerator<int> {
    Random _r;
    int _current;
    int _count = 0;

    public Generator(Random r) {
        _r = r;
    }

    public bool MoveNext() {
        _current= _r.Next();
        _count++;
        return _count <= 5;
    }

    public int Current {
        get { return _current; }
    }
 }

class RandomNumberStream : IEnumerable<int> {
    Random _r = new Random();
    public IEnumerator<int> GetEnumerator() {
        return new Generator(_r);
    }
    public IEnumerator IEnumerable.GetEnumerator() {
        return this.GetEnumerator();
    }
}

পূর্ববর্তী অ্যারে-ভিত্তিক গণনার তুলনায় এখন আমাদের কাছে খুব অনুরূপ কোড রয়েছে তবে দ্বিতীয় পুনরাবৃত্তির সাথে numbers:

IEnumerable<int> numbers = new RandomNumberStream();

foreach (var n in numbers) {
    Console.WriteLine(n);
}
foreach (var n in numbers) {
    Console.WriteLine(n);
}

দ্বিতীয়বার আমরা পুনরাবৃত্তি numbers করব তখন আমরা সংখ্যার একটি পৃথক ক্রম পাব, যা একই অর্থে পুনরায় ব্যবহারযোগ্য নয়। বা, RandomNumberStreamযদি আপনি একাধিকবার এটির পুনরাবৃত্তি করার চেষ্টা করেন, তবে গণনাযোগ্যকে বাস্তবে অযোগ্য ব্যবহারযোগ্য করে তুলবেন (জাভা স্ট্রিমের মতো) an

এছাড়াও, আপনার অঙ্কিত-ভিত্তিক দ্রুত বাছাইয়ের অর্থ কী যখন এ প্রয়োগ করা হয় RandomNumberStream ?


উপসংহার

সুতরাং, সবচেয়ে বড় পার্থক্য হ'ল নেটটি আপনাকে একটি পুনরায় ব্যবহার করতে দেয় IEnumerable স্পষ্টভাবে একটি নতুন তৈরি করেIEnumerator যখনই অনুক্রমের উপাদানগুলিতে অ্যাক্সেস করার প্রয়োজন হবে তখন পটভূমি করতে দেয়।

এই অন্তর্নিহিত আচরণটি প্রায়শই দরকারী (এবং আপনি যেমন বলেছিলেন তেমন 'শক্তিশালী'), কারণ আমরা বার বার সংগ্রহের মাধ্যমে পুনরাবৃত্তি করতে পারি।

তবে কখনও কখনও, এই অন্তর্নিহিত আচরণটি আসলে সমস্যা তৈরি করতে পারে। যদি আপনার ডেটা উত্স স্থিতিশীল না হয় বা অ্যাক্সেস করার জন্য ব্যয়বহুল হয় (যেমন একটি ডাটাবেস বা ওয়েব সাইটের মতো), তবে প্রচুর অনুমানগুলি IEnumerableবাতিল করতে হবে; পুনঃব্যবহার যে সোজা-এগিয়ে হয় না


2

স্ট্রিম এপিআইতে "একবার রান করুন" সুরক্ষাগুলির কিছুকে বাইপাস করা সম্ভব; উদাহরণস্বরূপ java.lang.IllegalStateExceptionএবং সরাসরি ব্যবহারের মাধ্যমে Spliterator( Streamপ্রত্যক্ষের পরিবর্তে ) পুনরায় ব্যবহারের মাধ্যমে উদাহরণস্বরূপ আমরা ব্যতিক্রমগুলি এড়াতে পারি (বার্তা সহ "স্ট্রিম ইতিমধ্যে পরিচালনা করা বা বন্ধ করা হয়েছে" )।

উদাহরণস্বরূপ, এই কোডটি একটি ব্যতিক্রম ছোঁড়া ছাড়াই চলবে:

    Spliterator<String> split = Stream.of("hello","world")
                                      .map(s->"prefix-"+s)
                                      .spliterator();

    Stream<String> replayable1 = StreamSupport.stream(split,false);
    Stream<String> replayable2 = StreamSupport.stream(split,false);


    replayable1.forEach(System.out::println);
    replayable2.forEach(System.out::println);

তবে আউটপুট সীমাবদ্ধ থাকবে

prefix-hello
prefix-world

বরং দুবার আউটপুট পুনরাবৃত্তি করা। এটি কারণ ArraySpliteratorহিসাবে Streamউত্স হিসাবে ব্যবহৃত হয় রাষ্ট্রীয় এবং এটির বর্তমান অবস্থান সংরক্ষণ করে। আমরা এটি পুনরায় প্লে করার সময় আমরা Streamআবার শেষে শুরু করি।

এই চ্যালেঞ্জটি সমাধান করার জন্য আমাদের কাছে অনেকগুলি বিকল্প রয়েছে:

  1. আমরা একটি রাজ্যহীন Streamসৃষ্টি পদ্ধতি যেমন ব্যবহার করতে পারি Stream#generate()। আমাদের নিজস্ব কোডে বাহ্যিকভাবে রাষ্ট্র পরিচালনা করতে হবে এবং Stream"রিপ্লে" এর মধ্যে পুনরায় সেট করতে হবে :

    Spliterator<String> split = Stream.generate(this::nextValue)
                                      .map(s->"prefix-"+s)
                                      .spliterator();
    
    Stream<String> replayable1 = StreamSupport.stream(split,false);
    Stream<String> replayable2 = StreamSupport.stream(split,false);
    
    
    replayable1.forEach(System.out::println);
    this.resetCounter();
    replayable2.forEach(System.out::println);
  2. এর আর একটি (কিছুটা ভাল তবে নিখুঁত নয়) সমাধানটি হ'ল আমাদের নিজস্ব ArraySpliterator(বা অনুরূপ Streamউত্স) লিখতে যা বর্তমান কাউন্টারটিকে পুনরায় সেট করতে কিছু ক্ষমতা অন্তর্ভুক্ত করে। যদি আমরা এটি তৈরি করতে ব্যবহার করি তবে Streamআমরা সম্ভবত তাদের সফলভাবে পুনরায় খেলতে পারি।

    MyArraySpliterator<String> arraySplit = new MyArraySpliterator("hello","world");
    Spliterator<String> split = StreamSupport.stream(arraySplit,false)
                                            .map(s->"prefix-"+s)
                                            .spliterator();
    
    Stream<String> replayable1 = StreamSupport.stream(split,false);
    Stream<String> replayable2 = StreamSupport.stream(split,false);
    
    
    replayable1.forEach(System.out::println);
    arraySplit.reset();
    replayable2.forEach(System.out::println);
  3. এই সমস্যার সর্বোত্তম সমাধানটি (আমার মতে) পাইপলাইনে Spliteratorযে কোনও রাষ্ট্রীয় এর নতুন কপি তৈরি করা Streamযখন নতুন অপারেটরগুলি ডায়াল করা হয় Stream। এটি আরও জটিল এবং বাস্তবায়নের সাথে জড়িত তবে আপনি যদি তৃতীয় পক্ষের গ্রন্থাগারগুলি ব্যবহার করতে আপত্তি করেন না তবে সাইক্লোপস-রিঅ্যাক্টের একটি Streamবাস্তবায়ন রয়েছে যা এটি ঠিক এটি করে। (প্রকাশ: আমি এই প্রকল্পের শীর্ষস্থানীয় বিকাশকারী))

    Stream<String> replayableStream = ReactiveSeq.of("hello","world")
                                                 .map(s->"prefix-"+s);
    
    
    
    
    replayableStream.forEach(System.out::println);
    replayableStream.forEach(System.out::println);

এটি মুদ্রণ করবে

prefix-hello
prefix-world
prefix-hello
prefix-world

প্রত্যাশিত.

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.