{"id":417004,"date":"2024-06-30T01:17:53","date_gmt":"2024-06-30T01:17:53","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=417004"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=417004","title":{"rendered":"<span>Algorithms in Go: Merge Intervals<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>This is the third part of a <a href=\"https:\/\/habr.com\/en\/post\/545986\/\" rel=\"noopener noreferrer nofollow\">series<\/a> covering the implementation of algorithms in Go. In this article, we discuss the Merge Intervals algorithm. Usually, when you start learning algorithms you have to deal with some problems like finding the least common denominator or finding the next Fibonacci number. While these are indeed important problems, it is not something that we solve every day. Actually, in the vast majority of cases, we only solve such kinds of algorithms when we learn how to solve the algorithms. What I like about the Merge Interval algorithm is that we apply it in our everyday life, usually without even noticing that we are solving an algorithmic problem. <\/p>\n<p>Let&#8217;s say that we need to organize a meeting for our team. We have three colleagues Jay, May, and Ray and their time schedule look as follows (a colored line represents an occupied timeslot):<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/ac1\/2ca\/b6c\/ac12cab6cb49264e89d3213d96fd210a.jpg\" width=\"1469\" height=\"680\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/ac1\/2ca\/b6c\/ac12cab6cb49264e89d3213d96fd210a.jpg\" data-blurred=\"true\"\/><figcaption><\/figcaption><\/figure>\n<p>Which timeslot should we pick up for a new meeting? One could look at the picture, find a gap in Ray&#8217;s schedule, and check whether the others a gap there as well. <\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/939\/7ac\/b13\/9397acb13fcb9762a222329815923e21.jpg\" width=\"1469\" height=\"680\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/939\/7ac\/b13\/9397acb13fcb9762a222329815923e21.jpg\" data-blurred=\"true\"\/><figcaption><\/figcaption><\/figure>\n<p>How can we implement this logic? Most straightforwardly, we can iterate through every minute of the day and check whether someone is having a meeting during that time. If none of the colleagues are occupied at that time, we find an available minute. <\/p>\n<p>How can we simplify this approach? Instead of checking all employees for every minute, we can merge their schedules and find the available slots in the resulting schedule.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/d7a\/7f3\/ff1\/d7a7f3ff1dca11ae4bae14f3aeb29df8.jpg\" width=\"1536\" height=\"650\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/d7a\/7f3\/ff1\/d7a7f3ff1dca11ae4bae14f3aeb29df8.jpg\" data-blurred=\"true\"\/><figcaption><\/figcaption><\/figure>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/842\/4ef\/fb1\/8424effb1863d820b5384fb982c2afbc.jpg\" width=\"1652\" height=\"605\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/842\/4ef\/fb1\/8424effb1863d820b5384fb982c2afbc.jpg\" data-blurred=\"true\"\/><figcaption><\/figcaption><\/figure>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/774\/39d\/d59\/77439dd598a857b617764b732acc7ace.jpg\" width=\"1657\" height=\"603\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/774\/39d\/d59\/77439dd598a857b617764b732acc7ace.jpg\" data-blurred=\"true\"\/><figcaption><\/figcaption><\/figure>\n<p>After the merge, we can iterate through the array of the merged meetings and find the gaps. <\/p>\n<p>How can we implement the algorithm above? Let&#8217;s create type <code>Slot<\/code> that represents a time slot in the schedule; for simplicity, we use integers to denote the start and the end of the time slot instead of <code>time.Time<\/code> type.<\/p>\n<pre><code class=\"go\">type Slot struct {   start int   end int }<\/code><\/pre>\n<p>For each of the employees will have a sorted array of <code>Slots<\/code> (staring from the earliest time slot) that represent occupied time slots in their schedule. Our function will take an array of <code>Slot<\/code> arrays that represents the schedule for the whole team as an input parameter.<\/p>\n<pre><code>[][]Slot{   {{9, 10}},         \/\/ occupied time slots for John   {{1, 3}, {5, 6}},  \/\/ occupied time slots for James   {{2, 3}, {6, 8}},  \/\/ ...   {{4, 6} }<\/code><\/pre>\n<p>Our function will return an array of <code>Slots<\/code> that represent the commonly available slots for each member of the team. <\/p>\n<p>We merge the schedules of two employees by merging their arrays of <code>Slots<\/code>. How do we do the merge of two arrays?  We need to iterate through both arrays and see whether we have overlapping time slots. How can we know that the slots are overlapping? In general, we have two options for overlapping intervals A and B:<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/1b7\/5ac\/4bd\/1b75ac4bdc429a0f90ffc5322598057f.jpg\" width=\"1572\" height=\"636\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/1b7\/5ac\/4bd\/1b75ac4bdc429a0f90ffc5322598057f.jpg\" data-blurred=\"true\"\/><figcaption><\/figcaption><\/figure>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/1d1\/35c\/eb8\/1d135ceb8bc45daac9c3cfc3eccea255.jpg\" width=\"1474\" height=\"678\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/1d1\/35c\/eb8\/1d135ceb8bc45daac9c3cfc3eccea255.jpg\" data-blurred=\"true\"\/><figcaption><\/figcaption><\/figure>\n<p>If neither of the two above conditions is satisfied, then the intervals do not overlap. <\/p>\n<p>We can iterate through both of the arrays and see whether we have an overlap. Let&#8217;s <code>arr1<\/code> and <code>arr2<\/code> represent the occupied time slots for two employees. We start with <code>i1<\/code> and <code>i2<\/code> equal to zero and continue the iteration till we exhaust either of the arrays. At each step, we check whether the current intervals from each of the arrays overlap. <\/p>\n<pre><code>\/\/ Merge two array of slots. func Merge(arr1, arr2 []Slot) []Slot { out := make([]Slot, 0) i1, i2 := 0, 0 for i1 &lt; len(arr1) &amp;&amp; i2 &lt; len(arr2) { v1, v2 := arr1[i1], arr2[i2]<\/code><\/pre>\n<p>If the intervals overlap then we merge them. Let&#8217;s say interval <code>v1<\/code> from <code>arr1<\/code> ends earlier than <code>v2<\/code> from <code>arr2<\/code> In this case, the next interval from <code>arr1<\/code> can have an overlap with the merged interval of <code>v1<\/code> and <code>v2<\/code>. Therefore, we merge <code>v1<\/code> into <code>v2<\/code>, i.e. <code>v2.Start = min(v2.Start, v1.Start)<\/code> and <code>v2.Stop = max(v2.Stop, v1.Stop)<\/code> and increase <code>i1<\/code>. <code>i2<\/code> stays the same, therefore at the next iteration, we will check whether the next interval from <code>arr1<\/code> overlaps with the merged interval. <\/p>\n<pre><code>overlap12 := (v2.Start >= v1.Start) &amp;&amp; (v2.Start &lt;= v1.Stop) overlap21 := (v1.Start >= v2.Start) &amp;&amp; (v1.Start &lt;= v2.Stop) if overlap12 || overlap21 { merged := Slot{ Start: min(v1.Start, v2.Start), Stop:  max(v1.Stop, v2.Stop),   }   if v1.Stop &lt; v2.Stop {     arr2[i2] = merged     i1++   } else {     arr1[i1] = merged     i2++   }   continue }<\/code><\/pre>\n<p>If there is no overlap, we save the interval with the earliest stop (let&#8217;s say an interval <code>v2<\/code> from <code>arr2<\/code>) to the output array and increase index <code>i2<\/code> for <code>arr2.<\/code> The other interval <code>v1<\/code> from <code>arr1<\/code> can still have an overlap with the next interval from <code>arr2<\/code> so we don&#8217;t increase <code>i1<\/code>. <\/p>\n<pre><code>if v1.Stop &lt; v2.Stop { out = append(out, v1) i1++ } else { out = append(out, v2) i2++ }<\/code><\/pre>\n<p>When <code>i1<\/code> or <code>i2<\/code> becomes equal to the length of the corresponding array we stop the iteration. As no more overlaps are possible, we simply add the remaining intervals to the output array. <\/p>\n<p>The full listing for the function:<\/p>\n<pre><code>\/\/ Merge two arrays of meetings. func Merge(arr1, arr2 []Slot) []Slot {   out := make([]Slot, 0)   i1, i2 := 0, 0   for i1 &lt; len(arr1) &amp;&amp; i2 &lt; len(arr2) {     v1, v2 := arr1[i1], arr2[i2]          overlap12 := (v2.Start >= v1.Start) &amp;&amp; (v2.Start &lt;= v1.Stop)     overlap21 := (v1.Start >= v2.Start) &amp;&amp; (v1.Start &lt;= v2.Stop)     if overlap12 || overlap21 {       merged := Slot{         Start: min(v1.Start, v2.Start),         Stop:  max(v1.Stop, v2.Stop),       }       if v1.Stop &lt; v2.Stop {         arr2[i2] = merged         i1++       } else {         arr1[i1] = merged         i2++       }       continue     }      \/\/ no overlap; save the earliest of the intervals     if v1.Stop &lt; v2.Stop {       out = append(out, v1)       i1++       } else {       out = append(out, v2)       i2++       }   }      out = append(out, arr1[i1:]...)   out = append(out, arr2[i2:]...)    return out }<\/code><\/pre>\n<p>We merged the schedules of two colleagues. We need to merge all the schedules and then find the gaps within the merged schedule. How do find the gaps? We need to invert a <code>Slot<\/code> array considering the start and the end of the working day.<\/p>\n<pre><code>\/\/ Given an array of occupied time slots, \/\/ returns available time slots in range from 1 to 12. \/\/ We consider that working day starts at 1 and ends at 12. func inverseSchedule(schedule []Slot) []Slot {   start := 1   var out []Slot   for ind, appointment := range schedule {     if ind == 0 &amp;&amp; appointment.Start == 1 {       start = appointment.Stop       continue     }     out = append(out, Slot{Start: start, Stop: appointment.Start})     start = appointment.Stop   }   if start &lt; 12 {     out = append(out, Slot{Start: start, Stop: 12})   }   return out }<\/code><\/pre>\n<p>The resulting function looks as follows:<\/p>\n<pre><code>\/\/ AvailableSlots for all employees. \/\/ Working hours starts at 1 ends at 12. func AvailableSlots(schedule [][]Slot) []Slot { if len(schedule) == 0 { return []Slot{{1, 12}} } if len(schedule) == 1 { return inverseSchedule(schedule[0]) } merged := Merge(schedule[0], schedule[1]) for _, s := range schedule[2:] { merged = Merge(merged, s) }  return inverseSchedule(merged) }<\/code><\/pre>\n<p>Here you can find the<a href=\"https:\/\/gist.github.com\/a11310b036a62716ea70f8e38f5bcf21\" rel=\"noopener noreferrer nofollow\"> source code<\/a> and the <a href=\"https:\/\/gist.github.com\/c9ebf7979df346c2f54cc8d1996f10c8\" rel=\"noopener noreferrer nofollow\">tests<\/a> for the solution.<\/p>\n<p>In this post, we implemented a solution for the Merge Intervals problem. This is one of the most popular algorithmic patterns that solve an entire class of problems. More algorithmic patterns such as <a href=\"https:\/\/habr.com\/en\/post\/531444\/\" rel=\"noopener noreferrer nofollow\">Sliding Window<\/a> or <a href=\"https:\/\/habr.com\/en\/post\/545980\/\" rel=\"noopener noreferrer nofollow\">Iterative Postorder Traversal<\/a><a href=\"https:\/\/habr.com\/en\/post\/543618\/\" rel=\"noopener noreferrer nofollow\"> <\/a>can be found in the series <a href=\"https:\/\/habr.com\/en\/post\/545986\/\" rel=\"noopener noreferrer nofollow\">Algorithms in Go.<\/a> <\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/538888\/\"> https:\/\/habr.com\/ru\/articles\/538888\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>This is the third part of a <a href=\"https:\/\/habr.com\/en\/post\/545986\/\" rel=\"noopener noreferrer nofollow\">series<\/a> covering the implementation of algorithms in Go. In this article, we discuss the Merge Intervals algorithm. Usually, when you start learning algorithms you have to deal with some problems like finding the least common denominator or finding the next Fibonacci number. While these are indeed important problems, it is not something that we solve every day. Actually, in the vast majority of cases, we only solve such kinds of algorithms when we learn how to solve the algorithms. What I like about the Merge Interval algorithm is that we apply it in our everyday life, usually without even noticing that we are solving an algorithmic problem. <\/p>\n<p>Let&#8217;s say that we need to organize a meeting for our team. We have three colleagues Jay, May, and Ray and their time schedule look as follows (a colored line represents an occupied timeslot):<\/p>\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<p>Which timeslot should we pick up for a new meeting? One could look at the picture, find a gap in Ray&#8217;s schedule, and check whether the others a gap there as well. <\/p>\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<p>How can we implement this logic? Most straightforwardly, we can iterate through every minute of the day and check whether someone is having a meeting during that time. If none of the colleagues are occupied at that time, we find an available minute. <\/p>\n<p>How can we simplify this approach? Instead of checking all employees for every minute, we can merge their schedules and find the available slots in the resulting schedule.<\/p>\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<p>After the merge, we can iterate through the array of the merged meetings and find the gaps. <\/p>\n<p>How can we implement the algorithm above? Let&#8217;s create type <code>Slot<\/code> that represents a time slot in the schedule; for simplicity, we use integers to denote the start and the end of the time slot instead of <code>time.Time<\/code> type.<\/p>\n<pre><code class=\"go\">type Slot struct {   start int   end int }<\/code><\/pre>\n<p>For each of the employees will have a sorted array of <code>Slots<\/code> (staring from the earliest time slot) that represent occupied time slots in their schedule. Our function will take an array of <code>Slot<\/code> arrays that represents the schedule for the whole team as an input parameter.<\/p>\n<pre><code>[][]Slot{   {{9, 10}},         \/\/ occupied time slots for John   {{1, 3}, {5, 6}},  \/\/ occupied time slots for James   {{2, 3}, {6, 8}},  \/\/ ...   {{4, 6} }<\/code><\/pre>\n<p>Our function will return an array of <code>Slots<\/code> that represent the commonly available slots for each member of the team. <\/p>\n<p>We merge the schedules of two employees by merging their arrays of <code>Slots<\/code>. How do we do the merge of two arrays?  We need to iterate through both arrays and see whether we have overlapping time slots. How can we know that the slots are overlapping? In general, we have two options for overlapping intervals A and B:<\/p>\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<p>If neither of the two above conditions is satisfied, then the intervals do not overlap. <\/p>\n<p>We can iterate through both of the arrays and see whether we have an overlap. Let&#8217;s <code>arr1<\/code> and <code>arr2<\/code> represent the occupied time slots for two employees. We start with <code>i1<\/code> and <code>i2<\/code> equal to zero and continue the iteration till we exhaust either of the arrays. At each step, we check whether the current intervals from each of the arrays overlap. <\/p>\n<pre><code>\/\/ Merge two array of slots. func Merge(arr1, arr2 []Slot) []Slot { out := make([]Slot, 0) i1, i2 := 0, 0 for i1 &lt; len(arr1) &amp;&amp; i2 &lt; len(arr2) { v1, v2 := arr1[i1], arr2[i2]<\/code><\/pre>\n<p>If the intervals overlap then we merge them. Let&#8217;s say interval <code>v1<\/code> from <code>arr1<\/code> ends earlier than <code>v2<\/code> from <code>arr2<\/code> In this case, the next interval from <code>arr1<\/code> can have an overlap with the merged interval of <code>v1<\/code> and <code>v2<\/code>. Therefore, we merge <code>v1<\/code> into <code>v2<\/code>, i.e. <code>v2.Start = min(v2.Start, v1.Start)<\/code> and <code>v2.Stop = max(v2.Stop, v1.Stop)<\/code> and increase <code>i1<\/code>. <code>i2<\/code> stays the same, therefore at the next iteration, we will check whether the next interval from <code>arr1<\/code> overlaps with the merged interval. <\/p>\n<pre><code>overlap12 := (v2.Start >= v1.Start) &amp;&amp; (v2.Start &lt;= v1.Stop) overlap21 := (v1.Start >= v2.Start) &amp;&amp; (v1.Start &lt;= v2.Stop) if overlap12 || overlap21 { merged := Slot{ Start: min(v1.Start, v2.Start), Stop:  max(v1.Stop, v2.Stop),   }   if v1.Stop &lt; v2.Stop {     arr2[i2] = merged     i1++   } else {     arr1[i1] = merged     i2++   }   continue }<\/code><\/pre>\n<p>If there is no overlap, we save the interval with the earliest stop (let&#8217;s say an interval <code>v2<\/code> from <code>arr2<\/code>) to the output array and increase index <code>i2<\/code> for <code>arr2.<\/code> The other interval <code>v1<\/code> from <code>arr1<\/code> can still have an overlap with the next interval from <code>arr2<\/code> so we don&#8217;t increase <code>i1<\/code>. <\/p>\n<pre><code>if v1.Stop &lt; v2.Stop { out = append(out, v1) i1++ } else { out = append(out, v2) i2++ }<\/code><\/pre>\n<p>When <code>i1<\/code> or <code>i2<\/code> becomes equal to the length of the corresponding array we stop the iteration. As no more overlaps are possible, we simply add the remaining intervals to the output array. <\/p>\n<p>The full listing for the function:<\/p>\n<pre><code>\/\/ Merge two arrays of meetings. func Merge(arr1, arr2 []Slot) []Slot {   out := make([]Slot, 0)   i1, i2 := 0, 0   for i1 &lt; len(arr1) &amp;&amp; i2 &lt; len(arr2) {     v1, v2 := arr1[i1], arr2[i2]          overlap12 := (v2.Start >= v1.Start) &amp;&amp; (v2.Start &lt;= v1.Stop)     overlap21 := (v1.Start >= v2.Start) &amp;&amp; (v1.Start &lt;= v2.Stop)     if overlap12 || overlap21 {       merged := Slot{         Start: min(v1.Start, v2.Start),         Stop:  max(v1.Stop, v2.Stop),       }       if v1.Stop &lt; v2.Stop {         arr2[i2] = merged         i1++       } else {         arr1[i1] = merged         i2++       }       continue     }      \/\/ no overlap; save the earliest of the intervals     if v1.Stop &lt; v2.Stop {       out = append(out, v1)       i1++       } else {       out = append(out, v2)       i2++       }   }      out = append(out, arr1[i1:]...)   out = append(out, arr2[i2:]...)    return out }<\/code><\/pre>\n<p>We merged the schedules of two colleagues. We need to merge all the schedules and then find the gaps within the merged schedule. How do find the gaps? We need to invert a <code>Slot<\/code> array considering the start and the end of the working day.<\/p>\n<pre><code>\/\/ Given an array of occupied time slots, \/\/ returns available time slots in range from 1 to 12. \/\/ We consider that working day starts at 1 and ends at 12. func inverseSchedule(schedule []Slot) []Slot {   start := 1   var out []Slot   for ind, appointment := range schedule {     if ind == 0 &amp;&amp; appointment.Start == 1 {       start = appointment.Stop       continue     }     out = append(out, Slot{Start: start, Stop: appointment.Start})     start = appointment.Stop   }   if start &lt; 12 {     out = append(out, Slot{Start: start, Stop: 12})   }   return out }<\/code><\/pre>\n<p>The resulting function looks as follows:<\/p>\n<pre><code>\/\/ AvailableSlots for all employees. \/\/ Working hours starts at 1 ends at 12. func AvailableSlots(schedule [][]Slot) []Slot { if len(schedule) == 0 { return []Slot{{1, 12}} } if len(schedule) == 1 { return inverseSchedule(schedule[0]) } merged := Merge(schedule[0], schedule[1]) for _, s := range schedule[2:] { merged = Merge(merged, s) }  return inverseSchedule(merged) }<\/code><\/pre>\n<p>Here you can find the<a href=\"https:\/\/gist.github.com\/a11310b036a62716ea70f8e38f5bcf21\" rel=\"noopener noreferrer nofollow\"> source code<\/a> and the <a href=\"https:\/\/gist.github.com\/c9ebf7979df346c2f54cc8d1996f10c8\" rel=\"noopener noreferrer nofollow\">tests<\/a> for the solution.<\/p>\n<p>In this post, we implemented a solution for the Merge Intervals problem. This is one of the most popular algorithmic patterns that solve an entire class of problems. More algorithmic patterns such as <a href=\"https:\/\/habr.com\/en\/post\/531444\/\" rel=\"noopener noreferrer nofollow\">Sliding Window<\/a> or <a href=\"https:\/\/habr.com\/en\/post\/545980\/\" rel=\"noopener noreferrer nofollow\">Iterative Postorder Traversal<\/a><a href=\"https:\/\/habr.com\/en\/post\/543618\/\" rel=\"noopener noreferrer nofollow\"> <\/a>can be found in the series <a href=\"https:\/\/habr.com\/en\/post\/545986\/\" rel=\"noopener noreferrer nofollow\">Algorithms in Go.<\/a> <\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/538888\/\"> https:\/\/habr.com\/ru\/articles\/538888\/<\/a><br \/><\/br><\/br><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-417004","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/417004","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=417004"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/417004\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=417004"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=417004"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=417004"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}