JavaScript’te Özyinelemenin Gücü – JSManifest

JavaScript'te Özyinelemenin Gücü – JSManifest

özyineleme bir fonksiyonun basitçe kendisini çağırdığı bilgisayar programcılığında güçlü bir kavramdır. Temel bilgileri öğrendikten sonra mümkün olan en kısa sürede özyinelemenin nasıl çalıştığını öğrenmenin ne kadar önemli olduğunu vurgulayamam.

Özyineleme kavramını ve nasıl oluşturulacağını anlamak, daha sağlam kod yazmanıza yardımcı olabilecek bir programcı gibi düşünmenize yardımcı olacaktır.

Özyinelemenin Faydaları

Genel olarak, durumlarda özyineleme uygularken, bundan elde ettiğiniz hemen hemen her zaman şu faydalar vardır:

  1. Kod satırlarını kaydedersiniz
  2. Kodunuz daha temiz görünebilir (böylece niyetiniz olmasa bile temiz kod uygulamaları uygulayın)
  3. Kod yazarken ve hata ayıklarken zamandan tasarruf etmenize yardımcı olur
  4. Bir algoritmayı çalıştırmak için gereken süreyi azaltır (zaman karmaşıklığı)
  5. Ağaç yapıları ile çalışırken sorunları kolayca çözmeye yardımcı olur
  6. Yardımcı olur görselleştirmek algoritmalar (Bana inanmıyor musun?)

Özyinelemenin Dezavantajları

  1. BT Yapabilmek daha yavaş olun – bu, yığının daha fazlasını kaplar (ek yük)
  2. Bir döngüden daha fazla bellek kullanırsa kuyruk çağrısı optimizasyonu kullanılmaz

İhtiyacımız var mı?

Pratikte, yinelemeyi kullanarak herhangi bir algoritmayı gerçekleştirebilirsiniz. Bilmen gereken şey şu ki ne zaman özyineleme uygulamak en iyisidir – ve ancak bu şekilde yineleme kullanmak yerine özyinelemeyi daha iyi bir seçim haline getirir.

En iyi sonuç veren durumlarda özyineleme uygularken, özyineleme gücü tıpkı özyinelemeyi uygulamanın ne kadar güçlü olduğu gibi Hanoi kulesi sorun.

Örnekler

Özyinelemeyi anlamanın iyi bir yolu, bir sorunu çözmek için özyineleme uygulayan çalışan bir koda bakmaktır.

Çapraz Nesneler

Daha önce de belirtildiği gibi, özyinelemeler, ağaç yapılarıyla çalışırken sorunları kolayca çözmeye yardımcı olabilir. Derinlemesine yuvalanmış bir nesne bir ağaç yapısıdır, bu yüzden bir nesneyle çalışacağız.

Her iç içe nesne nesnesinin öğelerin alt öğelerine sahip olabileceği HTML DOM öğelerini temsil eden bir nesnemiz olduğunu varsayın. Her çocuk başka bir HTML DOM öğesidir ve çocukları da olabilir, bu nedenle ebeveynleri tarafından kaç tane yavru üretildiğine bağlı olarak gerçekten çok büyük bir nesne olabilir.

Amacımız, ne kadar iç içe olursa olsun her bir nesneye dokunmaktır. onlara bakacağız style özelliği (bu belirli HTML öğesinin niteliklerini temsil eder) ve border, textColor ve width JavaScript ile çalışırken normal şekilde okunabilmeleri için stil temsillerine özellik.

İşte değiştirilmesi gereken bir stil nesnesi örneği:

{
  "border": {
    "color": "hotpink",
    "width": "2px"
  },
  "textColor": "violet",
  "width": "0.45"
}

Html’de metinleri renklendirmek için color mülk, bu yüzden dönüştürmek zorunda kalacağız textColor ile color. İçin widthbu ondalık sayıların, kullanıcının cihazının görüntü alanının yüzdesini temsil ettiğini varsayalım (bunun 45vw), ve border nesnenin aşağıdaki gibi bir şekle dönüştürülmesi gerekiyor { borderColor: 'hotpink', borderWidth: '2px' }

