Реализация функций zip и range Python в JavaScript

zip

zip берет дополнительные элементы (элементы с одинаковым индексом) из двух массивов и объединяет их в один элемент (кортеж)

Пример:

const arr1 = [1, 2, 3]
const arr2 = [4, 5, 6]
// : <- means returns

zip(arr1, arr2) :  [[1,4], [2,5],[3,6]]

Поскольку JavaScript не имеет кортежей, мы будем использовать внутренний массив. Он является кортежем, в зависимости от того, как вы используете массив в JS, его можно назвать по-разному, в том числе и кортежем.

Пример:

function tuple(){
   return [1, 2]

} // this function is said to return a tuple

let [one, two] = tuple()

// this is actually the concept react hooks use, example useState()

let [value, setValue] = useState()  // react code

zip очень полезен для объединения массивов, сохраняя при этом порядок элементов массива (это станет ясно при распаковке), zip особенно полезен в мире науки о данных, например, для построения диаграмм: создание кортежей координаты, объединяющие данные обучения (X) и метки (Y) в единую структуру

Функция zip

function zip(array1, array2){
     
   let zipped = []
   
    for(let i = 0; i < array1.length; i++){

          zipped.push([array1[i], array2[i]])

    }

  

    return zipped
    
}

объяснение:

for(let i = 0; i < array1.length; i++)

мы используем array1.length, потому что zip останавливается, как только заканчивается первый массив. Это одно простое правило, которому следует zip, что означает, что если длина вашего первого массива больше, чем второго, это выдаст ошибку.

 zipped.push([array1[i], array2[i]])

Мы помещаем новый массив (кортеж) в заархивированный с дополнительными элементами из каждого массива

console.log(zip([1, 2, 3], [4, 5, 6])) // [ [ 1, 4 ], [ 2, 5 ], [ 3, 6 ] ]

Чтобы деструктурировать массив в исходные массивы, мы можем фактически использовать ту же функцию, сделав второй массив необязательным, если нет второго массива, это означает, что заархивированный массив передается в рефакторинг:

function zip(array1, array2){
     
if(array2 === undefined){
  // unzip


}
     
 else{
    // zip
 	 let zipped = [] 

     for(let i = 0; i < list1.length; i++){



       zipped.push([list1[i], list2[i]])

     }



      return zipped

   }
    
}

раскрытие:

if(array2 === undefined){
  // unzip
   let list1_ = []   // will hold the original elements 
   let list2_ = []
   
    for(let i =0; i < array1.length; i++){

     list1_[i] = array1[i][0]

     list2_[i] = array1[i][1]

   }
    
  return [list1_, list2_]
   


}

объяснение:

list1_[i] = array1[i][0]

list2_[i] = array1[i][1]

Здесь мы получаем i-й кортеж и назначаем элементы в кортеже в соответствии с их индексом, 0 – первый, 1 – второй

У нас есть функции zip, которая может это все распаковать:

const zipped = zip([1, 2, 3], [4, 5, 6])

console.log(zipped) // [ [ 1, 4 ], [ 2, 5 ], [ 3, 6 ] ]

let [arr1, arr2] = zip(zipped)

console.log(arr1, arr2) // [ 1, 2, 3 ] [ 4, 5, 6 ]

Мы так же можем создать другую версию, которая прикрепляется к объектам в виде кортежей (удобно использовать для создания координат для диаграмм)

function zipToObjCoord(arr1, arr2){

 let zipped = []

 for(let i = 0; i < arr1.length; i++){

       let key = arr1[i]

      zipped.push({ x:key, y:arr2[i]})
 
 }

return zipped

}

Та же концепция, но для создания координат

console.log(zipToObjCoord([1, 2, 3], [4, 5, 6])) // [ { x: 1, y: 4 }, { x: 2, y: 5 }, { x: 3, y: 6 } ]

Диапазон функции

Диапазон принимает число (n) и возвращает «циклическую структуру» от 0 до n, более сложный диапазон fn принимает начало, конец и номер шага.

Простая реализация

Мы можем просто реализовать это, используя массив, range возвращает массив с числами от 0 до n:

function range(n){

let r = []

   for(let i = 0; i < n; i++){

   	r[i] = i

   }

  return r

}
for(let i of range(10)){
// works but very problematic
    
}

Что, если мы хотим создать диапазон 4 000 000, это означает, что диапазон должен сначала выполнить цикл 4 миллиона раз и создать массив со значениями от 0 до 4 миллионов, затем for..of может начать цикл еще 4 миллиона раз.

