首页 > 解决方案 > 试图在数组中找到确切单词的索引,而不仅仅是匹配的第一个单词的索引

问题描述

我有 2 个要解决的问题:我正在尝试为圣经应用程序制作圣经游戏。

  1. 问题是我的经文中有多次相同的词(例如“起初上帝创造了天地”有3次)。我现在设置的方式适用于仅在圣经中出现一次的单词,但对于单词“the”,我的代码当前将找到与“the”匹配的第一个数组项并将其移动到数组中的其他位置。例如:如果我有乱七八糟的经文——“起初上帝创造天地 the the在创造和天堂之间它移动第一个(在和开始之间的那个)。如何更改我的比较功能以便找到正确的?

  2. 我希望我的单词数组/项目列表在单词被拖到下一个单词时重新排列,而不仅仅是在单词被删除时。如果您对此有任何想法,请告诉我。

<template>
  <div>
    <!-- <div :scripture="scripture">{{ scripture }}</div> -->
    <ul :scripture="scriptureArray" id="list"></ul>
    <div :verse="verse">- {{ verse }}</div>
  </div>
</template>

<script>
let vm;
export default {
  data() {
    return {
      dragging: "",
      draggedOver: "",
      scriptureArray: [],
    };
  },
  props: {
    scripture: {
      type: String,
      required: true,
      default: "",
    },
    verse: {
      type: String,
      required: true,
      default: "",
    },
    correct: {
        type: String,
        required: true,
        default: "",
    }
  },
  methods: {
    renderDraggableItems(scriptureArr) {
      let list = document.getElementById("list");
      list.innerText = "";
      scriptureArr.forEach((word) => {
        var node = document.createElement("li");
        node.draggable = true;
        node.addEventListener("drag", vm.setDragging);
        node.addEventListener("dragover", vm.setDraggedOver);
        node.addEventListener("dragend", vm.compare);
        node.innerText = word;
        list.appendChild(node);
      });
    },
    setDragging(e) {
      this.dragging = Number.isNaN(parseInt(e.target.innerText))
        ? e.target.innerText
        : parseInt(e.target.innerText);
    },
    setDraggedOver(e) {
      e.preventDefault();
      this.draggedOver = Number.isNaN(parseInt(e.target.innerText))
        ? e.target.innerText
        : parseInt(e.target.innerText);
    },
    compare(e) {
      e.preventDefault();
      var index1 = this.scriptureArray.indexOf(this.dragging);
      var index2 = this.scriptureArray.indexOf(this.draggedOver);
      this.scriptureArray.splice(index1, 1);
      this.scriptureArray.splice(index2, 0, this.dragging);
      console.log("scriptureArray:", this.scriptureArray);
      this.renderDraggableItems(this.scriptureArray);
      // this way works as long as no 2 words in the scripture are the same (text matching), is there another way?
    },
  },
  mounted() {
    vm = this;
    this.scriptureArray = this.scripture.split(" ");
    this.renderDraggableItems(this.scriptureArray);
  },
};
</script>

<style scopped>
#list {
  list-style: none;
  font-size: 30px;
  display: flex;
  justify-content: space-evenly;
}
</style>

这个组件是这样调用的

<template>
  <!-- add a component that shows the scrptures, keeps track of time, and allows unscrabling -->
  <div>
    <Timer @GameOver="handleGameOver" />
    <DraggableSentence
      :scripture="scrambledCurrentScripture"
      :verse="currentScriptureVerse"
      :correct="correctCurrentScripture"
    />
    <!-- TODO: create a level for each key in genesis  -->
  </div>
</template>

在哪里

scrambledCurrentScripture = "the begging In God created the earth heavens and the"
currentScriptureVerse = "genesis 1:1"
correctCurrentScripture = "In the beginning God created the heave and the earth"

标签: javascriptarraysvue.jsdrag-and-dropdom-events

解决方案


您所拥有的有许多可以改进的地方:

  • 加扰版本中的单词与正确答案中的单词不同。如果这是给一些孩子看的,我可以想象他们变老了,还在玩耍,因为无法破解而感到沮丧
  • 使用 Vue 时不应该执行 DOM 操作。并不是说它不起作用,而是 Vue 在有效处理和更新 DOM 方面是一辆赛车。当你手动做的时候,有点像从驾驶座下来然后把它推到路上(只需更新源数组,让 Vue 根据值的变化来处理 DOM)
  • 处理拖放时,您需要元素的唯一标识符。他们的电流index就足够了。(.indexOf()返回匹配条件的第一个元素 - 这就是您的compare方法失败的原因)。

