सी -11 के लिए अनुक्रम-ज़िप फ़ंक्शन?


99

लूप के लिए नई रेंज-आधारित के साथ हम जैसे कोड लिख सकते हैं

for(auto x: Y) {}

कौन सा आईएमओ (पूर्व से) के लिए एक बड़ा सुधार है

for(std::vector<int>::iterator x=Y.begin(); x!=Y.end(); ++x) {}

क्या इसे पायथन zipफंक्शन की तरह दो एक साथ लूप में लूप किया जा सकता है ? पायथन के साथ अपरिचित लोगों के लिए, कोड:

Y1 = [1,2,3]
Y2 = [4,5,6,7]
for x1,x2 in zip(Y1,Y2):
    print x1,x2

आउटपुट के रूप में देता है (1,4) (2,5) (3,6)


रेंज-आधारित का forउपयोग केवल एक चर के साथ किया जा सकता है, इसलिए नहीं। यदि आप एक बार में दो मूल्यों का उपयोग करना चाहते हैं, तो आपको कुछ उपयोग करना होगा जैसेstd::pair
सेठ कार्नेगी

4
@SethCarnegie: सीधे नहीं, लेकिन आप एक ऐसे zip()कार्य के साथ आ सकते हैं, जो ट्यूपल्स को लौटाता है और ट्यूपल्स की सूची पर पुनरावृति करता है।
एंड्रे कारन

2
@ AndréCaron तुम सही हो, मेरा "नहीं" कहने का मतलब था कि आप दो चर का उपयोग नहीं कर सकते हैं, यह नहीं कि आप एक साथ दो कंटेनरों पर पुनरावृति नहीं कर सकते।
सेठ कार्नेगी

स्पष्ट रूप for(;;)से यह व्यवहार प्राप्त कर सकते हैं, भले ही लंबे समय से हाथ हो, तो क्या वास्तव में सवाल है: क्या एक बार में दो वस्तुओं पर "ऑटो" के लिए संभव है?

भविष्य के संशोधन में (उम्मीद है कि C ++ 17), STL के एक ओवरहाल में सीमाएं शामिल होंगी । फिर देखें :: ज़िप पसंदीदा समाधान प्रदान कर सकता है।
जॉन मैकफर्लेन

जवाबों:


88

चेतावनी: boost::zip_iterator और boost::combineबूस्ट 1.63.0 के रूप में (2016 दिसंबर 26) अपरिभाषित व्यवहार का कारण होगा यदि इनपुट कंटेनरों की लंबाई समान नहीं है (यह अंत से परे दुर्घटना या पुनरावृति कर सकता है)।


बूस्ट १.५६.० (२०१४ अगस्त Bo) से आप उपयोगboost::combine कर सकते हैं (फ़ंक्शन पहले के संस्करणों में मौजूद है, लेकिन अनिर्दिष्ट):

#include <boost/range/combine.hpp>
#include <vector>
#include <list>
#include <string>

int main() {
    std::vector<int> a {4, 5, 6};
    double b[] = {7, 8, 9};
    std::list<std::string> c {"a", "b", "c"};
    for (auto tup : boost::combine(a, b, c, a)) {    // <---
        int x, w;
        double y;
        std::string z;
        boost::tie(x, y, z, w) = tup;
        printf("%d %g %s %d\n", x, y, z.c_str(), w);
    }
}

यह छपेगा

४ a ४ ए
५ 5 बी ५
६ ९ ग ६

पहले के संस्करणों में, आप खुद को इस तरह से परिभाषित कर सकते हैं:

#include <boost/iterator/zip_iterator.hpp>
#include <boost/range.hpp>

template <typename... T>
auto zip(T&&... containers) -> boost::iterator_range<boost::zip_iterator<decltype(boost::make_tuple(std::begin(containers)...))>>
{
    auto zip_begin = boost::make_zip_iterator(boost::make_tuple(std::begin(containers)...));
    auto zip_end = boost::make_zip_iterator(boost::make_tuple(std::end(containers)...));
    return boost::make_iterator_range(zip_begin, zip_end);
}

उपयोग समान है।


1
क्या आप इसे छाँटने के लिए इस्तेमाल कर सकते हैं? यानी std :: सॉर्ट (zip (a.begin (), ...), zip (a.end (), ...), [] (tup a, tup b) {a.get <0> () > b.get <0> ()}); ?
gnzlbg