Bu benzer yapıyı temsil eden bir nesneyle çalışalım, böylece onu çaprazlayabilir ve tüm stil nesnelerini düzeltebiliriz:

{
  "type": "div",
  "style": {},
  "children": [
    {
      "type": "div",
      "style": {
        "backgroundColor": "black",
        "border": {
          "color": "hotpink",
          "width": "2px",
          "style": "dashed"
        },
        "fontStyle": "italic",
        "padding": "20px 25px",
        "textColor": "white"
      },
      "children": [
        {
          "type": "button",
          "style": {
            "backgroundColor": "#fda512",
            "border": {
              "color": "red"
            },
            "textColor": "#ffffff"
          }
        },
        {
          "type": "label",
          "style": {
            "height": "0.04",
            "width": "0.04"
          },
          "children": [
            {
              "type": "label",
              "style": {
                "border": {
                  "style": "solid",
                  "width": "5px"
                },
                "fontStyle": "italic"
              },
              "children": [
                {
                  "type": "span",
                  "style": {
                    "backgroundColor": "#039392",
                    "borderRadius": "10px",
                    "height": "0.03",
                    "outline": "none",
                    "width": "0.783"
                  }
                }
              ]
            }
          ]
        }
      ]
    }
  ]
}

Tamam, burada iç içe nesnelerin meydana geldiği bir ağaç yapımız var. children Emlak.

Yaratacağımız ilk şey bir transformStyleObject Düzeltmek için bir stil nesnesi alan, JavaScript ve DOM’da normal olarak çalışılabilecek yeni bir nesne döndüren işlev:

function transformStyleObject(styleObj) {
  const result = {}
  const keys = Object.keys(styleObj)
  keys.forEach((key) => {
    if (key === 'border') {
      const { color, width, style } = styleObj.border
      if (color) result.borderColor = color
      if (width) result.borderWidth = width
      if (style) result.borderStyle = style
    } else if (key === 'textColor') {
      result['color'] = styleObj.textColor
    } else if (key === 'width') {
      result['width'] = `${Number(styleObj.width) * 100}vw`
    } else if (key === 'height') {
      result['height'] = `${Number(styleObj.height) * 100}vh`
    } else {
      result[key] = styleObj[key]
    }
  })
  return result
}

const result = transformStyleObject({
  border: {
    width: '2px',
    style: 'dashed',
  },
  height: '0.42',
})

console.log(result) 

Nesneleri geçmek için normal yinelemeyi kullanabiliriz:

function transformAll({ type = '', style = {}, children = [] }) {
  const result = { type, style: transformStyleObject(style), children }
  if (Array.isArray(result.children)) {
    for (let index = 0; index < result.children.length; index++) {
      const child = result.children[index]
      child.style = transformStyleObject(child.style)
      if (Array.isArray(child.children)) {
        for (
          let childIndex = 0;
          childIndex < child.children.length;
          childIndex++
        ) {
          const childsChildren = child.children[childIndex]
          childsChildren.style = transformStyleObject(childsChildren.style)
          if (Array.isArray(childsChildren.children)) {
            for (
              let childsChildsChildrenIndex = 0;
              childsChildsChildrenIndex < childsChildren.children.length;
              childsChildsChildrenIndex++
            ) {
              const childsChildsChild =
                childsChildren.children[childsChildsChildrenIndex]
              
            }
          }
        }
      }
    }
  }
  return result
}

Ancak şu nedenlerle zahmetli olmaya başlar:

  1. daha uzun olur
  2. Okumak zorlaşıyor
  3. Hata ayıklamak zorlaşıyor
  4. Değişikliklere karşı daha duyarlı hale gelir
  5. Test etmek zorlaşıyor
  6. Yorucu oluyor çünkü daha fazla değişken ismi düşünmek zorundasın

Bunun yerine, yukarıda listelenen altı sorunun tümünü çözen bir özyineleme kullanılabilir:

function transformAll({ type = '', style = {}, children = [] }) {
  const result = { type, style: transformStyleObject(style), children }
  if (Array.isArray(result.children)) {
    result.children = result.children.map(transformAll)
  }
  return result
}
{
  "type": "div",
  "style": {},
  "children": [
    {
      "type": "div",
      "style": {
        "backgroundColor": "black",
        "borderColor": "hotpink",
        "borderWidth": "2px",
        "borderStyle": "dashed",
        "fontStyle": "italic",
        "padding": "20px 25px",
        "color": "white"
      },
      "children": [
        {
          "type": "button",
          "style": {
            "backgroundColor": "#fda512",
            "borderColor": "red",
            "color": "#ffffff"
          },
          "children": []
        },
        {
          "type": "label",
          "style": {
            "height": "4vh",
            "width": "4vw"
          },
          "children": [
            {
              "type": "label",
              "style": {
                "borderWidth": "5px",
                "borderStyle": "solid",
                "fontStyle": "italic"
              },
              "children": [
                {
                  "type": "span",
                  "style": {
                    "backgroundColor": "#039392",
                    "borderRadius": "10px",
                    "height": "3vh",
                    "outline": "none",
                    "width": "78.3vw"
                  },
                  "children": []
                }
              ]
            }
          ]
        }
      ]
    }
  ]
}

Uygulamamız artık çok daha zarif ve okunması daha kolay görünüyor! Bu özyineleme şu şekilde çalışır:

  1. transformAll bir tane al bekar bir HTML DOM öğesini temsil eden nesne.
  2. Bu öğenin stil niteliklerini dönüştürür (bizim durumumuzda her HTML DOM öğesi için hedefimiz budur)
  3. Öğenin durumunu kontrol ederek iç içe öğelerin olup olmadığını kontrol eder. children Emlak
  4. Varsa, bu işlev her çocuk arasında dolaşacak ve kendisini yeniden arayacaktır. transformAll her çocukta.
  5. Bu özyinelemeyi başlatır ve döngüye girer her aracılığıyla bulabileceği nesne children ağaç ne kadar derine giderse gitsin.

Dosya ve Klasörlerle Çalışmak

Şahsen daha işlevsel kod yazmayı harika bir deneyim buluyorum. Ve işlevsel kod olduğunda, daha fazla zarafet vardır. Özyineleme buna iyi uyuyor.

Bir dizinin altındaki her dizine bakacak bir program oluşturalım. dosya yoluadlı klasörleri tara __test__ ve dosya adlarını arayarak uygulanmayan herhangi bir birim testi olup olmadığını tespit edin. .test.js. Her klasör bir “modül”ve biz onu varsayacağız yapmaz eğer varsa, bunun için birim testleri uygulansın değil sahip olmak __test__ dosya ya da değil herhangi bir dosyan var onların içinde __test__ dosya ile biten .test.js.

Bir modül için bir test olduğunu tespit ederse, bize bu dizinin tamamı hakkında aşağıdaki gibi bilgiler içeren bir nesne döndürür:

{
  "../javascript-algorithms/src/algorithms/math/linked-list": {
    "name": "linked-list",
    "category": "algorithms",
    "subcategory": "math",
    "totalFiles": 0,
    "filesList": []
  }
}

Bu işlemin nihai sonucu, her nesnenin henüz birim testleri olmadığı için dikkatimizi gerektiren bir klasörü (bizim durumumuzda bir modül olan) temsil ettiği bu nesnelerin bir dizisidir.

Bunu gerçekleştirmek için özyineleme kolayca kullanılabilir.

ben kullandım https://github.com/trekhleb/javascript-algorithms repo, içindeki her şeyi çıkardı src dizini ve kasıtlı olarak bazı örneklerinde birkaç birim testi kaldırdı, böylece kodumuz bu konumları sonucumuzda döndürebilir.

İlerideki kod parçacıkları, yerel modülleri şuradan içe aktarır: düğümler.

İlk önce ithal edeceğiz fs ve geçişi başlatmak için bir kök dizin bildirin:

import fs from 'fs'

const rootDir = '../javascript-algorithms/src'

Ardından, kullanacağız isDirectory yöntemden fs modül daha sonra dizinlere ne zaman girileceğini algılamak için. Şahsen bunu bir işleve sarmayı tercih ediyorum çünkü tam yöntemi yazmayı sevmiyorum:

function isDirectory(filePath) {
  return fs.statSync(filePath).isDirectory()
}

