I've learned. I'll share.

August 12, 2009

More ways to do Reactive Programming in Python

In my last post, I covered a little bit of Rx and how you could a have invented it. But you might invent a different way of doing the same thing. And since most languages don't have anything like LINQ, you might be interested in ways to do things in your programming language that don't require monads.

Let's explore some other ways to do Reactive Programming (Rx).

What is Rx?

Just to remind you what we're trying to accomplish, Rx builds event handlers. The LINQ version of Rx works by making an event look like a query (or stream).

It makes sense if you think about it. An event is a stream, of event occurences. A list or enumerator or iterator is also a stream, of values. So if you squint hard enough, you see that events and enumerators are sort of the same thing. In fact, lists, streams, enumerators, events, channels, pipes, sockets, file handles, actors sending you messages are all pretty much the same thing: they are all producers of values.

Consumers and Receivers

Now what do you do with producers of values? You consume them, of course! Usually with something that looks like this (in python):
sum = 0
for val in vals:
   sum += val
   print sum
What we've created here is a consumer of vals. We can write it this way, as ordinary code, because vals is very flexible: it's anything that's iterable/enumerable. But what if instead of forcing the producer to be flexible, we forced the consumer to be flexible? What if we could write the consumer like this:
total = 0
while True:
  total += receive
  print total
Hmm... it sort of looks like the opposite of an iterator/generator/enumerator block. A mathematician might say something about "duals" at this point, but I'm not mathematician, so let's just go ahead and try and implement it. In fact, we'll use python generators to do just that. We'll call this a "receiver" and we'll spell "receive" as "yield receive":
class Event:
    def __init__(self):
        self.handlers = []

    def handle(self, handler):
        self.handlers.append(handler)

    def fire(self, val = None):
        for handler in self.handlers:
            handler(val)

receive = object()

def receiver(gen_rcvr):
    def gen_and_start_rcvr(*args, **kargs):
        rcvr = gen_rcvr(*args, **kargs)
        rcvr.send(None)
        return rcvr
    return gen_and_start_rcvr

@receiver
def sum_r(title):
    total = 0
    while True:
        total += yield receive
        print "%s: %d" % (title, total)

@receiver
def count_r(title):
    count = 0
    while True:
        yield receive
        count += 1
        print "%s: %d" % (title, count)


num_key  = Event()
sum_nums = sum_r("total nums")
num_key.handle(sum_nums.send)

num_key.fire(1) #prints "total nums: 1"
num_key.fire(2) #prints "total nums: 3" 
num_key.fire(3) #prints "total nums: 6"
It actually works! And because our consumer is very flexible, any producer, like an event, can use it. In fact, it's just a fancy event callback, just like everyrthing else in Rx land.

Remitters

But if we take this one step further and make a receiver wrap an event, we can make a receiver that's also a producer. We'll call it a "remitter", which is sort of like a receiver and an emitter.
class Remitter:
    def __init__(self, receiver_from_event_out):
        self.receiverFromEventOut = receiver_from_event_out

    def __rrshift__(self, event_in):
        event_out = Event()
        rcvr      = self.receiverFromEventOut(event_out)
        event_in.handle(rcvr.send)
        return event_out

def remitter(gen_rcvr):
    def gen_remitter(*args, **kargs):
        def receiver_from_event_out(event_out):
            rcvr = gen_rcvr(event_out, *args, **kargs)
            rcvr.send(None)
            return rcvr
        return Remitter(receiver_from_event_out)
    return gen_remitter

@remitter
def double_detect_r(double_click_event, threshold):
    last_click_time = 0
    while True:
        (yield)
        current_click_time = time.time()
        if (current_click_time - last_click_time) < threshold:
            double_click_event.fire()
        last_click_time = current_click_time

@remitter
def print_r(_, message):
    while True:
       val = (yield)
       print message

mouse_click  = Event()

mouse_click >> print_r("left")
mouse_click >> double_detect_r(.01) >> print_r("double left")

mouse_click.fire() #prints "left"
time.sleep(.02)
mouse_click.fire() #prints "left"
mouse_click.fire() #prints "left" and "double left"
Great. But it is kind of annoying passing in the event like that. What if we had the remitter yield values out and yield values in?

Remitters that yield out and in

We could do that using little state machines built from python generators. "yield receive" will mean receive and "yield" of anything else will mean "emit".
from collections import defaultdict