मैं optionalअतीत के अंत की संभावना संभावनाओं के लिए तत्वों द्वारा लुभाया जाएगा ...
यक - एडम नेवरामॉन्ट

3
किसी भी मौका आप std :: make_tuple और std :: टाई के साथ ऐसा कर सकते हैं? मैं बढ़ावा निर्भरता को कम करते हुए इसका उपयोग करने की कोशिश कर रहा था, लेकिन मैं इसे काम नहीं कर सका।
कार्नेइरो

@kennytm किसी भी विचार क्यों उन्होंने यूबी के साथ जाने का फैसला किया, बजाए सबसे छोटी सीमा के अंत में यूबी के साथ जाने के बजाय?
कैटस्कुल

18

इसलिए मैंने यह ऊब लिखा था जब मैं ऊब गया था, मैंने इसे पोस्ट करने का फैसला किया क्योंकि यह दूसरों की तुलना में अलग है कि यह बूस्ट का उपयोग नहीं करता है और सी ++ stdlib की तरह दिखता है।

template <typename Iterator>
    void advance_all (Iterator & iterator) {
        ++iterator;
    }
template <typename Iterator, typename ... Iterators>
    void advance_all (Iterator & iterator, Iterators& ... iterators) {
        ++iterator;
        advance_all(iterators...);
    } 
template <typename Function, typename Iterator, typename ... Iterators>
    Function zip (Function func, Iterator begin, 
            Iterator end, 
            Iterators ... iterators)
    {
        for(;begin != end; ++begin, advance_all(iterators...))
            func(*begin, *(iterators)... );
        //could also make this a tuple
        return func;
    }

उदाहरण का उपयोग करें:

int main () {
    std::vector<int> v1{1,2,3};
    std::vector<int> v2{3,2,1};
    std::vector<float> v3{1.2,2.4,9.0};
    std::vector<float> v4{1.2,2.4,9.0};
     zip (
            [](int i,int j,float k,float l){
                std::cout << i << " " << j << " " << k << " " << l << std::endl;
            },
            v1.begin(),v1.end(),v2.begin(),v3.begin(),v4.begin());
}

4
आपको यह जांचना चाहिए कि क्या कोई पुनरावृत्तियों के अंत में है।
Xeo

1
@ सभी श्रेणियों का आकार पहले या बड़े आकार का होना चाहिए
aaronman

क्या आप बता सकते हैं कि कैसे [](int i,int j,float k,float l)काम करता है? क्या यह एक मेमना कार्य है?
झुका

@ हाँ, यह एक लंबोदर है, यह मूल रूप से सिर्फ काम करता है, std::for_eachलेकिन आप एक मनमाने ढंग से संख्या का उपयोग कर सकते हैं,
लंबो

1
एक सामान्य आवश्यकता विभिन्न आकारों की सीमाओं को या अनंत सीमाओं के साथ ज़िप करना है।
Xio

16

आप के आधार पर एक समाधान का उपयोग कर सकते हैं boost::zip_iterator। अपने कंटेनरों के संदर्भों को बनाए रखने के लिए एक फोनी कंटेनर क्लास बनाएं, और जो और सदस्य कार्यों zip_iteratorसे लौटते हैं । अब आप लिख सकते हैंbeginend

for (auto p: zip(c1, c2)) { ... }

उदाहरण कार्यान्वयन (कृपया परीक्षण करें):

#include <iterator>
#include <boost/iterator/zip_iterator.hpp>

template <typename C1, typename C2>
class zip_container
{
    C1* c1; C2* c2;

    typedef boost::tuple<
        decltype(std::begin(*c1)), 
        decltype(std::begin(*c2))
    > tuple;

public:
    zip_container(C1& c1, C2& c2) : c1(&c1), c2(&c2) {}

    typedef boost::zip_iterator<tuple> iterator;

    iterator begin() const
    {
         return iterator(std::begin(*c1), std::begin(*c2));
    }

    iterator end() const
    {
         return iterator(std::end(*c1), std::end(*c2));
    }
};

template <typename C1, typename C2>
zip_container<C1, C2> zip(C1& c1, C2& c2)
{
    return zip_container<C1, C2>(c1, c2);
}