Ayrıca adında bir fonksiyon oluşturacağız. hasTest bu bir dizi diziyi alır, bunlar arasında döngü yapar ve bir test dosyası olduğunu bulursa geri döner trueveya false aksi halde:

function hasTest(testDir) {
  for (let index = 0; index < testDir.length; index++) {
    const filename = testDir[index]
    if (filename.endsWith('.test.js')) {
      return true
    }
  }
  return false
}

Şimdi ana işlev için onu arayacağız findEmptyTests uygulanan herhangi bir testi olmayan tüm modülleri toplamaktan sorumludur:

function findEmptyTests(basepath) {
  let emptyTests = {}

  if (isDirectory(basepath)) {
    const dir = fs.readdirSync(basepath)

    for (let index = 0; index < dir.length; index++) {
      const filename = dir[index]
      const filepath = `${basepath}/${filename}`

      if (isDirectory(filepath)) {
        if (filename === '__test__') {
          const testDir = fs.readdirSync(filepath)
          if (!hasTest(testDir)) {
            emptyTests[filepath] = createMissingTestsObject(basepath, testDir)
          }
        } else {
          emptyTests = { ...emptyTests, ...findEmptyTests(filepath) }
        }
      }
    }
  }
  return emptyTests
}

çağırdığı için bunun bir özyineleme olduğunu görebiliriz. kendisi bu satırda:

emptyTests = { ...emptyTests, ...findEmptyTests(filepath) }

En önemli kısım hangisi!

Bu fonksiyonun çalışma şekli şudur: findEmptyTests başlamak için bir dosya yolundan geçerek.

Dosya yolu girdiğimiz takdirde bir dizindirdizindeki tüm dosyaları okuyacak ve dosya adlarını dir dizi.

Hangisinin dizin olduğunu kontrol edebilmemiz için daha sonra bir döngü gerçekleştirilir. Geçerli yinelemeden bir dizinle karşılaşırsa filepathiki koşulu kontrol edecektir:

  1. Geçerli yinelenen dosya yolu, __test__ dizinin kendisi? Eğer öyleyse, ile biten herhangi bir dosya olup olmadığını görmek için o dizini kontrol edin. .test.js. Değilse, o modülün depodaki konumu hakkında bilgi alırız.
  2. Geçerli yinelenen dosya yolu olumsuzluk a __test__ dizin ama hala bir dizin? Eğer öyleyse, o dizinin içinde gezinin ve tüm işlevi başlatın. o dizinin içindeve bundan sonraki dizin vb.

Son olarak, işlemi bitirdiğinde sonuç döndürülür.

muhtemelen fark etmişsindir createMissingTestsObject işlev. Bu yalnızca bir dosya yolu ve dizini hakkında bilgi toplayan bir işlevdir:

function createMissingTestsObject(str, dir) {
  const indexToSrc = str.indexOf('src')
  let category = str.substring(indexToSrc + 4)
  let subcategory = category.substring(category.indexOf("https://jsmanifest.com/") + 1)
  subcategory = subcategory.substring(0, subcategory.indexOf("https://jsmanifest.com/"))
  category = category.substring(0, category.indexOf("https://jsmanifest.com/"))
  return {
    name: str.substring(str.lastIndexOf("https://jsmanifest.com/") + 1),
    category,
    subcategory,
    totalFiles: dir.length,
    filesList: dir,
  }
}

Bu şimdi bize birim testleri eksik olan güzel bir konum nesnesi döndürmelidir!

{
  "../javascript-algorithms/src/algorithms/math/fourier-transform/__test__": {
    "name": "fourier-transform",
    "category": "algorithms",
    "subcategory": "math",
    "totalFiles": 1,
    "filesList": ["FourierTester.js"]
  },
  "../javascript-algorithms/src/algorithms/sets/cartesian-product/__test__": {
    "name": "cartesian-product",
    "category": "algorithms",
    "subcategory": "sets",
    "totalFiles": 0,
    "filesList": []
  },
  "../javascript-algorithms/src/algorithms/sets/combination-sum/__test__": {
    "name": "combination-sum",
    "category": "algorithms",
    "subcategory": "sets",
    "totalFiles": 0,
    "filesList": []
  }
}

Bir cevap yazın

E-posta hesabınız yayımlanmayacak.