Skip to content

도서 리뷰 시리즈 - 자바스크립트로 하는 자료 구조와 알고리즘

출처

『자바스크립트로 하는 자료 구조와 알고리즘』 배세민 저/김무항 역 | 에이콘출판사 | 2019년 08월 30일

한 줄 리뷰

자바스크립트로 자료 구조와 알고리즘을 쉽게 학습해 볼 수 있는 도서

9장 집합

  • 가장 근간이 된 자료 구조 중 하나
  • 유한하고 구분되는 객체들의 그룹
  • 정렬되지 않는 유일한 항목들의 그룹
js
const set = new Set();
set.add(1);
set.delete(1);
set.add(2);
console.log(set.has(1), set.has(2)); // false true

교집합

js
const intersectSets = (setA, setB) => {
  const intersection = new Set();
  for (const elem of setB) {
    if (setA.has(elem)) {
      intersection.add(elem);
    }
  }
  return intersection;
};

console.log(
  intersectSets(
     new Set([1, 2, 3, 4]),
     new Set([2, 3]),
  )
); // Set {2, 3}

합집합

js
const unionSets = (setA, setB) => {
  const union = new Set(setA);
  for (const elem of setB) {
    union.add(elem);
  }
  return union;
};
// 또는
const unionSets = (setA, setB) => {
  return new Set([...setA, ...setB]);
};

console.log(
  unionSets(
     new Set([1, 2, 3, 4]),
     new Set([2, 3]),
  )
); // Set {1, 2, 3, 4}

차집합

js
const differenceSets = (setA, setB) => {
  const difference = new Set(setA);
  for (const elem of setB) {
    difference.delete(elem);
  }
  return difference;
};

console.log(
  differenceSets(
     new Set([1, 2, 3, 4]),
     new Set([2, 3]),
  )
); // Set {1, 4}

18장 고급 문자열

트라이(접두사 트리)

  • 문자열을 검색해 저장된 문자열 중 일치하는 문자열이 있는 지 확인하는데 주로 사용되는 특별한 종류의 트리이다.
  • 각 단계에서 노드는 단어를 완성하기 위해 가지를 친다.
  • 각 마지막 노드에는 endOfWord라는 불리언 플래그가 있다.
    • 이는 어떤 단어가 해당 경로에서 종료되는지 여부를 나타낸다.
    • 예를 들어 Sam에서 m은 endOfWord는 true로 설정된다.
  • 트라이는 중첩 객체를 사용해 구현된다. 이때 각 노드는 자신과 직접 연결된 자식들을 지니는 데 이 자식들은 키 역할을 한다.
  • 트라이는 루트 노드가 존재한다.
  • [삽입] 신규 노드를 삽입할 때 해당 노드가 루트의 자식으로 존재하지 않는 경우 해당 노드를 루트의 자식으로 생성해야 한다.

ES6로 작성한 예제

js
class TrieNode {
  constructor() {
    this.children = {}
    this.endOfWord = false
  }
}

class Trie {
  constructor() {
    this.root = new TrieNode()
  }
  insert(word) {
    let current = this.root
    Array.from(word).forEach(ch => {
      let node = current.children[ch]
      if (!node) {
        node = new TrieNode()
        current.children[ch] = node
      }
      current = node
    })
    current.endOfWord = true
  }
  search(word) {
    let current = this.root
    for(const ch of Array.from(word)) {
      const node = current.children[ch]
      if (!node) {
        return false
      }
      current = node
    }
    return current.endOfWord
  }
  delete(word) {
    this.deleteRecursively(this.root, word, 0)
  }
  deleteRecursively(current, word, index) {
    if (index === word.length) {
      if (!current.endOfWord) {
        return false
      }
      current.endOfWord = false
      return Object.keys(current.children).length === 0
    }
    const ch = word[index]
    const node = current.children[ch]
    if (!node) {
      return false
    }
    const shouldDeleteCurrentNode = this.deleteRecursively(node, word, index + 1)
    if (shouldDeleteCurrentNode) {
      delete current.children[ch]
      return Object.keys(current.children).length === 0
    }
    return false
  }
}

const trie = new Trie()
trie.insert('sammie')
trie.insert('simran')
console.log(trie.search('simran'))
trie.delete('sammie')
trie.delete('simran')
console.log(trie.search('sammie'))
console.log(trie.search('simran'))