मैं पाठक के लिए एक उत्कृष्ट व्यायाम के रूप में वैरेडिक संस्करण छोड़ता हूं।


3
+1: Boost.Range को संभवतः इसे शामिल करना चाहिए। वास्तव में, मैं उन्हें इस पर एक सुविधा अनुरोध छोड़ दूंगा।
निकोल बोलस

2
@ निचलोलस: आप अच्छा करते हैं। यह boost::iterator_range+ के साथ लागू करने के लिए काफी आसान होना चाहिए boost::zip_iterator, यहां तक ​​कि वैरिएडिक संस्करण भी।
अलेक्जेंड्रे सी।

1
मेरा मानना ​​है कि यदि सीमाएं समान लंबाई नहीं हैं, तो यह कभी भी समाप्त नहीं होगा (और अपरिभाषित व्यवहार)।
जोनाथन वेकली

1
boost::zip_iteratorविभिन्न लंबाई की श्रेणियों के साथ काम नहीं करता है
जोनाथन वेकली

1
यह भी tuple के बजाय जोड़ी के साथ साफ c ++ 03 में भी काम करना चाहिए। फिर भी यह wil तब भी समस्याएँ पैदा करता है जब लंबाई बराबर नहीं होती है। सबसे छोटे कंटेनर के संगत अंत () को ले कर अंत () के साथ कुछ किया जा सकता है। ऐसा लगता है कि यह ओप्स सवाल में था।
पॉल 15

16

std :: ट्रांस्फ़ॉर्म यह तुच्छ तरीके से कर सकते हैं:

std::vector<int> a = {1,2,3,4,5};
std::vector<int> b = {1,2,3,4,5};
std::vector<int>c;
std::transform(a.begin(),a.end(), b.begin(),
               std::back_inserter(c),
               [](const auto& aa, const auto& bb)
               {
                   return aa*bb;
               });
for(auto cc:c)
    std::cout<<cc<<std::endl;

यदि दूसरा अनुक्रम छोटा है, तो मेरा कार्यान्वयन डिफ़ॉल्ट आरंभिक मान दे रहा है।


1
यदि दूसरा क्रम छोटा है, तो मुझे उम्मीद है कि यह यूबी है क्योंकि आप इसके अंत से परेशान होंगे b
एड्रियन

15

<redi/zip.h>एक zipफ़ंक्शन के लिए देखें जो रेंज-बेस के साथ काम करता है forऔर किसी भी संख्या में रेंज को स्वीकार करता है, जो rvalues ​​या lvalues ​​हो सकते हैं और अलग-अलग लंबाई हो सकते हैं (यह सबसे छोटी रेंज के अंत में चलना बंद हो जाएगा)।

std::vector<int> vi{ 0, 2, 4 };
std::vector<std::string> vs{ "1", "3", "5", "7" };
for (auto i : redi::zip(vi, vs))
  std::cout << i.get<0>() << ' ' << i.get<1>() << ' ';

प्रिंटों 0 1 2 3 4 5


2
आप boost/tuple/tuple_io.hppcout << i;
kirill_igum

इसी से मेरा काम बना है। हालाँकि, मेरे कोड में मुझे boost::get<0>(i)और के बराबर का उपयोग करना था boost::get<1>(i)। मुझे यकीन नहीं है कि मूल नमूने को सीधे अनुकूलित क्यों नहीं किया जा सकता है, इस तथ्य के साथ करना पड़ सकता है कि मेरा कोड कंटेनरों के लिए निरंतर संदर्भ लेता है।
12

11

साथ सीमा-v3 :

#include <range/v3/all.hpp>
#include <vector>
#include <iostream>

namespace ranges {
    template <class T, class U>
    std::ostream& operator << (std::ostream& os, common_pair<T, U> const& p)
    {
      return os << '(' << p.first << ", " << p.second << ')';
    }
}

using namespace ranges::v3;

int main()
{
    std::vector<int> a {4, 5, 6};
    double b[] = {7, 8, 9};
    std::cout << view::zip(a, b) << std::endl; 
}

उत्पादन:

[(४, (), (५,)), (६, ९)]


@ einpoklum-reinstateMonica अब यह है!
yoyoyuppe

6

