لچکدار تلاش کی بنیادی باتیں

Elasticsearch json rest api کے ساتھ ایک سرچ انجن ہے، جس میں Lucene کا استعمال ہوتا ہے اور جاوا میں لکھا جاتا ہے۔ اس انجن کے تمام فوائد کی تفصیل یہاں دستیاب ہے۔ سرکاری ویب سائٹ. اس کے بعد ہم Elasticsearch کو ES کے طور پر دیکھیں گے۔

اسی طرح کے انجن کسی دستاویز کے ڈیٹا بیس میں پیچیدہ تلاشوں کے لیے استعمال ہوتے ہیں۔ مثال کے طور پر، زبان کی مورفولوجی کو مدنظر رکھتے ہوئے تلاش کریں یا جیو کوآرڈینیٹ کے ذریعے تلاش کریں۔

اس مضمون میں میں انڈیکسنگ بلاگ پوسٹس کی مثال کا استعمال کرتے ہوئے ES کی بنیادی باتوں کے بارے میں بات کروں گا۔ میں آپ کو دستاویزات کو فلٹر کرنے، ترتیب دینے اور تلاش کرنے کا طریقہ دکھاتا ہوں۔

آپریٹنگ سسٹم پر انحصار نہ کرنے کے لیے، میں CURL کا استعمال کرتے ہوئے ES سے تمام درخواستیں کروں گا۔ گوگل کروم کے لیے ایک پلگ ان بھی ہے جسے کہا جاتا ہے۔ احساس.

متن میں دستاویزات اور دیگر ذرائع کے لنکس شامل ہیں۔ آخر میں دستاویزات تک فوری رسائی کے لیے لنکس موجود ہیں۔ غیر مانوس اصطلاحات کی تعریفیں مل سکتی ہیں۔ لغت.

تنصیب

ایسا کرنے کے لیے، ہمیں پہلے جاوا کی ضرورت ہے۔ ڈویلپرز تجویز کریں جاوا 8 اپڈیٹ 20 یا جاوا 7 اپڈیٹ 55 سے نئے جاوا ورژن انسٹال کریں۔

ES کی تقسیم دستیاب ہے۔ ڈویلپر کی ویب سائٹ. آرکائیو کو پیک کھولنے کے بعد آپ کو چلانے کی ضرورت ہے۔ bin/elasticsearch. بھی دستیاب مناسب اور یم کے لیے پیکجز. وہاں ہے ڈاکر کے لئے سرکاری تصویر. تنصیب کے بارے میں مزید.

تنصیب اور لانچ کے بعد، آئیے فعالیت کو چیک کریں:

# для удобства запомним адрес в переменную
#export ES_URL=$(docker-machine ip dev):9200
export ES_URL=localhost:9200

curl -X GET $ES_URL

ہمیں کچھ اس طرح ملے گا:

{
  "name" : "Heimdall",
  "cluster_name" : "elasticsearch",
  "version" : {
    "number" : "2.2.1",
    "build_hash" : "d045fc29d1932bce18b2e65ab8b297fbf6cd41a1",
    "build_timestamp" : "2016-03-09T09:38:54Z",
    "build_snapshot" : false,
    "lucene_version" : "5.4.1"
  },
  "tagline" : "You Know, for Search"
}

اشاریہ کاری۔

آئیے ES میں ایک پوسٹ شامل کریں:

# Добавим документ c id 1 типа post в индекс blog.
# ?pretty указывает, что вывод должен быть человеко-читаемым.

curl -XPUT "$ES_URL/blog/post/1?pretty" -d'
{
  "title": "Веселые котята",
  "content": "<p>Смешная история про котят<p>",
  "tags": [
    "котята",
    "смешная история"
  ],
  "published_at": "2014-09-12T20:44:42+00:00"
}'

سرور کا جواب:

{
  "_index" : "blog",
  "_type" : "post",
  "_id" : "1",
  "_version" : 1,
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "created" : false
}

ES خود بخود بن گیا۔ انڈیکس بلاگ اور قسم۔ پوسٹ ہم ایک مشروط تشبیہ بنا سکتے ہیں: ایک انڈیکس ایک ڈیٹا بیس ہے، اور اس ڈیٹا بیس میں ایک قسم ایک جدول ہے۔ ہر قسم کی اپنی اسکیم ہوتی ہے۔ تعریفیں، بالکل ایک رشتہ دار میز کی طرح۔ جب دستاویز کو ترتیب دیا جاتا ہے تو نقشہ سازی خود بخود تیار ہوتی ہے:

# Получим mapping всех типов индекса blog
curl -XGET "$ES_URL/blog/_mapping?pretty"

سرور کے جواب میں، میں نے تبصروں میں انڈیکسڈ دستاویز کے فیلڈز کی قدریں شامل کیں:

{
  "blog" : {
    "mappings" : {
      "post" : {
        "properties" : {
          /* "content": "<p>Смешная история про котят<p>", */ 
          "content" : {
            "type" : "string"
          },
          /* "published_at": "2014-09-12T20:44:42+00:00" */
          "published_at" : {
            "type" : "date",
            "format" : "strict_date_optional_time||epoch_millis"
          },
          /* "tags": ["котята", "смешная история"] */
          "tags" : {
            "type" : "string"
          },
          /*  "title": "Веселые котята" */
          "title" : {
            "type" : "string"
          }
        }
      }
    }
  }
}

یہ بات قابل غور ہے کہ ES کسی ایک قدر اور قدروں کی صف میں فرق نہیں کرتا ہے۔ مثال کے طور پر، ٹائٹل فیلڈ میں صرف ایک ٹائٹل ہوتا ہے، اور ٹیگز فیلڈ میں تاروں کی ایک صف ہوتی ہے، حالانکہ نقشہ سازی میں ان کی نمائندگی اسی طرح کی جاتی ہے۔
ہم بعد میں نقشہ سازی کے بارے میں مزید بات کریں گے۔

درخواستوں

کسی دستاویز کو اس کی شناخت سے بازیافت کرنا:

# извлечем документ с id 1 типа post из индекса blog
curl -XGET "$ES_URL/blog/post/1?pretty"
{
  "_index" : "blog",
  "_type" : "post",
  "_id" : "1",
  "_version" : 1,
  "found" : true,
  "_source" : {
    "title" : "Веселые котята",
    "content" : "<p>Смешная история про котят<p>",
    "tags" : [ "котята", "смешная история" ],
    "published_at" : "2014-09-12T20:44:42+00:00"
  }
}

جواب میں نئی ​​چابیاں نمودار ہوئیں: _version и _source. عام طور پر، کے ساتھ شروع ہونے والی تمام چابیاں _ سرکاری کے طور پر درجہ بندی کر رہے ہیں.

کلیدی _version دستاویز کا ورژن دکھاتا ہے۔ پرامید لاکنگ میکانزم کے کام کرنے کے لیے اس کی ضرورت ہے۔ مثال کے طور پر، ہم ایک دستاویز کو تبدیل کرنا چاہتے ہیں جس کا ورژن 1 ہے۔ ہم تبدیل شدہ دستاویز جمع کراتے ہیں اور اشارہ کرتے ہیں کہ یہ ورژن 1 والی دستاویز میں ترمیم ہے۔ اگر کسی اور نے بھی ورژن 1 کے ساتھ دستاویز میں ترمیم کی اور ہمارے سامنے تبدیلیاں جمع کرائیں، تو ES ہماری تبدیلیوں کو قبول نہیں کرے گا، کیونکہ یہ دستاویز کو ورژن 2 کے ساتھ اسٹور کرتا ہے۔

کلیدی _source اس دستاویز پر مشتمل ہے جسے ہم نے ترتیب دیا ہے۔ ES اس قدر کو تلاش کے کاموں کے لیے استعمال نہیں کرتا ہے کیونکہ اشاریہ جات تلاش کے لیے استعمال ہوتے ہیں۔ جگہ بچانے کے لیے، ES ایک کمپریسڈ سورس دستاویز کو اسٹور کرتا ہے۔ اگر ہمیں صرف آئی ڈی کی ضرورت ہے، نہ کہ پوری سورس دستاویز کی، تو ہم سورس اسٹوریج کو غیر فعال کر سکتے ہیں۔

اگر ہمیں اضافی معلومات کی ضرورت نہیں ہے، تو ہم صرف _source کے مواد حاصل کر سکتے ہیں:

curl -XGET "$ES_URL/blog/post/1/_source?pretty"
{
  "title" : "Веселые котята",
  "content" : "<p>Смешная история про котят<p>",
  "tags" : [ "котята", "смешная история" ],
  "published_at" : "2014-09-12T20:44:42+00:00"
}

آپ صرف مخصوص فیلڈز کو بھی منتخب کر سکتے ہیں:

# извлечем только поле title
curl -XGET "$ES_URL/blog/post/1?_source=title&pretty"
{
  "_index" : "blog",
  "_type" : "post",
  "_id" : "1",
  "_version" : 1,
  "found" : true,
  "_source" : {
    "title" : "Веселые котята"
  }
}

آئیے کچھ اور پوسٹس کو انڈیکس کریں اور مزید پیچیدہ سوالات چلائیں۔

curl -XPUT "$ES_URL/blog/post/2" -d'
{
  "title": "Веселые щенки",
  "content": "<p>Смешная история про щенков<p>",
  "tags": [
    "щенки",
    "смешная история"
  ],
  "published_at": "2014-08-12T20:44:42+00:00"
}'
curl -XPUT "$ES_URL/blog/post/3" -d'
{
  "title": "Как у меня появился котенок",
  "content": "<p>Душераздирающая история про бедного котенка с улицы<p>",
  "tags": [
    "котята"
  ],
  "published_at": "2014-07-21T20:44:42+00:00"
}'

چھانٹ رہا ہے

# найдем последний пост по дате публикации и извлечем поля title и published_at
curl -XGET "$ES_URL/blog/post/_search?pretty" -d'
{
  "size": 1,
  "_source": ["title", "published_at"],
  "sort": [{"published_at": "desc"}]
}'
{
  "took" : 8,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 3,
    "max_score" : null,
    "hits" : [ {
      "_index" : "blog",
      "_type" : "post",
      "_id" : "1",
      "_score" : null,
      "_source" : {
        "title" : "Веселые котята",
        "published_at" : "2014-09-12T20:44:42+00:00"
      },
      "sort" : [ 1410554682000 ]
    } ]
  }
}

ہم نے آخری پوسٹ کا انتخاب کیا۔ size جاری کیے جانے والے دستاویزات کی تعداد کو محدود کرتا ہے۔ total درخواست سے مماثل دستاویزات کی کل تعداد دکھاتا ہے۔ sort آؤٹ پٹ میں عدد کی ایک صف ہوتی ہے جس کے ذریعے چھانٹائی جاتی ہے۔ وہ. تاریخ کو ایک عدد میں تبدیل کر دیا گیا تھا۔ چھانٹنے کے بارے میں مزید معلومات اس میں مل سکتی ہیں۔ دستاویزات.

فلٹرز اور سوالات

ES چونکہ ورژن 2 فلٹرز اور استفسارات میں فرق نہیں کرتا ہے۔ سیاق و سباق کا تصور متعارف کرایا گیا ہے۔.
استفسار کا سیاق و سباق فلٹر کے سیاق و سباق سے مختلف ہوتا ہے کہ استفسار ایک _score پیدا کرتا ہے اور کیش نہیں ہوتا ہے۔ میں آپ کو بعد میں دکھاؤں گا کہ _score کیا ہے۔

تاریخ کے لحاظ سے فلٹر کریں۔

ہم درخواست استعمال کرتے ہیں۔ رینج فلٹر کے تناظر میں:

# получим посты, опубликованные 1ого сентября или позже
curl -XGET "$ES_URL/blog/post/_search?pretty" -d'
{
  "filter": {
    "range": {
      "published_at": { "gte": "2014-09-01" }
    }
  }
}'

ٹیگز کے لحاظ سے فلٹر کریں۔

ہم استعمال کرتے ہیں اصطلاحی سوال دیئے گئے لفظ پر مشتمل دستاویز کی شناخت تلاش کرنے کے لیے:

# найдем все документы, в поле tags которых есть элемент 'котята'
curl -XGET "$ES_URL/blog/post/_search?pretty" -d'
{
  "_source": [
    "title",
    "tags"
  ],
  "filter": {
    "term": {
      "tags": "котята"
    }
  }
}'
{
  "took" : 9,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 2,
    "max_score" : 1.0,
    "hits" : [ {
      "_index" : "blog",
      "_type" : "post",
      "_id" : "1",
      "_score" : 1.0,
      "_source" : {
        "title" : "Веселые котята",
        "tags" : [ "котята", "смешная история" ]
      }
    }, {
      "_index" : "blog",
      "_type" : "post",
      "_id" : "3",
      "_score" : 1.0,
      "_source" : {
        "title" : "Как у меня появился котенок",
        "tags" : [ "котята" ]
      }
    } ]
  }
}