class Remitter:
  def __init__(self, ritr):
      self.ritr     = ritr
      self.eventOut = Event()

  def send(self, val_in):
      ritr      = self.ritr
      event_out = self.eventOut

      while True:
          val_out = ritr.send(val_in)
          if val_out is receive:
              break
          else:
              event_out.fire(val_out)            

  def handle(self, handler):
      self.eventOut.handle(handler)

  def handlein(self, *events):
      for event in events:
          event.handle(self.send)

  def __rrshift__(self, event_in):
      try:
          self.handlein(*event_in)
      except:
          self.handlein(event_in)
      return self


def remitter(gen_rcvr):
    def gen_remitter(*args, **kargs):
        ritr = gen_rcvr(*args, **kargs)
        ritr.send(None)
        return Remitter(ritr)
    return gen_remitter


@remitter
def double_detect_r(threshold):
    last_click_time = 0
    while True:
        yield receive
        current_click_time = time.time()
        if (current_click_time - last_click_time) < threshold:
            yield
        last_click_time = current_click_time

@remitter
def map_r(f, *args, **kargs):
    while True:
       val = yield receive
       yield f(val, *args, **kargs)

@remitter
def print_r(format):
    while True:
       val = yield receive
       print message % val

def label_r(label):
    return map_r(lambda val : (label, val))
        
@remitter
def label_count_r():
    count_by_label = defaultdict(int)
    while True:
        (label, val) = yield receive
        count_by_label[label] += 1
        yield count_by_label.copy()

def fix_click_counts(count_by_label, single_label, double_label):
    count_by_label[single_label] -= (count_by_label[double_label] * 2) #every double click "cancels" a single click
    return count_by_label.copy()

def print_label_counts(count_by_label, *labels):
    print ", ".join("%d %s" % (count, label) for (label, count) in count_by_label.iteritems())


mouse_clicks = Event()

([mouse_clicks >> label_r("single"),
  mouse_clicks >> double_detect_r(.01) >> label_r("double")] 
 >> label_count_r() >> map_r(fix_click_counts, "single", "double") >> map_r(print_label_counts))
 
#prints
#0 double, 1 single
#0 double, 2 single
#0 double, 3 single
#1 double, 1 single
mouse_clicks.fire() 
time.sleep(.02)
mouse_clicks.fire() 
mouse_clicks.fire()
Sweet. That looks pretty nice. But, it relies on the fact that Python allows you to yield values in to a generator. What if we have a programming language that only allows yielding values out (like any enumerator)?

Remitters that yield in by yielding out

We'll introduce a simple hack to work around that. We'll yield out a mutable "receive" value that "receives" in the value for us.
class Receive:
    def __init__(self, val = None):
        self.d = val

class Remitter:
  def __init__(self, receive, ritr):
      self.receive  = receive
      self.ritr     = ritr
      self.eventOut = Event()

  def send(self, val_in):
      self.receive.d = val_in

      ritr      = self.ritr
      event_out = self.eventOut
      while True:
          val_out = ritr.next()
          if isinstance(val_out, Receive):
              break
          else:
              event_out.fire(val_out)

  def handle(self, handler):
      self.eventOut.handle(handler)

  def handlein(self, *events):
      for event in events:
          event.handle(self.send)

  def __rrshift__(self, event_in):
      try:
          self.handlein(*event_in)
      except:
          self.handlein(event_in)
      return self

def remitter(gen_rcvr):
    def gen_remitter(*args, **kargs):
        receive = Receive()
        ritr = gen_rcvr(receive, *args, **kargs)
        ritr.send(None)
        return Remitter(receive, ritr)
    return gen_remitter

@remitter
def double_detect_r(receive, threshold):
    last_click_time = 0
    while True:
        yield receive
        current_click_time = time.time()
        gap                = current_click_time - last_click_time
        if gap < threshold:
            yield gap
        last_click_time = current_click_time

@remitter
def average_r(receive):
    total   = 0.0
    count   = 0
    while True:
        yield receive
        total += receive.d
        count += 1
        yield total/count

@remitter
def print_r(receive, format):
    while True:
        yield receive
        print format % (receive.d)
    
mouse_clicks = Event()
mouse_clicks >> double_detect_r(.05) >> average_r() >> print_r("double click; average gap is %s seconds")
        