मैं स्वतंत्र रूप से इसी प्रश्न में भाग गया और उपरोक्त में से किसी के वाक्य रचना को पसंद नहीं किया। इसलिए, मेरे पास एक छोटी हेडर फ़ाइल है जो अनिवार्य रूप से बूस्ट zip_iterator के समान है लेकिन मेरे पास सिंटैक्स को अधिक स्वादिष्ट बनाने के लिए कुछ मैक्रोज़ हैं:

https://github.com/cshelton/zipfor

उदाहरण के लिए आप कर सकते हैं

vector<int> a {1,2,3};
array<string,3> b {"hello","there","coders"};

zipfor(i,s eachin a,b)
    cout << i << " => " << s << endl;

मुख्य वाक्यविन्यास चीनी यह है कि मैं प्रत्येक कंटेनर से तत्वों का नाम दे सकता हूं। मैं एक "मैपफ़ोर" भी शामिल करता हूं जो ऐसा ही करता है, लेकिन नक्शे के लिए (तत्व के ".first" और ".Second" नाम)।


यह साफ है! क्या यह एक मनमानी संख्या ले सकता है क्या वे सभी आपके चतुर टेंपलेट द्वारा सीमित परिमित संख्या तक सीमित हैं?
हुक

वर्तमान में यह केवल 9 समानांतर कंटेनरों को संभालती है। यह अग्रिम करने के लिए सरल होगा। हालांकि वैरिएड मैक्रोज़ विभिन्न संख्याओं के मापदंडों को संभालने के लिए एकल "ज़िपफ़ोर" मैक्रो के लिए अनुमति देते हैं, फिर भी एक को प्रत्येक के लिए एक अलग मैक्रो को कोडित करना होगा (भेजा जाना)। देखें groups.google.com/forum/?fromgroups=#!topic/comp.std.c/... और stackoverflow.com/questions/15847837/...
cshelton

क्या यह विभिन्न आकार के तर्कों को अच्छी तरह से संभालता है? (जैसा कि ओपी में वर्णित है)
coyotte508

@ coyotte508, यह मानता है कि पहले कंटेनर में तत्वों की संख्या सबसे कम है (और अन्य कंटेनरों में अतिरिक्त तत्वों को अनदेखा करता है)। इस धारणा को न बनाने के लिए संशोधित करना आसान होगा, लेकिन यह तत्वों की संख्या से मेल खाने पर इसे धीमा कर देगा (वर्तमान में यह हाथ से लिखे की तुलना में धीमा नहीं है)।
सीएसहेल्टन


6

यदि आपको ऑपरेटर ओवरलोडिंग पसंद है, तो यहां तीन संभावनाएं हैं। पहले दो का उपयोग कर रहे हैं std::pair<>और std::tuple<>, क्रमशः, पुनरावृत्तियों के रूप में; तीसरा इसे रेंज-आधारित तक बढ़ाता है for। ध्यान दें कि हर कोई ऑपरेटरों की इन परिभाषाओं को पसंद नहीं करेगा, इसलिए उन्हें एक अलग नामस्थान में रखना सबसे अच्छा है और using namespaceफ़ंक्शन में हैं (फाइलें नहीं!) जहां आप इनका उपयोग करना चाहते हैं।

#include <iostream>
#include <utility>
#include <vector>
#include <tuple>

// put these in namespaces so we don't pollute global
namespace pair_iterators
{
    template<typename T1, typename T2>
    std::pair<T1, T2> operator++(std::pair<T1, T2>& it)
    {
        ++it.first;
        ++it.second;
        return it;
    }
}

namespace tuple_iterators
{
    // you might want to make this generic (via param pack)
    template<typename T1, typename T2, typename T3>
    auto operator++(std::tuple<T1, T2, T3>& it)
    {
        ++( std::get<0>( it ) );
        ++( std::get<1>( it ) );
        ++( std::get<2>( it ) );
        return it;
    }

    template<typename T1, typename T2, typename T3>
    auto operator*(const std::tuple<T1, T2, T3>& it)
    {
        return std::tie( *( std::get<0>( it ) ),
                         *( std::get<1>( it ) ),
                         *( std::get<2>( it ) ) );
    }

    // needed due to ADL-only lookup
    template<typename... Args>
    struct tuple_c
    {
        std::tuple<Args...> containers;
    };

    template<typename... Args>
    auto tie_c( const Args&... args )
    {
        tuple_c<Args...> ret = { std::tie(args...) };
        return ret;
    }