مکمل متن کی تلاش

ہماری تین دستاویزات مواد کے میدان میں درج ذیل پر مشتمل ہیں:

  • <p>Смешная история про котят<p>
  • <p>Смешная история про щенков<p>
  • <p>Душераздирающая история про бедного котенка с улицы<p>

ہم استعمال کرتے ہیں میچ استفسار دیئے گئے لفظ پر مشتمل دستاویز کی شناخت تلاش کرنے کے لیے:

# source: false означает, что не нужно извлекать _source найденных документов
curl -XGET "$ES_URL/blog/post/_search?pretty" -d'
{
  "_source": false,
  "query": {
    "match": {
      "content": "история"
    }
  }
}'
{
  "took" : 13,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 3,
    "max_score" : 0.11506981,
    "hits" : [ {
      "_index" : "blog",
      "_type" : "post",
      "_id" : "2",
      "_score" : 0.11506981
    }, {
      "_index" : "blog",
      "_type" : "post",
      "_id" : "1",
      "_score" : 0.11506981
    }, {
      "_index" : "blog",
      "_type" : "post",
      "_id" : "3",
      "_score" : 0.095891505
    } ]
  }
}

تاہم، اگر ہم مواد کے میدان میں "کہانیاں" تلاش کرتے ہیں، تو ہمیں کچھ نہیں ملے گا، کیونکہ انڈیکس میں صرف اصل الفاظ ہیں، ان کے تنوں پر نہیں۔ اعلی معیار کی تلاش کرنے کے لیے، آپ کو تجزیہ کار ترتیب دینے کی ضرورت ہے۔

فیلڈ _score شوز مطابقت. اگر درخواست کو فلٹر سیاق و سباق میں عمل میں لایا جاتا ہے، تو _score کی قدر ہمیشہ 1 کے برابر ہوگی، جس کا مطلب ہے فلٹر سے مکمل مماثلت ہے۔

تجزیہ کار

تجزیہ کار سورس ٹیکسٹ کو ٹوکنز کے سیٹ میں تبدیل کرنے کے لیے ضروری ہے۔
تجزیہ کار ایک پر مشتمل ہوتے ہیں۔ ٹوکنائزر اور کئی اختیاری ٹوکن فلٹرز. ٹوکنائزر سے پہلے کئی کئی ہو سکتے ہیں۔ چار فلٹرز. ٹوکنائزرز سورس سٹرنگ کو ٹوکنز میں توڑ دیتے ہیں، جیسے خالی جگہیں اور اوقاف کے حروف۔ TokenFilter ٹوکن کو تبدیل کر سکتا ہے، حذف کر سکتا ہے یا نئے شامل کر سکتا ہے، مثال کے طور پر، صرف لفظ کا تنا چھوڑ سکتا ہے، مترادفات کو ہٹا سکتا ہے، مترادفات شامل کر سکتا ہے۔ CharFilter - پورے سورس سٹرنگ کو تبدیل کرتا ہے، مثال کے طور پر، HTML ٹیگز کو کاٹ دیتا ہے۔

ES میں کئی ہیں۔ معیاری تجزیہ کار. مثال کے طور پر، ایک تجزیہ کار روسی.

آئیے فائدہ اٹھاتے ہیں۔ اے پی آئی اور آئیے دیکھتے ہیں کہ معیاری اور روسی تجزیہ کار "بلی کے بچوں کے بارے میں مضحکہ خیز کہانیاں" کے تار کو کیسے تبدیل کرتے ہیں:

# используем анализатор standard       
# обязательно нужно перекодировать не ASCII символы
curl -XGET "$ES_URL/_analyze?pretty&analyzer=standard&text=%D0%92%D0%B5%D1%81%D0%B5%D0%BB%D1%8B%D0%B5%20%D0%B8%D1%81%D1%82%D0%BE%D1%80%D0%B8%D0%B8%20%D0%BF%D1%80%D0%BE%20%D0%BA%D0%BE%D1%82%D1%8F%D1%82"
{
  "tokens" : [ {
    "token" : "веселые",
    "start_offset" : 0,
    "end_offset" : 7,
    "type" : "<ALPHANUM>",
    "position" : 0
  }, {
    "token" : "истории",
    "start_offset" : 8,
    "end_offset" : 15,
    "type" : "<ALPHANUM>",
    "position" : 1
  }, {
    "token" : "про",
    "start_offset" : 16,
    "end_offset" : 19,
    "type" : "<ALPHANUM>",
    "position" : 2
  }, {
    "token" : "котят",
    "start_offset" : 20,
    "end_offset" : 25,
    "type" : "<ALPHANUM>",
    "position" : 3
  } ]
}
# используем анализатор russian
curl -XGET "$ES_URL/_analyze?pretty&analyzer=russian&text=%D0%92%D0%B5%D1%81%D0%B5%D0%BB%D1%8B%D0%B5%20%D0%B8%D1%81%D1%82%D0%BE%D1%80%D0%B8%D0%B8%20%D0%BF%D1%80%D0%BE%20%D0%BA%D0%BE%D1%82%D1%8F%D1%82"
{
  "tokens" : [ {
    "token" : "весел",
    "start_offset" : 0,
    "end_offset" : 7,
    "type" : "<ALPHANUM>",
    "position" : 0
  }, {
    "token" : "истор",
    "start_offset" : 8,
    "end_offset" : 15,
    "type" : "<ALPHANUM>",
    "position" : 1
  }, {
    "token" : "кот",
    "start_offset" : 20,
    "end_offset" : 25,
    "type" : "<ALPHANUM>",
    "position" : 3
  } ]
}

معیاری تجزیہ کار نے سٹرنگ کو خالی جگہوں سے تقسیم کیا اور ہر چیز کو لوئر کیس میں تبدیل کر دیا، روسی تجزیہ کار نے غیر اہم الفاظ کو ہٹا دیا، اسے لوئر کیس میں تبدیل کر دیا اور الفاظ کے تنے کو چھوڑ دیا۔

آئیے دیکھتے ہیں کہ روسی تجزیہ کار کون سے ٹوکنائزر، ٹوکن فلٹرز، چار فلٹرز استعمال کرتا ہے:

{
  "filter": {
    "russian_stop": {
      "type":       "stop",
      "stopwords":  "_russian_"
    },
    "russian_keywords": {
      "type":       "keyword_marker",
      "keywords":   []
    },
    "russian_stemmer": {
      "type":       "stemmer",
      "language":   "russian"
    }
  },
  "analyzer": {
    "russian": {
      "tokenizer":  "standard",
      /* TokenFilters */
      "filter": [
        "lowercase",
        "russian_stop",
        "russian_keywords",
        "russian_stemmer"
      ]
      /* CharFilters отсутствуют */
    }
  }
}

آئیے روسی پر مبنی اپنے تجزیہ کار کی وضاحت کریں، جو HTML ٹیگز کو کاٹ دے گا۔ آئیے اسے ڈیفالٹ کہتے ہیں، کیونکہ اس نام کے ساتھ ایک تجزیہ کار بطور ڈیفالٹ استعمال کیا جائے گا۔

{
  "filter": {
    "ru_stop": {
      "type":       "stop",
      "stopwords":  "_russian_"
    },
    "ru_stemmer": {
      "type":       "stemmer",
      "language":   "russian"
    }
  },
  "analyzer": {
    "default": {
      /* добавляем удаление html тегов */
      "char_filter": ["html_strip"],
      "tokenizer":  "standard",
      "filter": [
        "lowercase",
        "ru_stop",
        "ru_stemmer"
      ]
    }
  }
}

سب سے پہلے، تمام ایچ ٹی ایم ایل ٹیگز کو سورس سٹرنگ سے ہٹا دیا جائے گا، پھر ٹوکنائزر اسٹینڈرڈ اسے ٹوکنز میں تقسیم کر دے گا، نتیجے میں آنے والے ٹوکن لوئر کیس میں چلے جائیں گے، غیر اہم الفاظ کو ہٹا دیا جائے گا، اور باقی ٹوکنز لفظ کا اسٹیم ہی رہیں گے۔