Эффективная реализация

Решение создает элемент @@ iterator

@@ iterator

Прежде чем мы даже перейдем к @@ iterator, позвольте мне объяснить концепции, лежащие в основе итераторов и коллекций,

Коллекция – это массив элементов (расходные элементы), итерации – это коллекции, которые определяют протокол итератора

Протокол итератора

Массив реализует протокол итератора, который сообщает for … of циклу, как выполнять итерацию самого массива, в основном массив говорит через протокол, если вы пытаетесь повторить его. Это форма взаимодействия между ними, поскольку for … of ожидает, что массив будет реализовывать протокол iter, а массив ожидает, что for … of понимает протокол iter.

{ // object 
  next(){  // w/ a next function 
  
     return {}  // which returns an object
  
  }


}

Gриближение к объекту, возвращаемому next

 // this object has a value and "state"  called done a boolean indicate whether we are at the end of an array
 
 {value: "element in the array", done: false}

Это означает, что этот объект может принимать две формы

  1. не в конце массива
 {value: "element in the array", done: false}
  1. в конце массива
{done: true}

Когда цикл for … of по массиву ищет этот объект и вызывает следующую функцию в зависимости от того, что возвращает следующий цикл for … of продолжается или останавливается

for(let i of [1, 2, 3]){
   console.log(i)


}


// 1st iter -> [1, 2, 3].next() returns {value: 1, done: false}
// 2nd iter -> [1, 2, 3].next() returns {value: 2, done: false}
// 3rd iter -> [1, 2, 3].next() returns {value: 3, done: false}
// 4rd iter -> [1, 2, 3].next() returns {done: true} // end of the array 

На каждой итерации значение возвращается или присваивается i, когда done становится истинным, for … of останавливает цикл, потому что мы находимся в конце массива.

Выполнение

Единственное, что мы реализуем, это следующую функцию, в JS есть объект symbol.iterator (@@ iterator), все, что нам нужно сделать, это настроить, как работает следующая функция,

Примечание: вы можете использовать итеративный алгоритм в любом случае, помимо коллекций

Например, в этом случае мы не выполняем цикл над коллекцией, а генерируем число на каждой итерации

function range(n){
    let i = 0 // start
 
    return { // iterator protocol
     
       
      [Symbol.iterator]:() =>{ // @@iterator
      
           return { // object with the next function
         
            next(){
             while(i !== n){
             let temp = i
             i++
             
             return {

               value: temp, 

               done: false

             }
           
           
           }
           
            return {done: true}
          
          
          }
 			   
         
         
         }
       
      
      
      }
  
      
  
  }
 
 
 }

Дополнение к протоколу итератора – это упаковка объекта, который возвращается следующим

  [Symbol.iterator]:() =>{ // @@iterator function

Объяснение:

[Symbol.iterator]:()// simply : allows array like behaviour(what for..of) looks for
            next(){ // the next we defined above 
          
             while(i !== n){  // will loop as long as i is not equal n(passed in val)
             let temp = i
             i++
             
             return {

               value: temp,   // returns the value

               done: false

             }
           
           
           }
           
            return {done: true}  // when done looping the range
          
          
          }

Для надежной реализации диапазона, вы можете добавить start, stop и step в качестве дополнительных функций.

for(let i of range(10)){

   console.log(i)

 }

Эффективная реализация против простой

Переименуем простую функцию диапазона в Nrange

 let start, finish

  

start = Date.now()

for(let i of Nrange(10)){


}

end = Date.now()

  

console.log("naive", end- start, " ms")

  

 start = Date.now()

 for(let i of range(10)){

 //  console.log(i)

 }

 end = Date.now()

  

 console.log("robust", end- start, " ms")

1 тест: 10

range(10) vs Nrange(10)

naive 0  ms
robust 1  ms

Простая функция работает лучше, чем эффективная.

2-й тест: 10 тысяч

range (10000) vs Nrange (10000)

naive 7  ms
robust 11  ms

3-й тест: 40 тысяч

range(40000) vs Nrange (40000)

naive 29  ms
robust 18  ms

4-й тест: 400 000

range(400000) vs Nrange(400000)

naive 106  ms
robust 32  ms

Финальный тест: 4 миллиона

range(4_000_000) vs Nrange(4_000_000)

naive 650  ms
robust 97  ms

При увеличении выбора простая реализация рушиться.

Ответить