mouse_clicks.fire() 
time.sleep(.1)
mouse_clicks.fire() 
time.sleep(.01)
mouse_clicks.fire() #prints #double click; average gap is 0.01... seconds
time.sleep(.02) 
mouse_clicks.fire() #double click; average gap is 0.015... seconds
It works! And it should work in any language with iterator blocks. You could even use this C# instead of using LINQ Rx, but then you'll have to type "yield return receive" :(.

Conclusion

Rx is all about making flexible consumers of values, which basically amounts to making event callbacks. LINQ Rx does so with monads, but that's not the only way. Here, we have shown how we can turn a generator or iterator block inside out and make it consume values rather than produce values. Using these is an alternative to LINQ Rx that might be more appropriate for your programming language. There are lots of other things to work out, like unhandling an event, error handling, catching the end of a stream, etc. But this is pretty good, simple foundation to show that the essense of reactive programming is making it easy to make flexible value consumers (basically event handlers). In both the case of Rx, and the code above, we've done so by making a little DSL in the host language.

Next time...

There are still other ways of making flexible consumers. If we had continuations or used CPS we could just use the current continuation as our consumer. So, scheme and Ruby ought to have easy solutions to this problem. We can do a similar thing with macros in any Lisp language that doesn't have continuations, like Clojure. In fact, I'd like to explore how to do Rx in clojure next time. And at some point, we need to figure out how concurrency fits into all of this.

P.S.

While I was researching all of this stuff, I was surprised to find that my friend, Wes Dyer, is right at the heart of it. You can see a video of him here. That was a surprise because I've never talked with him about this. In fact, I've only heard from him once in the last year because he's "gone dark" . I'm sure his work on Rx has something to do with that :). I just want to make it clear that all of my thoughts are mine alone, and not based on any communication with him. Don't blame him for my crazy stuff :).