这里是:

Vue.config.devtools = false;
Vue.config.productionTip = false;
new Vue({
  el: '#app',
  data: () => ({
    to: "",
    from: "",
    words: [],
    verse: "Genesis 1:1",
    correctAnswer: "In the beginning God created the heavens and the earth"
  }),
  computed: {
    isCorrectOrder() {
      return this.words.join(' ') === this.correctAnswer;
    }
  },
  methods: {
    onDragEnd() {
      const word = this.words[this.from];
      this.words.splice(this.from, 1);
      this.words.splice(this.to, 0, word);
    },
  },
  mounted() {
    // basic shuffle
    this.words = this.correctAnswer.split(" ")
      .map(s => ({s, r: Math.random()}))
      .sort((a, b) => a.r > b.r ? -1 : 1)
      .map(o => o.s);
  },
})
ul {
  list-style: none;
  font-size: 24px;
  display: flex;
  justify-content: space-evenly;
  padding-left: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
  <ul>
    <li v-for="( value, index ) in words"
        :key="index"
        draggable="true"
        @drag="from = index"
        @dragover.prevent="to = index"
        @dragend.prevent="onDragEnd"
        v-text="value" />
  </ul>
  <div :verse="verse">- {{ verse }}</div>
  
  <h4><em v-if="isCorrectOrder">You got it right!</em></h4>
</div>


就第二个请求而言,我的建议是使用Vue.draggable ,它是围绕sortable.js的 Vue 包装器,这是一个使用拖放 HTML5 api 的小而强大的 d&d 包,具有​​旧版浏览器的后备和触摸兼容。

它还将简化标记,因为您不再需要指定拖动事件:

Vue.config.devtools = false;
Vue.config.productionTip = false;
new Vue({
  el: '#app',
  data: () => ({
    words: [],
    verse: "Genesis 1:1",
    correctAnswer: "In the beginning God created the heavens and the earth"
  }),
  computed: {
    isCorrectOrder() {
      return this.words.join(' ') === this.correctAnswer;
    }
  },
  mounted() {
    this.words = _.shuffle(this.correctAnswer.split(" "));
  },
})
.drag-list {
  font-size: 24px;
  display: flex;
  justify-content: space-evenly;
  padding-left: 0;
  margin: 40px 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="//cdn.jsdelivr.net/npm/sortablejs@1.8.4/Sortable.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/Vue.Draggable/2.20.0/vuedraggable.umd.min.js"></script>

<div id="app">
  <draggable v-model="words" class="drag-list">
    <div v-for="( value, index ) in words"
         :key="index">{{value}}</div>
  </draggable>
  <div :verse="verse">- {{ verse }}</div>
  <h4><em v-if="isCorrectOrder">You got it right!</em></h4>
</div>


忍不住加了一个计时器。太诱人了……

Vue.config.devtools = false;
Vue.config.productionTip = false;
new Vue({
  el: '#app',
  data: () => ({
    words: [],
    verse: "Genesis 1:1",
    correctAnswer: "In the beginning God created the heavens and the earth",
    start: performance.now(),
    current: performance.now()
  }),
  computed: {
    isCorrectOrder() {
      return this.words.join(' ') === this.correctAnswer;
    },
    timeElapsed() {
      return Math.round((this.current - this.start) / 100) / 10;
    }
  },
  methods: {
    tick() {
      this.current = performance.now();
      if (!this.isCorrectOrder) {
        setTimeout(this.tick, 100);
      }
    }
  },
  mounted() {
    this.words = _.shuffle(this.correctAnswer.split(" "));
    this.tick();
  },
})
.drag-list {
  font-size: 24px;
  display: flex;
  justify-content: space-evenly;
  padding-left: 0;
  margin: 40px 10px;
}

.flexer {
  display: flex;
  align-items: center;
}

code {
  padding-left: 2rem;
}

code:after {
  content: 's'
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="//cdn.jsdelivr.net/npm/sortablejs@1.8.4/Sortable.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/Vue.Draggable/2.20.0/vuedraggable.umd.min.js"></script>

<div id="app">
  <draggable v-model="words" class="drag-list">
    <div v-for="( value, index ) in words" :key="index">{{value}}</div>
  </draggable>
  <div :verse="verse">- {{ verse }}</div>
  <div class="flexer">
    <h4><em v-if="isCorrectOrder">You got it right!</em></h4>
    <code v-text="timeElapsed"></code>
  </div>
</div>


推荐阅读