【问题标题】:Efficient algorithm for determining values not as frequent in a list用于确定列表中不那么频繁的值的有效算法
【发布时间】:2014-06-28 13:10:05
【问题描述】:

我正在构建一个测验应用程序,它会从问题池中随机抽取问题。但是,要求问题池仅限于用户尚未看到的问题。然而,如果用户已经看过所有问题,那么算法应该“重置”并且只显示用户看过一次的问题。也就是说,始终向用户显示他们从未见过的问题,或者,如果他们已经看到所有这些问题,则总是先向他们显示他们不经常看到的问题,然后再显示他们经常看到的问题。

列表 (L) 的创建方式满足以下条件:列表 (I) 中的任何值都可能存在一次或在列表中重复多次。让我们在列表中定义另一个值 J,使其与 I 的值不同。那么0 <= abs(frequency(I) - frequency(J)) <= 1 将始终为真。

换句话说:如果一个值在列表中重复 5 次,并且 5 次是列表中任何值重复的最大次数,那么列表中的所有值将重复 4 次或5次。该算法应该先用frequency == 4返回列表中的所有值,然后再用frequency == 5返回任何值。

对不起,这太冗长了,我正在努力简洁地定义这个问题。请随时向 cmets 提出问题,如果需要,我将进一步获得资格。

提前感谢您提供的任何帮助。

澄清

感谢您迄今为止提出的答案。我认为它们中的任何一个都还没有。让我进一步解释。

我没有与用户互动并向他们提问。我将问题 ID 分配给考试记录,以便在用户开始考试时确定他们有权访问的问题列表。因此,我有两种数据结构可供使用:

  • 用户有权访问的可能问题 ID 列表
  • 此用户之前分配的所有问题 ID 的列表。这是上面描述的列表 L。

因此,除非我弄错了,否则此问题的算法/解决方案将需要涉及使用上述两个列表的基于列表和/或集合的操作。

结果将是一个问题 ID 列表,我可以将其与考试记录相关联,然后插入到数据库中。

【问题讨论】:

  • 我认为问题比你说的要简单得多。我在下面有您的解决方案,它不使用多个列表或枢轴,并且更容易编码。让我知道是否有不足之处。您使用的是 Python 2 还是 3?
  • @AaronHall 请参阅我的问题中的说明。另外,我使用的是 Python 2。
  • 需求蔓延;即使是编码人员也无法抗拒。我相信我编辑的答案可以为您节省一步并获得相同的信息。

标签: python algorithm list frequency


【解决方案1】:

用填充伪代码中的数据库内容重写。

如果我正确理解了这个问题,我会将问题(或他们的 ID 作为代理)视为一副纸牌:对于每个用户,洗牌并一次向他们处理一个问题;如果他们想要的不仅仅是len(deck) 问题,那就重新开始吧:将牌组洗成新的顺序,然后再做一次。当一个问题出现nth 次时,所有其他问题都将出现nn-1 次。

为了跟踪用户有哪些可用问题,我们将未使用问题 ID 放回数据库中,并在需要新交易时增加“通过次数”计数器。

类似:

from random import shuffle

def deal():
    question_IDs = get_all_questions(dbconn) # all questions
    shuffle(question_IDs)
    increment_deal_count(dbconn, userID) # how often this student has gotten questions
    return question_IDs


count_deals = get_stored_deals(dbconn, userID) # specific to this user
if count_deals: 
    question_IDs = get_stored_questions(dbconn, userID) # questions stored for this user 
else: # If 0 or missing, this is the first time for this student
    question_IDs = deal()


while need_another_question(): #based on exam requirements
    try:
        id = question_IDs.pop()
    except IndexError:
        question_IDs = deal()
        id = question_IDs.pop() # Trouble if db is ever empty. 

    use_question(id) # query db with the ID, then put question in print, CMS, whatever

# When we leave that while loop, we have used at least some of the questions
# question_IDs lists the *unused* ones for this deal
# and we know how many times we've dealt.

store_in_db(dbconn, userinfo, question_IDs)
# If you want to know how many times a question has been available, it's
# count_deals - (ID in question_IDs)
# because True evaluates to 1 if you try to subtract it from an integer. 

【讨论】:

  • 你只需要对问题列表中没有被选择的部分进行洗牌,否则这将不满足最常被选择的问题比最不常见的问题只被选择一次的条件.此外,将整个未选择的问题列表打乱以随机选择一个问题是过度的和不必要的。
  • 这只会在未选择整个问题列表(或重新开始时)时随机播放问题列表,所以我不认为第一句话有问题。我同意,如果问题列表很大,那么对所有内容进行洗牌是低效的。但如果它不是很大,这种方法比枢轴更简单。 (编辑添加,对于编码人员来说更简单。)
  • 啊,你说得对;我今天一定有一些阅读困难。
  • 按元素而不是索引遍历列表更像是 Pythonic。
  • 我同意@AaronHall,n.b.
【解决方案2】:

为什么没有两个列表,一个用于尚未选择的问题,一个用于已选择的问题。最初,尚未选择的列表将是完整的,您将从其中选择元素,这些元素将被删除并添加到选择的列表中。一旦未选中列表为空,重复上述相同过程,这次使用完整选中列表作为未选中列表,反之亦然。

【讨论】:

  • 并且每次都打乱完整列表,以满足随机性要求?好的。 (Duh,随机选择。没关系。)
  • 一个单独的列表远不如简单地遍历一次打乱列表的效率。
  • @cphlewis 是的,完全正确。 :)
  • @AaronHall 我不希望这里有性能问题。根据我根据 OP 的问题收集到的信息,将“已挑选”和“尚未挑选”视为两个不同的元素集合在逻辑上是有道理的,这就是我喜欢这种方法的原因。
  • 感谢您的意见。因为我通过查询数据库来获取我的两个列表,所以我不能按照你的建议做。请参阅我更新后的问题,了解我开始使用的两个列表的性质。
【解决方案3】:

要实现你的算法,你只需要打乱列表并通过它,完成后重复。

不需要复制列表或在两个列表之间玩弄项目,只需使用以下控制流,例如:

import random

def ask_questions(list_of_questions):
    while True:
        random.shuffle(list_of_questions)
        for question in list_of_questions:
            print(question)
            # Python 3 use input not raw_input
            cont = raw_input('Another question?') 
            if not cont:
                break
        if not cont:
            break

【讨论】:

    【解决方案4】:

    让我们定义一个将列表分成两部分的“枢轴”。枢轴对数组进行分区,使得在枢轴之前的所有数字都比在枢轴之后的数字多一个(或更一般地,在枢轴之前的所有数字都没有资格被挑选,而在枢轴之后的所有数字都有资格被挑选)。

    您只需从枢轴后的数字列表中选择一个随机项目,将其与枢轴上的数字交换,然后增加枢轴。当枢轴到达列表的末尾时,您可以将其重置回开始。

    或者,您也可以使用两个列表,这更容易实现,但稍微效率较低,因为它需要扩展/收缩列表。大多数情况下,易于实施会胜过低效率,因此两个列表通常是我的首选。

    【讨论】:

    • 我认为你的建议是有价值的。问题是,我如何确定我的支点在哪里?请参阅我更新的问题,以澄清我必须使用的两个列表的性质。
    【解决方案5】:

    这是我想出的:

    from collections import Counter
    import random
    
    # the number of question ids I need returned to
    # assign to the exam
    needed = 3
    
    # the "pool" of possible question ids the user has access to
    possible = [1,2,3,4,5]
    
    # examples of lists of question ids I might see that represent
    # questions a user has already answered
    answered1 = []
    answered2 = [1,3]
    answered3 = [5,4,3,2]
    answered4 = [5,4,3,2,1,1,2]
    answered5 = [5,4,3,2,1,1,2,3,4,5,1]
    answered6 = [5,4,3,2,1]
    
    def getdiff(answered):
        diff = set(possible) - set(answered)
        still_needed = needed - len(diff)
        if still_needed > 0:
            not_already_selected = list(set(possible) - diff)
            random.shuffle(not_already_selected)
            diff = list(diff) + not_already_selected[0:still_needed]
            random.shuffle(diff)
            return diff
        diff = list(diff)
        random.shuffle(diff)
        if still_needed == 0:
            return diff
        return diff[0:needed]
    
    def workit(answered):
        """ based on frequency, reduce the list down to only
            those questions we want to consider "answered"
        """
        have_count = 0
        if len(possible) > len(answered):
            return getdiff(answered)
        counted = Counter(answered)
        max_count = max(counted.values())
        # the key here is to think of "answered" questions as
        # only those that have been seen with max frequency
        new_answered = []
        for value, count in counted.iteritems():
            if count == max_count:
                new_answered.append(value)
        return getdiff(new_answered)
    
    print 1, workit(answered1)
    print 2, workit(answered2)
    print 3, workit(answered3)
    print 4, workit(answered4)
    print 5, workit(answered5)
    print 6, workit(answered6)
    
    """
    >>> 
    1 [2, 4, 3]
    2 [2, 5, 4]
    3 [5, 2, 1]
    4 [5, 3, 4]
    5 [2, 4, 3]
    6 [2, 3, 5]
    >>> ================================ RESTART ================================
    >>> 
    1 [3, 1, 4]
    2 [5, 2, 4]
    3 [2, 4, 1]
    4 [5, 4, 3]
    5 [4, 5, 3]
    6 [1, 5, 3]
    >>> ================================ RESTART ================================
    >>> 
    1 [1, 2, 3]
    2 [4, 2, 5]
    3 [4, 1, 5]
    4 [5, 4, 3]
    5 [2, 5, 4]
    6 [2, 1, 4]
    """
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-03-11
      • 1970-01-01
      • 2020-02-14
      • 1970-01-01
      • 2023-03-18
      • 1970-01-01
      • 2020-12-29
      相关资源
      最近更新 更多