91 comments:

  1. Huge information, actually, is anticipated to be the following 'huge' thing. We're about enormous information at the present time. ExcelR Data Science Courses

    ReplyDelete
  2. I wanted to leave a little comment to support you and wish you a good continuation. Wishing you the best of luck for all your blogging efforts
    360Digitmg marketing analytics in hyderabad

    ReplyDelete
  3. I am looking for and I love to post a comment that "The content of your post is awesome" Great work!
    Data-science online course in chennai

    ReplyDelete
  4. I'm hoping you keep writing like this. I love how careful and in depth you go on this topic. Keep up the great work
    Data Science Course in Bangalore

    ReplyDelete
  5. I am impressed by the information that you have on this blog. It shows how well you understand this subject.
    360digitmg data science course

    ReplyDelete
  6. it's really cool blog. Linking is very useful thing.you have really helped.
    360digitmg data scientist courses

    ReplyDelete
  7. Thanks for the lovely blog. It helped me a lot. I'm glad I found this blog. Thanks for sharing with us, I too am always learning something new from your post.

    360DigiTMG Data Science Courses

    ReplyDelete
  8. Awesome Blog on Python programming keep up the good work thank you.
    Data Science Training in Hyderabad

    ReplyDelete
  9. I need to to thank you for this fantastic article, I definitely really liked every part of it keep up the great work.
    360DigiTMG Data Analytics Training

    ReplyDelete
  10. What an extremely wonderful post this is. Genuinely, perhaps the best post I've at any point seen to find in as long as I can remember. Goodness, simply keep it up.
    360DigiTMG data science malaysia

    ReplyDelete
  11. Great blog with top quality information with unique style of writing thank you.
    Data Science Course in Hyderabad

    ReplyDelete

  12. Top quality blog with very informative information found very useful thanks for sharing.
    Data Analytics Course Online

    ReplyDelete
  13. Very informative article with valuable information found resourceful thanks for sharing waiting for next blog.
    Ethical Hacking Course in Bangalore

    ReplyDelete
  14. Really impressed! Everything is very open and very clear clarification of issues. It contains truly facts. Your website is very valuable. Thanks for sharing.data science institute in hyderabad

    ReplyDelete
  15. It took a while to understand all the comments, but I really enjoyed the article. It turned out to be really helpful for me and I'm positive for all the reviewers here! It's always nice to be able to not only be informed, but also entertained! I'm sure you enjoyed writing this article. PMP Training in Hyderabad

    ReplyDelete
  16. You have completed certain reliable points there. I did some research on the subject and found that almost everyone will agree with your blog.

    Artificial Intelligence Course in Bangalore

    ReplyDelete
  17. First You got a great blog .I will be interested in more similar topics. i see you got really very useful topics, i will be always checking your blog thanks.
    Digital Marketing Training Institutes in Hyderabad

    ReplyDelete
  18. I just got to this amazing site not long ago. I was actually captured with the piece of resources you have got here. Big thumbs up for making such wonderful blog page!
    Artificial Intelligence Course

    ReplyDelete
  19. I have bookmarked your website because this site contains valuable information in it. I am really happy with articles quality and presentation. Thanks a lot for keeping great stuff. I am very much thankful for this site.
    business analytics course

    ReplyDelete
  20. This blog is truly impressive with valuable information found resourceful thank you for sharing.
    Data Scientist Training in Hyderabad

    ReplyDelete
  21. This is an awesome motivating article.I am practically satisfied with your great work.You put truly extremely supportive data. Keep it up. Continue blogging. Hoping to perusing your next post
    Best Digital Marketing Courses in Hyderabad

    ReplyDelete
  22. I am impressed by the information that you have on this blog. It shows how well you understand this subject.
    Data Science Training in Hyderabad

    ReplyDelete
  23. This is very educational content and written well for a change. It's nice to see that some people still understand how to write a quality post!
    business analytics course

    ReplyDelete
  24. Writing with style and getting good compliments on the article is quite hard, to be honest.But you've done it so calmly and with so cool feeling and you've nailed the job. This article is possessed with style and I am giving good compliment. Best!

    Data Science Course in Mysuru

    ReplyDelete
  25. You might comment on the order system of the blog. You should chat it's splendid. Your blog audit would swell up your visitors. I was very pleased to find this site.I wanted to thank you for this great read!!

    Data Science Course in Trichy

    ReplyDelete

  26. Top quality blog with excellent information looking forward for next updated thank you.
    Ethical Hacking Course in Bangalore

    ReplyDelete
  27. You know your projects stand out from the crowd. There is something special about them. I think they're all really cool!

    Data Analytics Courses in Bangalore

    ReplyDelete
  28. Great advice and very easy to understand. It will definitely come in handy when I get the chance to start my blog.
    Data Science In Bangalore

    ReplyDelete
  29. I want to thank you for your efforts in writing this article. I look forward to the same best job from you in the future.
    Data Science Course in Pune

    ReplyDelete
  30. I just got to this amazing site not long ago. I was actually captured with the piece of resources you have got here. Big thumbs up for making such wonderful blog page!
    <a href="https://360digitmg.com/india/artificial-intelligence-ai-and-deep-learning-in-pune
    >artificial intelligence course in pune</a>

    ReplyDelete
  31. Very good message. I stumbled across your blog and wanted to say that I really enjoyed reading your articles. Anyway, I will subscribe to your feed and hope you post again soon.

    Data Science Course in Pune

    ReplyDelete
  32. I was browsing the internet for information and found your blog. I am impressed with the information you have on this blog.

    Data Science Certification in Bangalore

    ReplyDelete
  33. Woohoo! It is an amazing and useful article. I really like. It's so good and so amazing. I am amazed. I hope you will continue to do your job in this way in the future as well.
    Data Science Training in Pune

    ReplyDelete
  34. It took a while to understand all the comments, but I really enjoyed the article. It turned out to be really helpful for me and I'm positive for all the reviewers here! It's always nice to be able to not only be informed, but also entertained! I'm sure you enjoyed writing this article.
    Data Analytics Courses in Bangalore

    ReplyDelete


  35. Very awesome!!! When I searched for this I found this website at the top of all blogs in search engines.

    Best Digital Marketing Courses in Hyderabad

    ReplyDelete
  36. https://mindmajix.blogspot.com/2014/05/big-data-hadoop-online-training.html?showComment=1614061165336#c5127184501471954740

    ReplyDelete
  37. I am sure it will help many people. Keep up the good work. It's very compelling and I enjoyed browsing the entire blog.

    Best Data Science Courses in Bangalore

    ReplyDelete
  38. I see the greatest content on your blog and I extremely love reading them.

    digital marketing courses in hyderabad with placement

    ReplyDelete
  39. Very good message. I stumbled across your blog and wanted to say that I really enjoyed reading your articles. Anyway, I will subscribe to your feed and hope you post again soon.

    Data Analytics Course in Bangalore

    ReplyDelete
  40. Nice blog,
    The demand for Digital Marketing is growing big every day. With more and more users coming online, the demand for digital marketing is expected to grow even further.
    Digital Marketing internship course at Digital brolly with 100% placement assistance.

    ReplyDelete
  41. Very awesome!!! When I searched for this I found this website at the top of all blogs in search engines.

    best data science institute in hyderabad

    ReplyDelete
  42. I think I have never seen such blogs before that have completed things with all the details which I want. So kindly update this ever for us.

    digital marketing courses in hyderabad with placement

    ReplyDelete
  43. Now is the perfect time to plan for the future and now is the time to be happy. I have read this article and if I can I would like to suggest some cool tips or advice. Perhaps you could write future articles that reference this article. I want to know more!
    Digital Marketing Course in Bangalore

    ReplyDelete
  44. This knowledge.Excellently written article, if only all bloggers offered the same level of content as you, the internet would be a much better place. Please keep it up.
    data scientist training in hyderabad

    ReplyDelete
  45. I think I have never seen such blogs before that have completed things with all the details which I want. So kindly update this ever for us.

    business analytics course

    ReplyDelete
  46. Regular visits listed here are the easiest method to appreciate your energy, which is why I am going to the website every day, searching for new, interesting info. Many, thank you!
    digital marketing courses in hyderabad with placement

    ReplyDelete
  47. This knowledge.Excellently written article, if only all bloggers offered the same level of content as you, the internet would be a much better place. Please keep it up.
    data scientist course

    ReplyDelete
  48. Very wonderful informative article. I appreciated looking at your article. Very wonderful reveal. I would like to twit this on my followers. Many thanks! .
    Data Science certification training in Bangalore

    ReplyDelete
  49. Very nice job... Thanks for sharing this amazing and educative blog post!
    Best Data Science courses in Hyderabad

    ReplyDelete
  50. Thanks for sharing this.,
    Leanpitch provides online training in Scrum Master during this lockdown period everyone can use it wisely.
    Join Leanpitch 2 Days CSM Certification Workshop in different cities.

    Scrum master certification

    csm certification

    certified scrum master certification

    certified scrum master

    agile scrum master certification

    scrum master certification cost

    csm certification cost

    Scrum master Training

    Scrum master

    Best Scrum master certification

    ReplyDelete
  51. Very good message. I stumbled across your blog and wanted to say that I really enjoyed reading your articles. Anyway, I will subscribe to your feed and hope you post again soon.Business Analytics Course

    ReplyDelete
  52. It's good to visit your blog again, it's been months for me. Well, this article that I have been waiting for so long. I will need this post to complete my college homework, and it has the exact same topic with your article. Thanks, have a good game.
    Data Analytics Course in Bangalore

    ReplyDelete
  53. From this post I know that your good knowledge of the game with all the pieces has been very helpful. I inform you that this is the first place where I find problems that I look for. You have a clever but attractive way of writing.
    Best Data Science Courses in Bangalore

    ReplyDelete

  54. Thanks for the good writeup. It in truth was once
    a entertainment account it. Look complex to far introduced agreeable from you!
    By the way, how can we communicate?


    my website - 풀싸롱

    (freaky)

    ReplyDelete
  55. I was very pleased to find this site.I wanted to thank you for this great read!! I definitely enjoying every little bit of it and I have you bookmarked to check out new stuff you post.
    data science training

    ReplyDelete
  56. You have completed certain reliable points there. I did some research on the subject and found that almost everyone will agree with your blog.

    Business Analytics Course

    ReplyDelete
  57. Nice blog and absolutely outstanding. You can do something much better but i still say this perfect.Keep trying for the best.
    cloud computing online training in hyderabad

    ReplyDelete
  58. Amazingly by and large very interesting post. I was looking for such information and thoroughly enjoyed examining this one. Keep posting. An obligation of appreciation is all together for sharing.
    ethical hacking in hyderabad

    ReplyDelete

  59. I am impressed by the information that you have on this blog. It shows how well you understand this subject.data scientist course in nagpur

    ReplyDelete
  60. I just found this blog and have high hopes for it to continue. Keep up the great work, its hard to find good ones. I have added to my favorites. Thank You.
    data science course in hyderabad

    ReplyDelete
  61. There is perceptibly a bundle to realize about this. I suppose you made various good points in features also.
    고스톱

    ReplyDelete
  62. I’m extremely impressed together with your writing talents and also with the
    layout for your blog. Is that this a paid subject matter or did you customize it your self?
    Either way stay up the nice quality writing, it’s uncommon to peer a great blog like this one these days..
    스포츠토토

    ReplyDelete
  63. This is a smart blog. I mean it. You have so much knowledge about this issue, and so much passion. You also know how to make people rally behind it, obviously from the responses.
    data analytics training in hyderabad

    ReplyDelete
  64. Amazing knowledge and I like to share this kind of information with my friends and hope they like it they why I do
    full stack developer course

    ReplyDelete
  65. We are really grateful for your blog post. You will find a lot of approaches after visiting your post. Great work
    cyber security course malaysia

    ReplyDelete
  66. Amazingly by and large very interesting post. I was looking for such an information and thoroughly enjoyed examining this one. Keep posting. An obligation of appreciation is all together for sharing.data analytics course in rohtak

    ReplyDelete
  67. I have a mission that I’m just now working on, and I have been at the look out for such information.
    cyber security course malaysia

    ReplyDelete
  68. I have bookmarked your site since this site contains significant data in it. You rock for keeping incredible stuff. I am very appreciative of this site.
    data analytics training in hyderabad

    ReplyDelete
  69. I have read your excellent post. This is a great job. I have enjoyed reading your post first time. I want to say thanks for this post. Thank you... data science course in nashik

    ReplyDelete
  70. It is collected from different sources for different reasons, and then it is analyzed to make sense of data that the machines can interpret.
    data science training in bhubaneswar

    ReplyDelete
  71. For product research, the company requires a data analysis process to make better judgments for the growth of the business.

    ReplyDelete
  72. This post is very easy to read. Great work!

    Matlab Training in Chennai at login360 centers around Matlab as one of the main specialized programming dialects and abilities today.

    ReplyDelete
  73. As mentioned, live streaming is available, while buyer help is on hand 24/7. Most in style CSGO gambling web site of all time, and it’s highly trusted and one hundred pc reliable when it comes to of|in relation to} paying winners. From then on, there 토토사이트 are common bonuses and promos out there for dedicated gamers. Once you’ve entered your code, you’re then free to start out|to begin} betting. New gamers are also rewarded with 15% every day cashback all through their first week, while common gamers can compete in the Joker Race to win more money prizes every week.

    ReplyDelete
  74. Are you still unsuccessful in your search for the top online data science courses? Several platforms provide data science courses, but it's crucial to focus on those that meet your requirements and allow for domain specialisation. A few training opportunities in data science are included below for those who are just entering the profession.data science training institute in jaipur

    ReplyDelete
  75. The post is really very helpful and informative. Thanks for sharing.
    Python Course in Nagpur

    ReplyDelete
  76. Nice Post . Thanks for sharing with us
    Python Course in Solapur

    ReplyDelete
  77. For anyone wishing to improve their Python programming abilities, "More Ways to Do Reactive Programming in Python" is a game-changer. This thorough manual goes deeply into the area of reactive programming, providing cutting-edge methods and useful examples that enable programmers to build fast and effective applications. Even individuals who are new to reactive programming can understand complex ideas thanks to the author's lucid explanations and well-organized code samples. This resource stands out due to its emphasis on practical applications, which guarantees that readers will understand the theory and develop the confidence to use these strategies in their own projects.
    estate lawyer near me Virginia
    Commercial Contract Disputes

    ReplyDelete
  78. This comment has been removed by the author.

    ReplyDelete
  79. Diverse perspectives on Reactive Programming in Python! Exploring more ways to approach this paradigm showcases the versatility of Python, empowering developers to choose the best-fit approach for their projects.
    New Jersey Reckless Driving Speed
    New Jersey Traffic Court Lawyer</a

    ReplyDelete
  80. This post is very informative in which explain about the Reactive Programming in Python that is a game-changer for building responsive and resilient applications. By focusing on data streams and the propagation of change, it allows developers to create systems that react to real-time updates effortlessly.

    ReplyDelete
  81. Thank you for the valuable information, stay updated and check here for complete details about b tech artificial intelligence colleges

    ReplyDelete

Blog Archive

Google Analytics