    template<typename T1, typename T2, typename T3>
    auto begin( const tuple_c<T1, T2, T3>& c )
    {
        return std::make_tuple( std::get<0>( c.containers ).begin(),
                                std::get<1>( c.containers ).begin(),
                                std::get<2>( c.containers ).begin() );
    }

    template<typename T1, typename T2, typename T3>
    auto end( const tuple_c<T1, T2, T3>& c )
    {
        return std::make_tuple( std::get<0>( c.containers ).end(),
                                std::get<1>( c.containers ).end(),
                                std::get<2>( c.containers ).end() );
    }

    // implement cbegin(), cend() as needed
}

int main()
{
    using namespace pair_iterators;
    using namespace tuple_iterators;

    std::vector<double> ds = { 0.0, 0.1, 0.2 };
    std::vector<int   > is = {   1,   2,   3 };
    std::vector<char  > cs = { 'a', 'b', 'c' };

    // classical, iterator-style using pairs
    for( auto its  = std::make_pair(ds.begin(), is.begin()),
              end  = std::make_pair(ds.end(),   is.end()  ); its != end; ++its )
    {
        std::cout << "1. " << *(its.first ) + *(its.second) << " " << std::endl;
    }

    // classical, iterator-style using tuples
    for( auto its  = std::make_tuple(ds.begin(), is.begin(), cs.begin()),
              end  = std::make_tuple(ds.end(),   is.end(),   cs.end()  ); its != end; ++its )
    {
        std::cout << "2. " << *(std::get<0>(its)) + *(std::get<1>(its)) << " "
                           << *(std::get<2>(its)) << " " << std::endl;
    }

    // range for using tuples
    for( const auto& d_i_c : tie_c( ds, is, cs ) )
    {
        std::cout << "3. " << std::get<0>(d_i_c) + std::get<1>(d_i_c) << " "
                           << std::get<2>(d_i_c) << " " << std::endl;
    }
}

3

एक के लिए सी ++ स्ट्रीम प्रसंस्करण पुस्तकालय मैं लिख रहा हूँ मैं एक समाधान है कि कंटेनरों की एक मनमाना संख्या के साथ तीसरे पक्ष के पुस्तकालयों और काम करता है पर निर्भर नहीं करता लिए देख रहा था। मैं इस समाधान के साथ समाप्त हुआ। यह स्वीकृत समाधान के समान है जो बढ़ावा का उपयोग करता है (और कंटेनर की लंबाई बराबर नहीं होने पर भी अपरिभाषित व्यवहार का परिणाम होता है)

#include <utility>

namespace impl {

template <typename Iter, typename... Iters>
class zip_iterator {
public:
  using value_type = std::tuple<const typename Iter::value_type&,
                                const typename Iters::value_type&...>;

  zip_iterator(const Iter &head, const Iters&... tail)
      : head_(head), tail_(tail...) { }

  value_type operator*() const {
    return std::tuple_cat(std::tuple<const typename Iter::value_type&>(*head_), *tail_);
  }

  zip_iterator& operator++() {
    ++head_; ++tail_;
    return *this;
  }

  bool operator==(const zip_iterator &rhs) const {
    return head_ == rhs.head_ && tail_ == rhs.tail_;
  }

  bool operator!=(const zip_iterator &rhs) const {
    return !(*this == rhs);
  }

private:
  Iter head_;
  zip_iterator<Iters...> tail_;
};

template <typename Iter>
class zip_iterator<Iter> {
public:
  using value_type = std::tuple<const typename Iter::value_type&>;

  zip_iterator(const Iter &head) : head_(head) { }

  value_type operator*() const {
    return value_type(*head_);
  }

  zip_iterator& operator++() { ++head_; return *this; }

  bool operator==(const zip_iterator &rhs) const { return head_ == rhs.head_; }

  bool operator!=(const zip_iterator &rhs) const { return !(*this == rhs); }

private:
  Iter head_;
};

}  // namespace impl

template <typename Iter>
class seq {
public:
  using iterator = Iter;
  seq(const Iter &begin, const Iter &end) : begin_(begin), end_(end) { }
  iterator begin() const { return begin_; }
  iterator end() const { return end_; }
private:
  Iter begin_, end_;
};

/* WARNING: Undefined behavior if iterator lengths are different.
 */