OOP로 작성한 예제

js
class TrieNode {
  constructor() {
    this.children = new Map()
    this.endOfWord = false
  }
  addChild(key) {
    this.children.set(key, new TrieNode())
  }
  getChild(key) {
    return this.children.get(key)
  }
  removeChild(key) {
    delete this.children.delete(key)
  }
  hasChild(key) {
    return this.children.has(key)
  }
  hasChildren() {
    return !!this.children.size
  }
  isEndWord() {
    return this.endOfWord
  }
  setEndWord() {
    this.endOfWord = true
  }
  resetEndWord() {
    this.endOfWord = false
  }
}

class Trie {
  constructor() {
    this.root = new TrieNode()
  }
  insert(word) {
    let current = this.root
    Array.from(word).forEach(ch => {
      if (!current.hasChild(ch)) {
        current.addChild(ch)
      }
      current = current.getChild(ch)
    })
    current.setEndWord()
  }
  search(word) {
    let current = this.root
    for (const ch of Array.from(word)) {
      if (!current.hasChild(ch)) {
        return false
      }
      current = current.getChild(ch)
    }
    return current.isEndWord()
  }
  delete(word) {
    this.deleteRecursively(this.root, word, 0)
  }
  deleteRecursively(current, word, index) {
    if (index === word.length) {
      if (current.isEndWord()) {
        current.resetEndWord()
        return !current.hasChildren()
      }
    } else {
      const ch = word[index]
      if (current.hasChild(ch)) {
        const shouldDeleteCurrentNode = this.deleteRecursively(
          current.getChild(ch),
          word,
          index + 1
        )
        if (shouldDeleteCurrentNode) {
          current.removeChild(ch)
          return !current.hasChildren()
        }
      }
    }
    return false
  }
}

console.group('trie-oop')
const trie = new Trie()
trie.insert('sammie')
trie.insert('simran')
trie.insert('sam')
trie.insert('sarada')
trie.insert('apple')
console.log(trie.search('simran') === true)
console.log(trie.search('sam') === true)
console.log(trie.search('sarada') === true)
console.log(trie.search('apple') === true)
trie.delete('sammie')
trie.delete('simran')
console.log(trie.search('sammie') === false)
console.log(trie.search('simran') === false)
console.groupEnd('trie-oop')

보이어-무어 문자열 검색

  • 문자열 내에서 패턴을 검색할 때 인덱스를 건너 뜀으로서 선형 시간에 검색이 가능하다.
  • 인덱스의 문자열이 패턴에 존재하는 경우 앞으로 건너뜀으로써 문자열 비교 횟수를 줄이는 최적화된 반복 방식을 확인 할 수 있다.
  • 건너뛰기 규칙을 구현하기 위해 불일치 표 구조를 만들 수 있다.

ES6로 작성한 예제

js
const buildBadMatchTable = str => {
  const table = {}
  const {length} = str

  for (let i = 0; i < length - 1; i++) {
    table[str[i]] = length - 1 - i
  }

  if (table[str[length - 1]] === undefined) {
    table[str[length - 1]] = length
  }
  return table
}

console.log(
  buildBadMatchTable('data')
)
console.log(
  buildBadMatchTable('struct')
)
console.log(
  buildBadMatchTable('roi')
)
console.log(
  buildBadMatchTable('jam')
)

const boyerMoore = (str, pattern) => {
  const badMatchTable = buildBadMatchTable(str)
  let offset = 0
  let patternLastIndex = pattern.length - 1
  let scanIndex = patternLastIndex
  let maxOffset = str.length - pattern.length

  while (offset <= maxOffset) {
    scanIndex = 0
    while(pattern[scanIndex] === str[scanIndex + offset]) {
      if (scanIndex === patternLastIndex) {
        return offset
      }
      scanIndex++
    }
    const badMatchString = str[offset + patternLastIndex]
    if (badMatchTable[badMatchString]) {
      offset += badMatchTable[badMatchString]
    } else {
      offset += 1
    }
  }
  return -1
}

console.log(
  boyerMoore('jellyjam', 'jam')
)
console.log(
  boyerMoore('jellyjam', 'jelly')
)
console.log(
  boyerMoore('jellyjam', 'sam')
)