انڈیکس بنانا

اوپر ہم نے پہلے سے طے شدہ تجزیہ کار کو بیان کیا۔ یہ تمام سٹرنگ فیلڈز پر لاگو ہوگا۔ ہماری پوسٹ میں ٹیگز کی ایک صف شامل ہے، اس لیے ٹیگز پر تجزیہ کار بھی کارروائی کرے گا۔ کیونکہ ہم ایک ٹیگ کے عین مطابق مماثل پوسٹس تلاش کر رہے ہیں، پھر ہمیں ٹیگز فیلڈ کے لیے تجزیہ کو غیر فعال کرنے کی ضرورت ہے۔

آئیے تجزیہ کار اور نقشہ سازی کے ساتھ ایک انڈیکس بلاگ 2 بنائیں، جس میں ٹیگز فیلڈ کا تجزیہ غیر فعال ہے:

curl -XPOST "$ES_URL/blog2" -d'
{
  "settings": {
    "analysis": {
      "filter": {
        "ru_stop": {
          "type": "stop",
          "stopwords": "_russian_"
        },
        "ru_stemmer": {
          "type": "stemmer",
          "language": "russian"
        }
      },
      "analyzer": {
        "default": {
          "char_filter": [
            "html_strip"
          ],
          "tokenizer": "standard",
          "filter": [
            "lowercase",
            "ru_stop",
            "ru_stemmer"
          ]
        }
      }
    }
  },
  "mappings": {
    "post": {
      "properties": {
        "content": {
          "type": "string"
        },
        "published_at": {
          "type": "date"
        },
        "tags": {
          "type": "string",
          "index": "not_analyzed"
        },
        "title": {
          "type": "string"
        }
      }
    }
  }
}'

آئیے اسی 3 پوسٹس کو اس انڈیکس (blog2) میں شامل کریں۔ میں اس عمل کو چھوڑ دوں گا کیونکہ... یہ بلاگ انڈیکس میں دستاویزات شامل کرنے کے مترادف ہے۔

اظہار کی حمایت کے ساتھ مکمل متن کی تلاش

آئیے ایک اور قسم کی درخواست پر ایک نظر ڈالتے ہیں:

# найдем документы, в которых встречается слово 'истории'
# query -> simple_query_string -> query содержит поисковый запрос
# поле title имеет приоритет 3
# поле tags имеет приоритет 2
# поле content имеет приоритет 1
# приоритет используется при ранжировании результатов
curl -XPOST "$ES_URL/blog2/post/_search?pretty" -d'
{
  "query": {
    "simple_query_string": {
      "query": "истории",
      "fields": [
        "title^3",
        "tags^2",
        "content"
      ]
    }
  }
}'

کیونکہ ہم روسی اسٹیمنگ کے ساتھ ایک تجزیہ کار استعمال کر رہے ہیں، پھر یہ درخواست تمام دستاویزات واپس کر دے گی، حالانکہ ان میں صرف لفظ 'تاریخ' ہے۔

درخواست میں خاص حروف شامل ہو سکتے ہیں، مثال کے طور پر:

""fried eggs" +(eggplant | potato) -frittata"

نحو کی درخواست کریں:

+ signifies AND operation
| signifies OR operation
- negates a single token
" wraps a number of tokens to signify a phrase for searching
* at the end of a term signifies a prefix query
( and ) signify precedence
~N after a word signifies edit distance (fuzziness)
~N after a phrase signifies slop amount
# найдем документы без слова 'щенки'
curl -XPOST "$ES_URL/blog2/post/_search?pretty" -d'
{
  "query": {
    "simple_query_string": {
      "query": "-щенки",
      "fields": [
        "title^3",
        "tags^2",
        "content"
      ]
    }
  }
}'

# получим 2 поста про котиков

حوالہ جات

PS

اگر آپ اسی طرح کے مضامین کے اسباق میں دلچسپی رکھتے ہیں، نئے مضامین کے لیے آئیڈیاز رکھتے ہیں، یا تعاون کے لیے تجاویز رکھتے ہیں، تو مجھے ذاتی پیغام یا ای میل کے ذریعے پیغام موصول ہونے پر خوشی ہوگی۔ [ای میل محفوظ].

ماخذ: www.habr.com