template <typename... Seqs>
seq<impl::zip_iterator<typename Seqs::iterator...>>
zip(const Seqs&... seqs) {
  return seq<impl::zip_iterator<typename Seqs::iterator...>>(
      impl::zip_iterator<typename Seqs::iterator...>(std::begin(seqs)...),
      impl::zip_iterator<typename Seqs::iterator...>(std::end(seqs)...));
}

1
लिंक टूट गया ... यदि पोस्ट दिखाता है कि इसका उपयोग कैसे किया जाए तो यह उपयोगी होगा जैसे कि मुख्य ()?
javaLover

@javaLover: आप इसे @ knedlsepp के उत्तर में cppitertools के समान उपयोग कर सकते हैं। एक उल्लेखनीय अंतर यह है कि उपरोक्त समाधान के साथ आप अंतर्निहित कंटेनरों को संशोधित नहीं कर सकते हैं क्योंकि यह एक संदर्भ के रिटर्न के operator*लिए seq::iteratorहै std::tuple
winnetou

2

यदि आपके पास C ++ 14 अनुरूप संकलक (जैसे gcc5) है तो आप लाइब्रेरी में रयान हेनिंग द्वारा zipप्रदान किया जा सकता है cppitertools, जो वास्तव में आशाजनक लगता है:

array<int,4> i{{1,2,3,4}};
vector<float> f{1.2,1.4,12.3,4.5,9.9};
vector<string> s{"i","like","apples","alot","dude"};
array<double,5> d{{1.2,1.2,1.2,1.2,1.2}};

for (auto&& e : zip(i,f,s,d)) {
    cout << std::get<0>(e) << ' '
         << std::get<1>(e) << ' '
         << std::get<2>(e) << ' '
         << std::get<3>(e) << '\n';
    std::get<1>(e)=2.2f; // modifies the underlying 'f' array
}

0

Boost.Iterators zip_iteratorआप उपयोग कर सकते हैं (उदाहरण के डॉक्स में)। यह सीमा के साथ काम नहीं करेगा, लेकिन आप std::for_eachएक लैम्ब्डा का उपयोग कर सकते हैं ।


यह रेंज-आधारित के साथ काम क्यों नहीं करेगा? इसे Boost.Range के साथ मिलाएं और आपको सेट किया जाना चाहिए।
Xeo

@ Xeo: मैं सीमा को अच्छी तरह से नहीं जानता। मुझे लगता है कि आप कुछ बॉयलरप्लेट को शामिल कर सकते हैं और इसे काम कर सकते हैं, लेकिन IMO का उपयोग for_eachकरने से कम परेशानी होगी।
कैट प्लस प्लस

आप कुछ इस तरह से परेशानी का मतलब है std::for_each(make_zip_iterator(make_tuple(Y1.begin(), Y2.begin())), make_zip_iterator(make_tuple(Y1.end(), Y2.end())), [](const tuple<int, int>& t){printf("%d %d\n", get<0>(t), get<1>(t)); });:?
अंकलबन्स

2
मुझे एक लैम्बडा शुरू करना चाहिए न कि std :: for_each उपयोगी अभियान। :)
अंकलबन्स

2
@ Xeo: यह शायद एक अलग सवाल होना चाहिए, लेकिन क्यों ओह ??
अंकलबन्स

-2

यहां एक सरल संस्करण है जिसे बढ़ावा देने की आवश्यकता नहीं है। यह विशेष रूप से कुशल नहीं होगा क्योंकि यह अस्थायी मूल्य बनाता है, और यह सूचियों के अलावा कंटेनरों पर सामान्यीकरण नहीं करता है, लेकिन इसकी कोई निर्भरता नहीं है और यह ज़िपिंग के लिए सबसे आम मामले को हल करता है।

template<class L, class R>
std::list< std::pair<L,R> >  zip(std::list<L> left, std::list<R> right)
{
auto l = left.begin();
auto r = right.begin();
std::list< std::pair<L,R> > result;
  while( l!=left.end() && r!=right.end() )
    result.push_back( std::pair<L,R>( *(l++), *(r++) ) );
  return result;
}

यद्यपि अन्य संस्करण अधिक लचीले हैं, अक्सर सूची ऑपरेटर का उपयोग करने का बिंदु एक साधारण एक-लाइनर है। इस संस्करण में यह लाभ है कि सामान्य-मामला सरल है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.